I have a problem with a post_save function. The function is correctly triggered but the instance doesn't contains the value insereted. I checked the function using ipdb and there is nothing wrong. Simply the ManyToManyField is empty.
The code:
#receiver(post_save, sender=Supplier)
def set_generic_locations(sender, instance, **kwargs):
""" Set the generic locations for the NEW created supplier.
"""
created = kwargs.get('created')
if created:
glocations = LocationAddress.get_generic_locations()
for location in glocations:
instance.locations.add(location)
instance.save()
The field used in the instance:
locations = models.ManyToManyField(LocationAddress, blank=True)
I don't understand why, but the locations is always empty.
I use django 1.8.8
UPDATE
The problem is the django admin. I found an explanation here: http://timonweb.com/posts/many-to-many-field-save-method-and-the-django-admin/
The code that solve the problem in the django admin
def save_related(self, request, form, formsets, change):
super(SupplierAdmin, self).save_related(request, form, formsets, change)
form.instance.set_generic_locations()
ManyToManyFields work a little bit differently with signals because of the difference in database structures. Instead of using the post_save signal, you need to use the m2m_changed signal
For manytomanyfield, you have to save first your parent object () and then add your
Related
I am new to python and django and I want to know the best way to set things up.
I have a model called OauthProviders which I set up to encrypt some fields before save in the ModelViewSet (override perform_create method). I dont want to create routes (urls) for this model.
Now, if I want to access this model in the code (I can with OauthProvider.objects.all() of course), but I have a few questions:
how do I enter data to this model NOT in code? If I use the admin portal for it, it doesn't execute my custom perform_create method, so it gets added to the database in plain text
What is the best way to decrypt a message if I retrieve data?
EDIT:
I moved the logic from the ModelViewSet to the save() method on the model with the following code:
def save(self, *args, **kwargs):
self.credentials = encrypt_message(self.credentials, '<keyhere>')
return super().save(*args, **kwargs)
This seems to work. Is this good practice? How to do this with the get method?
A good way of doing this is using django signals.
Assuming you have:
class OauthProvider(models.Model):
pass # some fields here
In your case you can use either:
pre_save - code that will be executed before OauthProvider gets saved in the database.
post_save - code that will be executed after OauthProvider gets saved in the database.
It's up to you to decide when exactly you want to execute additional code.
Example with pre_save:
from django.db.models.signals import pre_save
from django.dispatch import receiver
class OauthProvider(models.Model):
pass # some fields here
#receiver(pre_save, sender=OauthProvider)
def oauthprovider_on_save(sender, instance, **kwargs):
instance.credentials = encrypt_message(self.credentials, '<keyhere>')
# no need to call instance.save() in pre_save signal
The pre_save signal will be executed:
If you call obj.save() programatically.
If you save the obj through django admin site.
If you create/update the obj within rest api
I'm trying to get chosen objects from affiliate_networks in Django admin when the user clicks the submit button.
When I choose one from affiliate_networks and submit it, the console prints an empty of affiliate_networks, and then I come back to the page and the chosen object is stored properly. So, I submit it again, then the console prints the chosen object. save() only receives objects that are already stored, not objects that I choose before saving.
Is there a way, that I can have save() to notice affiliate_networks to have any object chosen?
class Store(models.Model):
...
affiliate_networks = models.ManyToManyField(AffiliateNetwork, blank=True)
def save(self, *args, **kwargs):
print(self.affiliate_networks.all())
You can't do it in save() - as you have discovered, Django admin doesn't save ManyToMany objects until afterwards. You need to do it in the save_related method of ModelAdmin. See https://timonweb.com/posts/many-to-many-field-save-method-and-the-django-admin/
In admin.py:
...
class StoreAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
super(StoreAdmin, self).save_related(request, form, formsets, change)
print(form.instance.affiliate_networks.all())
...
admin.site.register(Store, StoreAdmin)
The default notification setting for a wagtail user instance seems to be "Receive notification when a page is submitted for moderation", which does not fit our needs: we do not want each and every wagtail user to be notified (emailed) when a page gets submitted for moderation.
The default for this user notification setting is "True":
# wagtail/wagtail/users/models.py
# https://github.com/wagtail/wagtail/blob/master/wagtail/users/models.py#L25
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
submitted_notifications = models.BooleanField(
verbose_name=_('submitted notifications'),
default=True,
help_text=_("Receive notification when a page is submitted for moderation")
)
… and I would like to change the default to "False" (e.g. users should opt-in for getting email notifications).
But I do not know what’s the “best practices approach” for this task.
And I was a little surprised to have this problem at all, since I assumed that only members of the "Moderators" group/role would be notified. (which does not seem to be true).
What I tried/thought of:
Modifying this setting pro grammatically (which worked):
Existing user instances could be modified via shell:
./manage.py shell
from django.contrib.auth import get_user_model
User = get_user_model()
for user in User.objects.all():
user.wagtail_userprofile.update(submitted_notifications=False)
user.wagtail_userprofile.save()
Modifying this setting in my CustomUser save() method (does not work):
# project/users/models.py (pseudocode)
class CustomUser(AbstractUser):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.wagtail_userprofile.update(submitted_notifications=False)
self.wagtail_userprofile.save()
This raises a "RelatedObjectDoesNotExist" error.
Adding a data migration to alter the default value (just an idea – I did not know if that’s an option or how to implement this).
Setup:
django 2.0.8
wagtail 2.2.2
You can use a post save signal
import UserProfile from wagtail.users.models
#receiver(post_save, sender=UserProfile)
def update_user_profile(sender, instance, created, **kwargs):
if created:
instance.submitted_notifications = False
instance.save()
So this triggers when ever a new Profile is created and sets the 'submitted_notifications' default to False. Hope it helps
The reason updating the related profile in the save method raises a RelatedObjectDoesNotExist is because related objects are created after save. The db transaction needs to commit to get a primary key. Obviously you need a pk to store the relation.
This is why signals work. The post save signal is fired after the db transaction is finished. I think a signal is a valid solution to your problem.
You can use transaction.on_commit in your save method too. On commit waits for the transaction to be finished.
Sometimes you need to perform an action related to the current database transaction, but only if the transaction successfully commits. Examples might include a Celery task, an email notification, cache invalidation or setting some default in a related object. This is the documentation on that topic:
https://docs.djangoproject.com/en/1.11/topics/db/transactions/#performing-actions-after-commit
In your model do something like:
from django.db import transaction
class Foo(...):
...
def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs)
if not self.pk:
transaction.on_commit(self.update_profile)
return instance
def update_profile(self):
self.wagtail_userprofile.update(submitted_notifications=False)
# Note: update, no need to call save.
# Signals will not be fired on behalf of the UserProfile.
Disclaimer, I did not run/test this code.
I have a pre-save signal listener that updates a second model. The same as this example:
Django Signals to update a different model
I'd like to let the user know that the listener succeeded in updating the model and provide some information. Normally, I would think I could use the built in messages functionality that django has. The problem is that the signal doesn't have access to 'request'. So I can't see how to use the built in Django Messages Framework.
https://docs.djangoproject.com/en/dev/ref/contrib/messages/
Is there a known method for sending a message to the user in the admin? Maybe by overriding the save() method for one of the models? (the one sending the signal, or receiving), but I don't think the save() method has access to 'request' either?
This must be something others want to do as well?
You can override save_model method in ModelAdmin. Something like this:
from django.contrib import messages
# your imports
...
# your code
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
# you can just call super(YourModelAdminName, self).save_model(request, obj, form, change)
messages.add_message(request, messages.INFO, 'Text of message')
I am using django-voting as a voting application for two of my models. Those both models have fields "author".
How can I restrict a user from voting on a model that has this particular user set as it's author without modifying django-voting app?
Django middleware is the first thing that comes to my mind, but I don't understand it's "proces_view" function. If you think middleware is the right way could you please give an example of how to do it.
Add this code anywhere in your settings.py:
from voting.managers import VoteManager
def check_user(func):
def wrapper(self, obj, user, vote):
if obj.user != user:
return func(self, obj, user, vote)
else:
return None
# or raise some exception
return wrapper
VoteManager.record_vote = check_user(VoteManager.record_vote)
I didn't run this code, maybe it's incorrect, but I hope idea is clear
Rather than a middleware hack, why not reroute requests to that particular URI through another view? Then you can performs whatever logic you like, and subsequently call the original view if appropriate.
Another idea is to use the post_save signal
like so:
from django.db.models.signals import post_save
from voting.models import Vote
def check_user(sender, instance, **kwargs):
if instance.user == instance.object.user:
instance.delete()
# do some other stuff to tell the user it didn't work
post_save.connect(check_user, sender=Vote)
The benefit of doing this vs overriding VoteManager.record_vote is that it's less tightly coupled to the voting module, and if they make changes it's less likely to break your code
edit: as in Glader's answer, you need to make sure that all the objects you're voting on have a 'user' attribute.