Django - How to trigger an action when a specific model is saved? - django

I have two models. One called MainModel and other called HistoricMainModel. I would like to INSERT automatically a row in the HistoricMainModel every time I data is inserted to MainModel. What is best/correct way of doing this in Django?
Best Regards,

If you already have some custom save().-magic going on I would recommend using a post_save() signal or a pre_save() which ever would work best for you.
in your models.py
#receiver(pre_save, sender=MainModel)
def save_a_historicmodel(sender, **kwargs):
#do your save historicmodel logic here
or
def save_a_historicmodel(sender, instance, created, **kwargs):
print "Post save was triggered! Instance:", instance
signals.post_save.connect(save_a_historicmodel, sender=MainModel)
This works so that every time your MainModel is saved this signal is triggered.
Docs here

Rewrite save method in MainModel model to create and insert the HistoricMainModel object before calling the "real" save method.

You could implement this yourself but the best way would be to use an application that does this - like reversion - a django app that implements model history/roll back automatically.
In addition to whatever you will implement, reversion will provide you with:
Integration with the admin backend.
A API to add meta-data to your versions and to revert back to previous versions of your model.
It is a lot more flexible and is widely used and implemented.

Related

Django Rest Framework call custom function when model is created

Suppose I want to do some extra stuff whenever a model is created in my Django App:
models.py:
class MyModel(models.Model):
name = models.CharField(max_length=255)
def do_some_extra_stuff(self):
// whatever...
return "I did some extra stuff"
Suppose the extra stuff is some long-running asynchronous task.
I am using Django Rest Framework and have a standard ModelSerializer and ViewSet.
My question is where is the best place to put the call to do_some_extra_stuff ? I can think of the following options:
In the serializer.create method
In the viewset.create method
In the model.save method
In a post_save signal
I am leaning towards #1 or #2 b/c I only want this fn to be called when normal users interact w/ the API - not when using the Django Admin or the shell.
This question will probably be closed because its opinion based, so here are my opinions on it!
Background Tasks
Use Celery or Django-RQ.
Celery is "better", but Django-RQ (and RQ itself) is almost always enough if you have good logging and error reporting. These aren't usually bank account transfers we're dealing with.
#2 - Viewset Based
I believe serializers should only serialize & validate. Because of this, any custom save logic needs to go in the ViewSet - up front, visible at the point use, and has access to everything without any serializer context tricks.
class MyViewSet(ModelViewSet):
def perform_create(self, serializer):
# pass a value into save(), which passes it to model.create()
serializer.save(org_id=self.request.user.secret_org_id)
# use the serializer instance that was created to enqueue a bg task
my_background_task.delay(item_pk=serializer.instance.pk)
This is just a preference. Its perfectly fine to put it in the serializer if that is how you normally do other tasks. The only goal is consistency.
This may make more sense if you imagine using 1 serializer for each action - list, create, and update.
Other Methods
#3 In the model.save method
The model.save() is not called in some rare cases, and its not the place I expect to see logic that runs other tasks. Some model field cleanups work there. Again this is a preference, but using this method with DRF seems unusual, as opposed to when you are in pure django and using the admin site.
#4 In a post_save signal
I feel post-save signals are too far away from the model, and also not called in some rare cases (same mostly as #3). I would rather be explicit with things like this, so I keep it close to where I expect to see it happen. We use post_save signals for other things, but they do suffer from not being able to see the context, and its difficult to pass additional parameters into them.

See if Django form submission came from the admin

I'm new to django, so I apologize if this has been asked. I'm using the post_save signal to run a task when a new object is created. I need to be able to check if the form was submitted from the admin page or if it was submitted on the live website, is this possible? Where might I find documentation on this?
post_save is too late in the process to log or take action on the source of the object.
post_save is a signal sent from the database/ORM, i.e. it is what is called after a save is done. Does the save function take any input about the source? No. Does that function put anything into the ORM or the database about the source? No.
You want to take whatever action it is in the view function where this occurs. Here is the simplest way to do it that I can think of. I shall assume that your preferred choice of action is to save where it was created in the database.
Consider the following:
class YourObject(models.Model):
name = models.CharField(max_length=30)
creation_location = models.CharField(max_length=30, default="Admin")
Here we have an object with a name and a creation_location, the purpose of the second being to tell where the object is created. To prevent any need to edit the Admin functionality, as that can be a pain, the default value is set to Admin.
Onto the view:
def create_model_view(request):
your_object = YourObject.objects.create(name='FirstObject', creation_location="View")
Here we have a view with a create function for the object. In the initialization, the default value of Admin for the creation_location is overwritten and set to View.

To use signals or override model save method?

Simple use case:
After a user updates a record, I want to get the changed fields and save them in a history table. I'm using django-ditryfields to grab this history. So my thought process was to use the pre_save signal to grab all the 'dirty' fields and them store them in my history table.
Problem there is that I can't get request.user while using signals. I need this to see which user has made the change to the record. My other thought was just to override the save method of my model but then I also can't get request.user from a model directly either. I would have to send a **kwarg['user'] with the user info from the view to get this info. This is fine but I am going to be making save calls from a bunch of different places around the code. I don't want to have to keep passing request.user every time I edit an object. This is why I'd love to have one spot, like a signal, to handle all of this. Perhaps some middleware I'm not familiar with?
Is there a better way to achieve such a thing?
You cannot access the user object from a signal.
You can consider using this third party package: django-requestprovider to access the request object in the signal.
The other way would be to overriding the models' save method.

MVC2 - Are There Signals for Models?

I'm just starting on my first ASP.NET MVC project (it's actually extending an existing MVC project that uses 4.0 and Linq2SQL). What I'm looking for is a way to create an instance of a model every time a different model is created (i.e., saved to the database). I see an OnCreated() method in the generated code, but that's in a partial class, so I can't replace it/ override it.
Is there a way to tie things together like this? I feel like I've been working in Django so long (where I would use a signal) that I don't know the right term to search for.
EDIT: What I want to do -- every time an instance of Model A is saved to the database for the first time (and only the first time), I want to create an instance of Model B and save that to the database.
And it looks like OnCreated() doesn't have anything to do with that.
You can always override the save method.
class A(models.Model)
def save(self,**kwargs):
super(A,self).save(**kwargs)
if self.pk: #Primary Key is assigned only after the save.
B.objects.create()
return self

Django: How to dynamically add tag field to third party apps without touching app's source code

Scenario: large project with many third party apps. Want to add tagging to those apps without having to modify the apps' source.
My first thought was to first specify a list of models in settings.py (like ['appname.modelname',], and call django-tagging's register function on each of them. The register function adds a TagField and a custom manager to the specified model. The problem with that approach is that the function needs to run BEFORE the DB schema is generated.
I tried running the register function directly in settings.py, but I need django.db.models.get_model to get the actual model reference from only a string, and I can't seem to import that from settings.py - no matter what I try I get an ImportError. The tagging.register function imports OK however.
So I changed tactics and wrote a custom management command in an otherwise empty app. The problem there is that the only signal which hooks into syncdb is post_syncdb which is useless to me since it fires after the DB schema has been generated.
The only other approach I can think of at the moment is to generate and run a 'south' like database schema migration. This seems more like a hack than a solution.
This seems like it should be a pretty common need, but I haven't been able to find a clean solution.
So my question is: Is it possible to dynamically add fields to a model BEFORE the schema is generated, but more specifically, is it possible to add tagging to a third party model without editing it's source.
To clarify, I know it is possible to create and store Tags without having a TagField on the model, but there is a major flaw in that approach in that it is difficult to simultaneously create and tag a new model.
From the docs:
You don't have to register your models
in order to use them with the tagging
application - many of the features
added by registration are just
convenience wrappers around the
tagging API provided by the Tag and
TaggedItem models and their managers,
as documented further below.
Take a look at the API documentation and the examples that follow for how you can add tags to any arbitrary object in the system.
http://api.rst2a.com/1.0/rst2/html?uri=http://django-tagging.googlecode.com/svn/trunk/docs/overview.txt#tags
Updated
#views.py
def tag_model_view(request, model_id):
instance_to_tag = SomeModel.objects.get(pk=model_id)
setattr(instance_to_tag, 'tags_for_instance', request.POST['tags'])
...
instance_to_tag.save()
...returns response
#models.py
#this is the post_save signal receiver
def tagging_post_save_handler(sender, instance, created):
if hasattr(instance, 'tags_for_instance'):
Tag.objects.update_tags(instance, instance.tags_for_instance)