Django Form的save_m2m()与save(commit = False)无效 - 请求模型字段具有值

时间:2016-06-23 18:35:27

标签: django python-2.7 django-views

我使用commit = False来保存表单,因为我希望在保存之前在数据表中包含一些非表单数据(表单的作者)。在Django中设置commit = False时,文档中会提到副作用:

  

当您的模型与另一个模型具有多对多关系时,可以看到使用commit = False的另一个副作用。如果模型具有多对多关系,并且在保存表单时指定commit = False,则Django无法立即保存多对多关系的表单数据。这是因为在实例存在于数据库中之前,无法为实例保存多对多数据。

所以我尝试使用文档here中描述的save_m2m()方法,但无济于事,我收到以下错误。

views.py

class MinutesCreate(LoginRequiredMixin, View):
    @method_decorator(permission_required('pd.add_agenda'))
    def dispatch(self, *args, **kwargs):
        return super(MinutesCreate, self).dispatch(*args, **kwargs)

    def get(self, request, **kwargs):
        minutes_form = MinutesForm()
        return render(request, "pd/minutes_form.html", {"form": minutes_form, "kwargs": kwargs})

    def post(self, request, **kwargs):
        minutes_form = MinutesForm(request.POST)
        if minutes_form.is_valid():
            minutes = minutes_form.save(commit=False)
            minutes.author = request.user
            minutes.save()
            minutes_form.save_m2m()
            return redirect('pd:agenda_list')
        return render(request, 'pd/minutes_form.html', {'form': minutes_form})

forms.py

class MinutesForm(BetterModelForm):

    def __init__(self, *args, **kwargs):
        super(MinutesForm, self).__init__(*args, **kwargs)
        self.fields['participants'].required = False

    participants = UserModelMultipleChoiceField(queryset=UserProfile.objects.all(), widget=forms.CheckboxSelectMultiple())

    class Meta:
        model = Minutes

        fieldsets = (
            ("Minutes", {"fields": ["participants","minutes"]}),
        )

models.py

class Minutes(models.Model):
    agenda = models.OneToOneField(
        Agenda,
        on_delete=models.CASCADE,
        primary_key=True,
    )
    published = models.DateTimeField(verbose_name='Minutes Published', auto_now_add=True)
    edited = models.DateTimeField(verbose_name='Last Modified', auto_now=True)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="minutes_author", db_index=True, blank=True)
    participants = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="participants")
    minutes = models.TextField(blank=True)

栈跟踪

Environment:


Request Method: POST
Request URL: http://localhost:8000/committee/agenda/2016/6/22/minutes/add

Django Version: 1.8
Python Version: 2.7.11
Installed Applications:
('flat',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'pdpauth',
 'pd',
 'bootstrap3',
 'recurrence',
 'mail_templated',
 'django_navtag',
 'debug_toolbar')
Installed Middleware:
(u'debug_toolbar.middleware.DebugToolbarMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'rollbar.contrib.django.middleware.RollbarNotifierMiddleware')


Traceback:
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  132.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  22.                 return view_func(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/views/generic/base.py" in view
  71.             return self.dispatch(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/utils/decorators.py" in _wrapper
  34.             return bound_func(*args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  22.                 return view_func(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/utils/decorators.py" in bound_func
  30.                 return func.__get__(self, type(self))(*args2, **kwargs2)
File "/Users/ryancastner/Code/pdpsite/pd/views.py" in dispatch
  410.         return super(MinutesCreate, self).dispatch(*args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/views/generic/base.py" in dispatch
  89.         return handler(request, *args, **kwargs)
File "/Users/ryancastner/Code/pdpsite/pd/views.py" in post
  422.             minutes_form.save_m2m()
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/forms/models.py" in save_m2m
  102.                 f.save_form_data(instance, cleaned_data[f.name])
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in save_form_data
  2576.         setattr(instance, self.attname, data)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in __set__
  1259.         manager = self.__get__(instance)
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in __get__
  1242.             through=self.field.rel.through,
File "/Users/ryancastner/Code/pdpsite/venv/lib/python2.7/site-packages/django/db/models/fields/related.py" in __init__
  874.                                  (instance, source_field_name))

Exception Type: ValueError at /committee/agenda/2016/6/22/minutes/add
Exception Value: "<Minutes: Minutes object>" needs to have a value for field "minutes" before this many-to-many relationship can be used.

发布数据

请求信息

GET

No GET data

POST

Variable    Value

csrfmiddlewaretoken u'W7tWohIGT1YsMR45pbGhuK5I6VbdIv9m'

minutes                 u'<p>testing this out</p>'

participants            u'17'

使用print语句来证明minutes.minutes具有发布值

[23/Jun/2016 13:08:04]"GET /committee/agenda/2016/6/22/minutes/add HTTP/1.1" 200 25470
minutes object's minutes:
<p>testinga askdlj;slkdje</p>
[23/Jun/2016 13:08:14]"POST /committee/agenda/2016/6/22/minutes/add HTTP/1.1" 500 148995

class MinutesCreate(LoginRequiredMixin, View):
    @method_decorator(permission_required('pd.add_agenda'))
    def dispatch(self, *args, **kwargs):
        return super(MinutesCreate, self).dispatch(*args, **kwargs)

    def get(self, request, **kwargs):
        minutes_form = MinutesForm()
        return render(request, "pd/minutes_form.html", {"form": minutes_form, "kwargs": kwargs})

    def post(self, request, **kwargs):
        minutes_form = MinutesForm(request.POST)
        if minutes_form.is_valid():
            minutes = minutes_form.save(commit=False)
            minutes.author = request.user
            print "minutes object's minutes:\n", minutes.minutes # new print that is shown above
            minutes.save()
            minutes_form.save_m2m()
            return redirect('pd:agenda_list')
        return render(request, 'pd/minutes_form.html', {'form': minutes_form})

1 个答案:

答案 0 :(得分:1)

更新&amp;答案

问题是由于我使用BetterModelForm Django包。 BetterModelForm将表单划分为fieldsets,以允许在同一页面上显示可重复或多个表单。因此,save_m2m()的方法没有在form上传播,而是在formset传播,这需要一种不同的方式来调用save_m2m()

我通过将BetterModelForm类转换为标准forms.ModelForm来解决此问题,并且当fieldsets被删除后,所有内容都按预期工作。

注意**我还在为表单提供UserProfile查询集,我的模型接受了User个对象。我更新了代码,以便为我的表单提供正确的查询集。

更新后的代码如下:

forms.py

class AgendaForm(forms.ModelForm):
    time_fmt = ["%I:%M %p"]
    date_fmt = ["%Y/%m/%d"]

    start = forms.SplitDateTimeField(label="Meeting Start", input_time_formats=time_fmt,
                                     input_date_formats=date_fmt,
                                     widget=forms.widgets.SplitDateTimeWidget(date_format=date_fmt[0], time_format=time_fmt[0]))
    end = forms.SplitDateTimeField(label="Meeting End", input_time_formats=time_fmt,
                                     input_date_formats=date_fmt,
                                     widget=forms.widgets.SplitDateTimeWidget(date_format=date_fmt[0], time_format=time_fmt[0]))

    location = forms.CharField(initial='TBD')

    class Meta:
        model = Agenda
        fields = ["start", "end", "location", "announcements", "agenda"]


class MinutesForm(forms.ModelForm):
    participants = UserModelMultipleChoiceField(queryset=User.objects.all(), widget=forms.CheckboxSelectMultiple())

    class Meta:
        model = Minutes
        fields = ["participants","minutes"]

views.py

class MinutesCreate(LoginRequiredMixin, View):
    @method_decorator(permission_required('pd.add_agenda'))
    def dispatch(self, *args, **kwargs):
        return super(MinutesCreate, self).dispatch(*args, **kwargs)

    def get(self, request, **kwargs):
        minutes_form = MinutesForm()
        return render(request, "pd/minutes_form.html", {"form": minutes_form, "kwargs": kwargs})

    def post(self, request, **kwargs):
        minutes_form = MinutesForm(request.POST)
        if minutes_form.is_valid():
            minutes = minutes_form.save(commit=False)
            minutes.author = request.user
            ag = get_object_or_404(Agenda, start__year=kwargs.get('year'),
                               start__month=kwargs.get('month'),
                               start__day=kwargs.get('day'))
            minutes.agenda = ag
            minutes.save()
            minutes_form.save_m2m()
            return redirect('pd:agenda_detail', **kwargs)
        return render(request, 'pd/minutes_form.html', {'form': minutes_form})