Django model form saves m2m after instance - python

I am having an issue with the way Django class-based forms save a form. I am using a form.ModelForm for one of my models which has some many-to-many relationships.
In the model's save method I check the value of some of these relationships to modify other attributes:
class MyModel(models.Model):
def save(self, *args, **kwargs):
if self.m2m_relationship.exists():
self.some_attribute = False
super(MyModel, self).save(*args, **kwargs)
Even if I populated some data in the m2m relationship in my form, I self.m2m_relationship when saving the model and surprisingly it was an empty QuerySet. I eventually found out the following:
The form.save() method is called to save the form, it belongs to the BaseModelForm class. Then this method returns save_instance, a function in forms\models.py. This function defines a local function save_m2m() which saves many-to-many relationships in a form.
Here's the thing, check out the order save_instance chooses when saving and instance and m2m:
instance.save()
save_m2m()
Obviously the issue is here. The instance's save method is called first, that's why self.m2m_relationship was an empty QuerySet. It just doesn't exist yet.
What can I do about it? I can't just change the order in the save_instance function because it is part of Django and I might break something else.

Daniel's answer gives the reason for this behaviour, you won't be able to fix it.
But there is the m2m_changed signal that is sent whenever something changes about the m2m relationship, and maybe you can use that:
from django.db.models import signals
#signals.receiver(signals.m2m_changed, sender=MyModel.m2m_relationship.through)
def handle_m2m_changed(sender, instance, action, **kwargs):
if action == 'post_add':
# Do your check here
But note the docs say that instance "can be an instance of the sender, or of the class the ManyToManyField is related to".
I don't know how that works exactly, but you can try out which you get and then adapt the code.

But it would be impossible to do it any other way.
A many-to-many relationship is not a field on the instance, it is an entry in a linking table. There is no possible way to save that relationship before the instance itself exists, as it won't have an ID to enter into that linking table.

Related

How do I set a specific value for an attribute of a model every time I save it on Django?

I have a legacy project that saves models with save, bulk_create and other methods within the framework.
What is the best way to set a specific value for an attribute so that every time a record is saved the new value is also saved? This value is constructed based on other attributes of the instance that is being saved.
I pose this question because I'm not sure all ways that save is possible in Django except save and bulk_create and knowing that on bulk_create:
The model’s save() method will not be called, and the pre_save and
post_save signals will not be sent.
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#bulk-create
As far as I know, there are 3 ways to create/update model instances (which are records in database tables):
Using the model instance method save().
Using the queryset methods create(), update(), get_or_create(), update_or_create() and bulk_create().
Using raw SQL or other low-level ways.
If you intend to calculate the value of a field when saving, you could override all of the methods I listed above.
Signals (like pre_create) are not a complete solution because they don't get triggered when bulk_create() is used and so some instance could get saved without the calculated attribute.
There is no django way (that I know) to intercept the third point I mentioned (raw SQL).
You did not elaborate on your use case, but (depending on your table size and change frequency) maybe you could also try:
run a periodical process (maybe using crontab) that updates the calculated field of all model instances.
add a database trigger that calculates the field.
Legacy databases or systems or usually not fun to work with, so maybe you will have to settle for a sub-optimal solution.
You can set default value in your model's field using custom functions. For example you have a Post model that also has a field slug. You want default value for slug field to be auto generated from name field. You can write your model like below:
class Post(models.Model):
def generate_slug(self):
return slugify(self.name)
name = models.CharField()
description = models.TextField()
attachment = models.FileField()
slug = models.CharField(default=generate_slug)
This way when you create a new post, the slug field will be auto generated from the name field.
Another way to do that is to create a layer between your caller and the models(database layer) so you can add your logic there. With this you will narrow the possibilities to just the methods you expose in that layer and have control over what should happen everywhere in terms of database talk.
The best way to deal with this issue is to override the save method().
You can use as well raw sql queries , which can easily solve your problems as well
class Model(model.Model):
field1=models.CharField()
field2=models.CharField()
field3=models.CharField()
def myfunc (self):
pass
#
def save(self, *args, **kwargs):
q = MyModel.objects.select_related('fields1', 'field2', 'filed2').filter(related_field)
super(Model, self).save(*args, **kwargs)

Django - attributes and redefined fields with a ModelForm?

I have been learning about how forms, and now ModelForms, work.
In a video by Max Goodridge, he redefines a field for one of his ModelFields in his ModelForm class. That is, he manually adds a field to his ModelForm class that could have been auto-generated by the ModelForm framework. From what I have read and understood thus far, that may be something to avoid. Though, that is not where my question lies.
I am wondering how redefining fields within a ModelForm class works. In the Django Docs, it is stated (with an example) that a ModelForm instance will have a form field for every model field specified. What happens then, when a form field is explicitly defined in a ModelForm instance? Are two fields generated or does ModelForm recognise that a field is already defined, thus not generating another one?
Furthermore, what exactly does adding an attribute to a ModelForm instance in the views do? For example, I have seen this:
form = ExampleForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.user = request.user # herein lies my confusion
post.save()
What exactly is happening here? I have seen people do this and adding a timestamp as well, but I fail to understand exactly what it does. Presumably, the .save() method recognizes the attribute name 'user' and adds it to the database if the name corresponds with a Model-field name 'user'?
What happens when a form field is explicitly defined in a ModelForm instance?
How does adding an attribute with information for a model-field in a ModelForm instance work?
Thank you!
When you define a field at class level, the form will use that definition rather than create one from the model field. Far from being something to avoid, this is the correct thing to do if you want to completely customise a field.
Your second question is hard to understand. Save is not "recognising" anything. form.save() returns the instance of the model, on which you can set any field values as normal.

What's the difference between these two ways to override the save() method in a Django ModelForm?

I've come across two methods of doing this. The accepted answer here suggests:
def save(self, *args, **kwargs):
instance = super(ModelClass, self).save(commit=False)
instance.my_stuff = manual_value
instance.save()
But the following, found here, seems more elegant:
def save(self, *args, **kwargs):
self.my_stuff = manual_value
super(ModelClass, self).save(*args, **kwargs)
Is there any reason to choose one over the other, other than the latter being one less line, such as a reason for running the parent save() first?
The two examples are doing different things. The first is saving the model form's save method, the second is overriding the model's save method.
It only makes sense to override the model's method if you want the value to be set every time the model is saved. If updating the field is related to the form, then overriding the form's save method is the correct thing to do.
In the model form's save method, you have to call save(commit=False) first to get the instance. I wouldn't worry about it being inelegant, it's a very common pattern in Django, and is documented here.
First one will create instance of model, without saving it, then you can add some value (that is required or not) and manually trigger save on that instance.
Second one will save some field on ModelForm (not on actual instance of your model) and then create + save instance of your model.
If in second one you're just setting value on form field that corresponds to model field edited in first example, that will almost work in same way.
Why almost? On second example you have to have that form field inside your form class, on first example you don't. If that field is required, and have been left empty, second example won't validate.
That being said, first example can add or change fields in your model that haven't appeared on form, second example can only modify fields that have been specified inside form class.

Django disable model delete

So every model comes with some commonly used functions such as save and delete.
Delete is often overridden to set a boolean field such as is_active to false, this way data is not lost. But sometimes a model exists that has information that, once created, should always exist and never even be "inactive". I was wondering what the best practice for handling this model's delete method would be?
ideas
make it simply useless:
def delete(self):
return False
but that just seems odd. Is there maybe a Meta option to disable deleting? is there any "nice" way to do this?
Well it depends, you cannot truly restrict deletion, because somebody can always call delete() on queryset or just plain DELETE sql command. If you want to disable delete button in django admin though, you should look here.
delete() on queryset can be restricted with this:
class NoDeleteQuerySet(models.QuerySet):
def delete(self, *args, **kwargs):
pass
class MyModel(models.Model):
objects = NoDeleteQuerySet.as_manager()
...
Django docs - link

Django trigger parent model save when editing inline in admin

I have a model (Parent) with one-to-many relation to another model (Child). The save method of Parent model is overwritten:
class ParentModel(models.Model)
(...)
def save(self, *args, **kwargs):
(...) # Do sth with the model
super(ParentModel, self).save(*args, **kwargs)
class ChildModel(models.Model):
parent= models.ForeignKey(ParentModel)
In admin panel multiple Child models objects are displayed using StackedInline on Parent model's page. If a field of parent is edited and saved, the save method is called. When only child's fields are edited, Django do not call the save method of parent (as expected, because nothing changed).
What is the best way to force saving the parent, even if only child was edited (so that my overwritten method does it's stuff)?
You have a few solutions. Here goes, from simpler to more complex:
You could implement a custom save method for ChildModel that calls ParentModel.save.
You could also connect to your ChildModel's post_save or pre_save signal.
Now, these two solutions will prove annoying if you're going to update a lot of ChildModel instances at once, as you will be calling ParentModel.save several times, maybe without purpose.
You might then want to use the following:
Override your ParentModel's ModelAdmin.change_view to handle your logic; this is pretty tricky however.
I'm however pretty surprised by the behavior your're encountering, from checking the source, the object should be saved anyway; edited or not.

Categories