简体   繁体   English

Django不会在ModelForm上保存ManyToMany字段

[英]Django won't save ManyToMany field on ModelForm

I've got a model similar to the following (this is a condensed version of a long form): 我有一个类似于以下的模型(这是一个长格式的精简版本):

class UserProfile(models.Model):
    display_name = models.CharField()
    nationality = models.ForeignKey(Nationality)
    religion = models.ForeignKey(Religion)

    partner_nationality = models.ManyToManyField(
        Nationality,
        related_name='partner_nationality',
        blank=True)
    partner_religion = models.ManyToManyField(
        Religion,
        related_name='partner_religion',
        blank=True)

And the following model form: 以及以下模型形式:

class UserProfilePartnerPreferencesForm(ModelForm):
    partner_nationality = ModelChoiceField(
        queryset=Nationality.objects.order_by('name'),
        widget=CheckboxSelectMultiple,
        empty_label=None,
        required=False,
        to_field_name='id')

    class Meta:
        model = UserProfile
        fields = [
            'partner_religion',
            'partner_nationality',
        ]
        widgets = {
            'partner_religion': CheckboxSelectMultiple(),
        }

And this generic view: 这个通用视图:

class UserProfilePartnerPreferencesUpdateView(LoginRequiredMixin,
                                              UpdateView):
    model = UserProfile
    form_class = UserProfilePartnerPreferencesForm

    def get(self, request, *args, **kwargs):
        """
        Force the PK of the object to edit to the current user's profile
        """
        self.kwargs[self.pk_url_kwarg] = request.user.profile.id
        return super(UserProfilePartnerPreferencesUpdateView, self).get(
            request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """
        Force the PK of the object to edit to the current user's profile
        """
        self.kwargs[self.pk_url_kwarg] = request.user.profile.id
        return super(UserProfilePartnerPreferencesUpdateView, self).post(
            request, *args, **kwargs)

So what I'm doing in the model form is: 因此,我在模型表单中所做的是:

  • Not displaying some fields (the display_name ) 不显示某些字段( display_name
  • Changing the widget on a field 更改字段上的小部件
  • Creating a custom queryset and changing the widget on a field. 创建自定义查询集并更改字段上的窗口小部件。

This form displays OK but the field where I've passed a custom queryset won't save. 此表单显示“确定”,但是我通过自定义查询集的字段将不会保存。 If I don't give any values for it, django complains with 'NoneType' object is not iterable while trying to save the field ( partner_nationality in this example). 如果我没有提供任何值,django会在尝试保存字段时抱怨'NoneType' object is not iterable (在此示例中为partner_nationality )。 If I give it a valid value, it says that the value isn't valid. 如果我给它一个有效值,则表明该值无效。

So it seems like fields where I supply a custom queryset aren't being applied correctly when the form is saved. 因此,保存表单时,似乎无法正确应用我提供自定义查询集的字段。 If I comment out the customisation (ie partner_nationality in the ModelForm), it saves correctly with the default settings. 如果我注释掉该自定义项(即ModelForm中的partner_nationality ),它将使用默认设置正确保存。

How can I pass a customised queryset and change the widget for a model's ManyToMany field? 如何传递自定义查询集并更改模型的ManyToMany字段的小部件? Also, bonus points if there's a simpler way (something like passing a parameter to the widgets dict (where partner_religion is defined). 另外,如果有更简单的方法(例如,将参数传递给widgets dict(定义了partner_religion ),则可以加分)。

I'm using Django 1.11.1. 我正在使用Django 1.11.1。

Update 更新

Full traceback is as follows when I don't select any options: 当我不选择任何选项时,完全回溯如下:

Environment:


Request Method: POST
Request URL: http://127.0.0.1:8000/user-profile/edit/preferences

Django Version: 1.11.1
Python Version: 3.5.1
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'dating.dating',
 'dating_site']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback:

File "/venv/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)

File "/venv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/venv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/venv/lib/python3.5/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)

File "/venv/lib/python3.5/site-packages/django/contrib/auth/mixins.py" in dispatch
  56.         return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)

File "/venv/lib/python3.5/site-packages/django/contrib/auth/mixins.py" in dispatch
  116.         return super(UserPassesTestMixin, self).dispatch(request, *args, **kwargs)

File "/venv/lib/python3.5/site-packages/django/views/generic/base.py" in dispatch
  88.         return handler(request, *args, **kwargs)

File "/dating/dating/dating/views/user_profile.py" in post
  99.         return super(UserProfilePartnerPreferencesUpdateView, self).post(request, *args, **kwargs)

File "/venv/lib/python3.5/site-packages/django/views/generic/edit.py" in post
  240.         return super(BaseUpdateView, self).post(request, *args, **kwargs)

File "/venv/lib/python3.5/site-packages/django/views/generic/edit.py" in post
  183.             return self.form_valid(form)

File "/venv/lib/python3.5/site-packages/django/views/generic/edit.py" in form_valid
  162.         self.object = form.save()

File "/venv/lib/python3.5/site-packages/django/forms/models.py" in save
  452.             self._save_m2m()

File "/venv/lib/python3.5/site-packages/django/forms/models.py" in _save_m2m
  434.                 f.save_form_data(self.instance, cleaned_data[f.name])

File "/venv/lib/python3.5/site-packages/django/db/models/fields/related.py" in save_form_data
  1686.         getattr(instance, self.attname).set(data)

File "/venv/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py" in set
  982.             objs = tuple(objs)

Exception Type: TypeError at /events/dating/user-profile/edit/preferences
Exception Value: 'NoneType' object is not iterable

In your model, partner_nationality is a ManyToManyField . 在您的模型中, partner_nationalityManyToManyField Therefore you should use a ModelMultipleChoiceField in your form, not a ModelChoiceField . 因此,您应该在表单中使用ModelMultipleChoiceField ,而不是ModelChoiceField

class UserProfilePartnerPreferencesForm(ModelForm):
    partner_nationality = ModelMultipleChoiceField(
        queryset=Nationality.objects.order_by('name'),
        widget=CheckboxSelectMultiple,
    )

If you define the widget in the field definition, you don't have to set it in widgets as well. 如果您在字段定义中定义窗口小部件,则不必在widgets中也进行设置。

Another approach is to change the field's attributes in the __init__ method. 另一种方法是在__init__方法中更改字段的属性。 For example, you can change the queryset with the following: 例如,您可以使用以下命令更改查询集:

class UserProfilePartnerPreferencesForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(UserProfilePartnerPreferencesForm, self).__init__(*args, **kwargs)
        self.fields['partner_nationality'].queryset = Nationality.objects.order_by('name')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM