Is save_m2m () required in the save () method of a Django form when commit = False?

The documents seem pretty solid that this is true.

https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method

And I specifically refer to this section:

Another side effect of using commit = False is when your model has a many-to-many relationship to another model. If your model has a many-to-many relationship and you specify commit = False when you save the form, Django cannot immediately save the form data for the many-to-many relationship. This is because it is not possible to store many-to-many data for an instance until the instance exists in the database.

To work around this problem, every time you save the form with commit = False, Django adds the save_m2m () method to your subclass of ModelForm. After you manually saved the instance created by the form, you can call save_m2m () to save the many-to-many form data.

I am new to django and came across this info yesterday.

However, I have a view where I do not call save_m2m (), but it actually saves m2m data.

Here is my view:

class SubscriberCreateView(AuthCreateView): model = Subscriber template_name = "forms/app.html" form_class = SubscriberForm success_url = "/app/subscribers/" def get_form_kwargs(self): kwargs = super(SubscriberCreateView, self).get_form_kwargs() kwargs.update({'user': self.request.user}) return kwargs def form_valid(self, form): self.object = form.save(commit=False) self.object.user = self.request.user try: self.object.full_clean() except ValidationError: form._errors["email"] = ErrorList([u"This subscriber email is already in your account."]) return super(SubscriberCreateView, self).form_invalid(form) return super(SubscriberCreateView, self).form_valid(form) 

My model:

 class Subscriber(models.Model): STATUS_CHOICES = ( (1, ('Subscribed')), (2, ('Unsubscribed')), (3, ('Marked as Spam')), (4, ('Bounced')), (5, ('Blocked')), (6, ('Disabled')), ) user = models.ForeignKey(User) status = models.IntegerField(('status'), choices=STATUS_CHOICES, default=1) email = models.EmailField() subscriber_list = models.ManyToManyField('SubscriberList') first_name = models.CharField(max_length=70, blank=True) last_name = models.CharField(max_length=70, blank=True) phone = models.CharField(max_length=20, blank=True) facebook_id = models.CharField(max_length=40, blank=True) twitter_id = models.CharField(max_length=40, blank=True) address1 = models.CharField(max_length=100, blank=True) address2 = models.CharField(max_length=100, blank=True) postcode = models.CharField(max_length=10, blank=True) city = models.CharField(max_length=30, blank=True) country = models.CharField(max_length=30, blank=True) date_joined = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) class Meta: unique_together = ( ('user', 'email',), ) def __unicode__(self): return self.email 

My form:

 class SubscriberForm(ModelForm): def __init__(self, user, *args, **kwargs): super (SubscriberForm, self).__init__(*args, **kwargs) self.fields['subscriber_list'].queryset = SubscriberList.objects.filter(user=user) class Meta: model = Subscriber exclude = ('user', 'facebook_id', 'twitter_id') 

Why does my presentation work? (which means that the m2m ratio of one of the fields in the form is actually preserved when processing the form.)

+6
source share
3 answers

One of the parent classes fully preserves the model object and its m2m relationships. I can’t know for sure because I don’t have an AuthCreateView , but the naming convention indicates that it comes from “CreateView”. If so, the inheritance of your view is as follows: SubscriberCreateView -> AuthCreateView -> CreateView -> BaseCreateView -> ModelFormMixin . ModelFormMixin has a form_valid() method that you (possibly) call with super() .

Here is the whole method from Django 1.4:

 def form_valid(self, form): self.object = form.save() return super(ModelFormMixin, self).form_valid(form) 

So you have it. However, let me point out some potential confusion. @Wogan is insightful, indicating that you are not saving your object. As your code stands, you use your unsaved instance of the model for validation, and then it is discarded because ModelFormMixin reassigns self.object .

  • This means that self.object may not be what you expect if you receive it later.
  • You are losing self.object.user information. (Since user is a required field on the Model, and you exclude it in the Form, I would expect save() to fail. Therefore, the parent AuthCreateView must do something. Of course, this can be the processing of all save() and never click ModelFormMixin at all.)

To avoid this confusion, just do not assign your instance of self.object . Perhaps: validate_obj = form.save(commit=False)

+6
source

This is a simple answer if we know how to use save (commit = False).

save method with commit = False does not modify your database:

"If you call save () with commit = False, it will return an object that has not yet been saved in the database. In this case, you need to call save () in the resulting instance of the model. This is useful if you want to perform custom processing of the object before saving it, or if you want to use one of the specialized options for saving the model.

Thus, in the case of many-to-many relationships, it is not possible to save m2m data without saving the object to the database. Regular saving (commit = False) can change the object when saving part of the data (and not the data that is assigned to the m2m relationship). You really cannot store m2m relationships only in memory.

If you want to work with the m2m data of the model object after saving (commit = False), save_m2m () must be saved. You must do this when you use save (commit = False), otherwise you may get an intermediate object after saving (commit = False) that does not match your database (or database model) properly. Sometimes this can be normal (if you do not touch the data that is involved in processing the m2m part of the model). To restore the save_m2m matching call with every save call (commit = False).

Check out save_m2m :

 def save_m2m(): cleaned_data = form.cleaned_data for f in opts.many_to_many: if fields and f.name not in fields: continue if f.name in cleaned_data: f.save_form_data(instance, cleaned_data[f.name]) 
0
source

I noticed that you are not actually saving the object that you get through your call to form.save(commit=False) . Therefore, it seems to me that your data is actually stored somewhere else - perhaps in the AuthCreateView form_valid method (or another ancestor class). This explains why many-to-many objects are stored correctly.

0
source

Source: https://habr.com/ru/post/895179/


All Articles