Требуется ли save_m2m () в формах save () форм Django, когда commit = False? - PullRequest
9 голосов
/ 16 августа 2011

Документы кажутся довольно твердыми, что это действительно так ....

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

И я специально ссылаюсь на этот раздел:

Другой побочный эффект использования commit = False наблюдается, когда ваша модель имеет отношение многие ко многим с другой моделью.Если ваша модель имеет отношение «многие ко многим» и вы указываете commit = False при сохранении формы, Django не может сразу сохранить данные формы для отношения «многие ко многим».Это связано с тем, что невозможно сохранить данные «многие ко многим» для экземпляра до тех пор, пока экземпляр не появится в базе данных.

Чтобы обойти эту проблему, каждый раз, когда вы сохраняете форму, используя commit = False,Django добавляет метод save_m2m () в ваш подкласс ModelForm.После того, как вы вручную сохранили экземпляр, созданный формой, вы можете вызвать save_m2m () для сохранения данных формы многие-ко-многим.

Я довольно новичок в django и наткнулся на эту информациювчера.

Однако у меня есть представление, в котором я не вызываю метод save_m2m (), но на самом деле он сохраняет данные m2m.

Вот мой взгляд:

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)

Моя модель:

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

Моя форма:

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')

Почему мой вид работает,затем?(имеется в виду, что отношение m2m одного из полей формы фактически сохраняется при обработке формы.)

Ответы [ 3 ]

8 голосов
/ 22 июля 2012

Один из родительских классов выполняет полное сохранение модельного объекта и его отношений m2m. Я не могу знать наверняка, потому что у меня нет объявления AuthCreateView, но соглашение об именах указывает, что оно происходит от «CreateView». Если это так, наследование вашего представления выглядит так: SubscriberCreateView -> AuthCreateView -> CreateView -> BaseCreateView -> ModelFormMixin. В ModelFormMixin есть метод form_valid(), который вы (вероятно) вызываете с помощью super().

Вот весь метод из Django 1.4:

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

Так что у вас это есть. Однако позвольте мне указать на некоторую потенциальную путаницу. @ Воган проницателен, когда указывает, что вы не сохраняете свой объект. Как ваш код стоит, вы используете свой несохраненный экземпляр модели для проверки, а затем он отбрасывается , потому что ModelFormMixin переназначает self.object.

  1. Это означает, что self.object может оказаться не тем, что вы ожидаете, если получите к нему доступ позже.
  2. Вы теряете информацию self.object.user. (Поскольку user является обязательным полем в модели, а вы исключаете его из формы, я ожидаю, что save() не удастся. Поэтому родительский элемент AuthCreateView должен что-то делать. Конечно, он может обрабатывать все save() и ни разу не ударил ModelFormMixin.

Чтобы избежать этой путаницы, просто не присваивайте свой экземпляр self.object. Возможно: validate_obj = form.save(commit=False)

0 голосов
/ 19 декабря 2011

Я заметил, что вы на самом деле не сохраняете объект, полученный при вызове form.save(commit=False). Поэтому мне кажется, что ваши данные на самом деле сохраняются где-то еще - возможно, в методе AuthCreateView form_valid (или другом классе предков). Это объясняет, почему объекты «многие ко многим» правильно сохраняются.

0 голосов
/ 16 сентября 2011

Это простой ответ, если мы знаем, как использовать save (commit = False) .

save метод с commit = False не меняет вашу базу данных:

"Если вы вызовите save () с commit = False, то он вернет объект, который еще не был сохранен в базе данных. В этом случае вам решатьвызовите save () для полученного экземпляра модели. Это полезно, если вы хотите выполнить пользовательскую обработку объекта перед его сохранением или если вы хотите использовать одну из специализированных опций сохранения модели. "

Так что вВ случае отношения «многие ко многим» невозможно сохранить данные m2m без сохранения объекта в базе данных.Обычное сохранение (commit = False) может изменить объект при сохранении части данных (а не данных, которые назначаются для отношения m2m).Вы действительно не можете хранить отношения m2m только в памяти.

Если вы хотите работать с этими данными m2m объекта модели после save (commit = False) save_m2m ().Это следует делать, когда вы используете save (commit = False) в противном случае после сохранения вы можете получить промежуточный объект (commit = False), который не соответствует вашей базе данных (или модели базы данных) должным образом.Иногда это может быть нормальным (если вы не касаетесь данных, которые участвуют в обработке части m2m модели).Чтобы восстановить соответствие, вызовите save_m2m в любое время, когда вы вызываете save (commit = False).

Просмотр реализации 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])
...