Pass additional attributes with Django signals - python

Is there any way to pass additional parameters to instance I'm saving in DB to later access them after the instance is saved?
Brief example of my case:
I'm using Django's signals as triggers to events, like sending a confirmation email, executed by other processes, like workers.
I'm willing to specify which instance and when should trigger the event, and which should not: sometimes I want created/updated records to trigger series of events, and sometimes I want them to be processes silently or do some other actions.
One solution for this is saving desired behaviour for specific instance in model's field like JSONField and recover this behaviour at post_save, but this seems very ugly way of handlign such problem.
I'm using post_save signal as verification that instance was correctly saved in the DB, because I don't want to trigger event and a moment later something goes wrong while saving instance in DB.
Instances are saved through Django Forms, backend routines and RestFramework Seralizers

One solution is to use an arbitrary model instance attribute (not field) to store the desired state. For example:
def my_view(request):
...
instance._send_message = True if ... else False
instance.save()
#receiver(post_save, sender=MyModel)
def my_handler(sender, instance, **kwargs):
if instance._send_message:
...

Related

Django - Connect to post_init signal on model change form only

I would like to use a post_init signal connection on a model instance to call a remote API which will fetch some data and then use this as a sort of dynamic model property that gets assigned to that instance when the change form is loaded.
Specifically, I have another system that uses a completely different framework that has a model that is (mostly) the same as a model I have in Django. The post_init signal should check the remote version to see if there are any images saved to it, and then assign this boolean as a dynamic model property to the Django model instance.
This would give me a condition to check during the model's post_save signal that I can use to determine if I need to upload images to the remote model or not.
Since this will be calling a remote system, I don't want the API call in post_init to be called for every single model instance when loading the admin model list page, I only want it to happen on the change form.
How can I exclude this expensive API call from all post_init signals except for when it is triggered from the model instance's admin change form?
If i understood you correct, you need to make the API call when post_save is emitted. you have the option to check if the instance is created or updated.
#receiver(post_save, sender=Your Model)
def upload_image(sender, instance, created, **kwargs):
try:
#This will check if the instance is created or updated
if created:
#upload the image or do any api calls
#update your dynamic field on the instance
except Exception as e:
print(e)
This way you will be making only one API call after creation. If you want it to happen only during updates, just change the condition to
if not created :

Django: Using signals with inline models

Using Django 2.2
I have two models, Job and Operation, where Operation has a foreign key relation to Job (ie, a job can have 0 or more operations).
I want people to enter info about jobs and operations together via the Django Admin interface, using inlines. (This means that on the Admin site, on the job create/edit page, a user can add one or more operations "inline" without leaving the page.)
Job has a few fields computed from its related operations. Rather than simply make them computed #property properties, I want them to be regular database fields that get updated when the operations change via signals. It looks something like this:
class Job(models.Model):
name = models.CharFields(...)
def compute_fields(self):
qs = self.operations.filter(...) # get data from operations
self.name = ... # set properties using that data
self.save()
...
class Operation(models.Model):
job = models.ForeignKey(Job, related_name="operations", on_delete=models.CASCADE)
...
#receiver(post_save, sender=Operation)
def update_job_on_operation_save(sender, instance, **kwargs):
"""Update job fields when an operation is saved"""
instance.job.compute_fields()
Here is the issue: If someone is editing the Job form on the Django Admin, and they add multiple operations inline before hitting save, then the receiver function gets called multiple times simultaneously. I am a little worried about a race condition, as well as the inefficiency of each signal leading Job to recompute some properties and save to the database.
It would be better, perhaps, to attach the receiver to Job, so the function only gets called once, but if someone were to edit an Operation outside of the Job form that should also trigger a recompute.
Is it possible to set up similar post_save receivers for both Job and Operation and say "ignore the operation receiver if the operation was edited inline as part of the job form"? Are there alternative solutions?

Call a function after saving a model

I'm working on a Django application connected to a LDAP server. Here's the trick i'm trying to do.
I have a model called system containing some information about computers. When i add a new system, the model generates a unique UUID, like and AutoField. The point is that this parameter is generated when saving, and only the first time.
After saved, i need a function to keep that UUID and create a new object on my LDAP.
Since i didn't know a lot about signals, i tried overriding the model save function in this way:
def save(self):
# import needed modules
import ldap
import ldap.modlist as modlist
[--OPERATIONS ON THE LDAP--]
super(System, self).save()
In this way, if i modify an existing system all work as should, because its UUID has been generated already. But if i try adding a new system i find the error UUID is None, and i can't work with an empty variable on LDAP (also it would be useless, don't u think so?)
It seems i need to call the function that work on LDAP after the system is saved, and so after an UUID has been generated. I tried to unserstand how to create a post_save function but i couldn't get it.
How can i do that?
Thanks
As you stated on your own, you do need signals, it will allow your code to stay more clean and seperate logic between parts.
The usual approach is to place signals just at the end of your models file:
# Signals
from django.dispatch import receiver
#receiver(models.signals.post_save, sender=YourModel)
def do_something(sender, instance, created, **kwargs):
....
On the above example we connect the post_save signal with the do_something function, this is performed through the decorator #receiver, sender of the decorator points to your Model Class.
Inside your function you have instance which holds the current instance of the model and the created flag which allows you to determine if this is a new record or an old (if the Model is being updated).
Signals would be excellent for something like this, but moving the line super(System, self).save() to the top of the save method might work as well. That means you first save the instance, before passing the saved object to the LDAP.

when to use pre_save, save, post_save in django?

I see I can override or define pre_save, save, post_save to do what I want when a model instance gets saved.
Which one is preferred in which situation and why?
I shall try my best to explain it with an example:
pre_save and post_save are signals that are sent by the model. In simpler words, actions to take before or after the model's save is called.
A save triggers the following steps
Emit a pre-save signal.
Pre-process the data.
Most fields do no pre-processing — the field data is kept as-is.
Prepare the data for the database.
Insert the data into the database.
Emit a post-save signal.
Django does provide a way to override these signals.
Now,
pre_save signal can be overridden for some processing before the actual save into the database happens - Example: (I dont know a good example of where pre_save would be ideal at the top of my head)
Lets say you have a ModelA which stores reference to all the objects of ModelB which have not been edited yet. For this, you can register a pre_save signal to notify ModelA right before ModelB's save method gets called (Nothing stops you from registering a post_save signal here too).
Now, save method (it is not a signal) of the model is called - By default, every model has a save method, but you can override it:
class ModelB(models.Model):
def save(self):
#do some custom processing here: Example: convert Image resolution to a normalized value
super(ModelB, self).save()
Then, you can register the post_save signal (This is more used that pre_save)
A common usecase is UserProfile object creation when User object is created in the system.
You can register a post_save signal which creates a UserProfile object that corresponds to every User in the system.
Signals are a way to keep things modular, and explicit. (Explicitly notify ModelA if i save or change something in ModelB )
I shall think of more concrete realworld examples in an attempt to answer this question better. In the meanwhile, I hope this helps you
pre_save
it's used before the transaction saves.
post_save
it's used after the transaction saves.
You can use pre_save for example if you have a FileField or an ImageField and see if the file or the image really exists.
You can use post_save when you have an UserProfile and you want to create a new one at the moment a new User it's created.
Don't forget about recursions risk.
If you use post_save method with instance.save() calling, instead of .update method, you should disconnect your post_save signal:
Signal.disconnect(receiver=None, sender=None,
dispatch_uid=None)[source] To disconnect a receiver from a signal,
call Signal.disconnect(). The arguments are as described in
Signal.connect(). The method returns True if a receiver was
disconnected and False if not.
The receiver argument indicates the registered receiver to disconnect.
It may be None if dispatch_uid is used to identify the receiver.
... and connect it again after.
update() method don't send pre_ and post_ signals, keep it in mind.

How to use Post_save in Django

I am trying to add points to a User's profile after they submit a comment- using the Django comment framework. I think I need to use a post_save but am not sure to be perfectly honest.
Here is what I have as a method in my models.py:
def add_points(request, Comment):
if Comment.post_save():
request.user.get_profile().points += 2
request.user.get_profile().save()
From the examples of post_save I've found, this is far from what is shown - so I think I am way off the mark.
Thank you for your help.
Unfortunately this makes no sense at all.
Firstly, this can't be a method, as it doesn't have self as the first parameter.
Secondly, it seems to be taking the class, not an instance. You can't save the class itself, only an instance of it.
Thirdly, post_save is not a method of the model (unless you've defined one yourself). It's a signal, and you don't call a signal, you attach a signal handler to it and do logic there. You can't return data from a signal to a method, either.
And finally, the profile instance that you add 2 to will not necessarily be the same as the one you save in the second line, because Django model instances don't have identity. Get it once and put it into a variable, then save that.
The Comments framework defines its own signals that you can use instead of the generic post_save. So, what you actually need is to register a signal handler on comment_was_posted. Inside that handler, you'll need to get the user's profile, and update that.
def comment_handler(sender, comment, request, **kwargs):
profile = request.user.get_profile()
profile.points += 2
profile.save()
from django.contrib.comments.signals import comment_was_posted
comment_was_posted.connect(comment_handler, sender=Comment)

Categories

Resources