django - 有没有办法动态定义字段,但不是最后一个字段?

标签 django django-forms django-translated-fields

我有一个带有 Django 2.1 的 ModelForm,我将一些字段移到了另一个模型。调用 make_migrations 会导致错误,因为这些字段在当前模型中不存在。我在表单中添加了一些字段,但其中一个字段是 TranslatedField(来自 django-translated-fields ),因此目前有 2 个字段,将来可能会有更多,具体取决于语言的数量。该字段的名称是城市,目前我收到一条错误消息“Unknown field(s) (city_en, city_he) specified for SiteProfile”(因为我使用的是两种语言 - “en”和“他”) - 但我想通过我们在项目中使用的语言的 for 循环动态创建所有字段。我可以覆盖(这是一种很好的编程方法)__new__ 方法还是有其他方法?我不希望对特定字段名称(city_encity_he)进行硬编码,因为它们将来可能会发生变化,具体取决于我们使用的语言数量。

可以看到我现在的commit (不工作)在 GitHub 上。

和当前code这个分支。

我想知道定义动态字段列表的最佳编程方法是什么(它们都是相同的,并且只会使用其中一个,其他在 __init__ 中删除方法)在 ModelForm 中,其中字段保存在另一个模型中(有 2 个模型,但只有一种形式)。

由于运行 make_migrations 时出现此错误,我仍然没有提交迁移。

(我定义了一个命令make_migrations,它只执行makemigrations)

表单(我试图覆盖 __new__):

class SpeedyMatchProfileBaseForm(DeleteUnneededFieldsMixin, forms.ModelForm):
    user_fields = (
        'diet',
        'smoking_status',
        'marital_status',
        *(to_attribute(name='city', language_code=language_code) for language_code, language_name in django_settings.LANGUAGES),
    )
    validators = {
        'height': [speedy_match_accounts_validators.validate_height],
        'diet': [speedy_match_accounts_validators.validate_diet],
        'smoking_status': [speedy_match_accounts_validators.validate_smoking_status],
        'marital_status': [speedy_match_accounts_validators.validate_marital_status],
        **{to_attribute(name='profile_description', language_code=language_code): [speedy_match_accounts_validators.validate_profile_description] for language_code, language_name in django_settings.LANGUAGES},
        **{to_attribute(name='city', language_code=language_code): [speedy_match_accounts_validators.validate_city] for language_code, language_name in django_settings.LANGUAGES},
        **{to_attribute(name='children', language_code=language_code): [speedy_match_accounts_validators.validate_children] for language_code, language_name in django_settings.LANGUAGES},
        **{to_attribute(name='more_children', language_code=language_code): [speedy_match_accounts_validators.validate_more_children] for language_code, language_name in django_settings.LANGUAGES},
        **{to_attribute(name='match_description', language_code=language_code): [speedy_match_accounts_validators.validate_match_description] for language_code, language_name in django_settings.LANGUAGES},
        'gender_to_match': [speedy_match_accounts_validators.validate_gender_to_match],
        'min_age_match': [speedy_match_accounts_validators.validate_min_age_match],
        'max_age_match': [speedy_match_accounts_validators.validate_max_age_match],
        'diet_match': [speedy_match_accounts_validators.validate_diet_match],
        'smoking_status_match': [speedy_match_accounts_validators.validate_smoking_status_match],
        'marital_status_match': [speedy_match_accounts_validators.validate_marital_status_match],
    }
    # ~~~~ TODO: diet choices depend on the current user's gender. Also same for smoking status and marital status.
    diet = forms.ChoiceField(choices=User.DIET_VALID_CHOICES, widget=forms.RadioSelect(), label=_('My diet'))
    smoking_status = forms.ChoiceField(choices=User.SMOKING_STATUS_VALID_CHOICES, widget=forms.RadioSelect(), label=_('My smoking status'))
    marital_status = forms.ChoiceField(choices=User.MARITAL_STATUS_VALID_CHOICES, widget=forms.RadioSelect(), label=_('My marital status'))
    photo = forms.ImageField(required=False, widget=CustomPhotoWidget, label=_('Add profile picture'))

    class Meta:
        model = SpeedyMatchSiteProfile
        fields = (
            'photo',
            *(to_attribute(name='profile_description', language_code=language_code) for language_code, language_name in django_settings.LANGUAGES),
            *(to_attribute(name='city', language_code=language_code) for language_code, language_name in django_settings.LANGUAGES),
            'height',
            *(to_attribute(name='children', language_code=language_code) for language_code, language_name in django_settings.LANGUAGES),
            *(to_attribute(name='more_children', language_code=language_code) for language_code, language_name in django_settings.LANGUAGES),
            'diet',
            'smoking_status',
            'marital_status',
            'gender_to_match',
            *(to_attribute(name='match_description', language_code=language_code) for language_code, language_name in django_settings.LANGUAGES),
            'min_age_match',
            'max_age_match',
            'diet_match',
            'smoking_status_match',
            'marital_status_match',
        )
        widgets = {
            'smoking_status': forms.RadioSelect(),
            'marital_status': forms.RadioSelect(),
            **{to_attribute(name='profile_description', language_code=language_code): forms.Textarea(attrs={'rows': 3, 'cols': 25}) for language_code, language_name in django_settings.LANGUAGES},
            **{to_attribute(name='city', language_code=language_code): forms.TextInput() for language_code, language_name in django_settings.LANGUAGES},
            **{to_attribute(name='children', language_code=language_code): forms.TextInput() for language_code, language_name in django_settings.LANGUAGES},
            **{to_attribute(name='more_children', language_code=language_code): forms.TextInput() for language_code, language_name in django_settings.LANGUAGES},
            **{to_attribute(name='match_description', language_code=language_code): forms.Textarea(attrs={'rows': 3, 'cols': 25}) for language_code, language_name in django_settings.LANGUAGES},
            'diet_match': CustomJsonWidget(choices=User.DIET_VALID_CHOICES),
            'smoking_status_match': CustomJsonWidget(choices=User.SMOKING_STATUS_VALID_CHOICES),
            'marital_status_match': CustomJsonWidget(choices=User.MARITAL_STATUS_VALID_CHOICES),
        }

    @staticmethod
    def __new__(cls, *args, **kwargs):
        for language_code, language_name in django_settings.LANGUAGES:
            setattr(cls, to_attribute(name='city', language_code=language_code), forms.CharField(label=_('city or locality'), max_length=120))
        return super().__new__(*args, **kwargs)

    def __init__(self, *args, **kwargs):
        self.step = kwargs.pop('step', None)
        super().__init__(*args, **kwargs)
        self.delete_unneeded_fields()
        if ('gender_to_match' in self.fields):
            self.fields['gender_to_match'] = forms.MultipleChoiceField(choices=User.GENDER_CHOICES, widget=forms.CheckboxSelectMultiple)
        if ('photo' in self.fields):
            self.fields['photo'].widget.attrs['user'] = self.instance.user
        if ('diet' in self.fields):
            update_form_field_choices(field=self.fields['diet'], choices=self.instance.user.get_diet_choices())
            self.fields['diet'].initial = self.instance.user.diet
        if ('smoking_status' in self.fields):
            update_form_field_choices(field=self.fields['smoking_status'], choices=self.instance.user.get_smoking_status_choices())
            self.fields['smoking_status'].initial = self.instance.user.smoking_status
        if ('marital_status' in self.fields):
            update_form_field_choices(field=self.fields['marital_status'], choices=self.instance.user.get_marital_status_choices())
            self.fields['marital_status'].initial = self.instance.user.marital_status
        if ('diet_match' in self.fields):
            update_form_field_choices(field=self.fields['diet_match'], choices=self.instance.get_diet_match_choices())
        if ('smoking_status_match' in self.fields):
            update_form_field_choices(field=self.fields['smoking_status_match'], choices=self.instance.get_smoking_status_match_choices())
        if ('marital_status_match' in self.fields):
            update_form_field_choices(field=self.fields['marital_status_match'], choices=self.instance.get_marital_status_match_choices())
        for field_name, field in self.fields.items():
            if (field_name in self.validators):
                field.validators.extend(self.validators[field_name])
                field.required = True

更新 1:我正在考虑在 __init__ 方法中定义这些字段,同时将它们从 class 的 fields 中删除Meta,但这是一个好方法吗?要定义不在 fields 列表中的字段?

Django warns反对未明确定义字段。

It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute. Failure to do so can easily lead to security problems when a form unexpectedly allows a user to set certain fields, especially when new fields are added to a model. Depending on how the form is rendered, the problem may not even be visible on the web page.

The alternative approach would be to include all fields automatically, or blacklist only some. This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub).

我想知道是否有不对语言进行硬编码的解决方案。目前我对语言进行了硬编码:

_city = forms.CharField(label=_('City or locality'), max_length=120, error_messages={'required': _("Please write where you live.")})
city_en = _city
city_he = _city

https://github.com/speedy-net/speedy-net/blob/staging/speedy/match/accounts/forms.py#L64-L66

更新 2: 我发现我可以通过在表单的 __init__ 方法中添加此行来动态添加此字段:

# Create the localized city field dynamically.
self.fields[to_attribute(name='city')] = forms.CharField(label=_('City or locality'), max_length=120, error_messages={'required': _("Please write where you live.")})

然后将其从 class Meta 中的字段列表和表单本身的硬编​​码定义中删除。但是,该字段是作为表单中的最后一个字段创建的,我希望它位于中间。有没有办法在中间添加这个字段?

最佳答案

Update 2: ... But, the field is created as the last field in the form and I want it to be in the middle. Is there a way to add this field in the middle?

来自 https://docs.djangoproject.com/en/2.1/ref/forms/api/#notes-on-field-ordering :

If field_order is a list of field names, the fields are ordered as specified by the list and remaining fields are appended according to the default order. ...

You may rearrange the fields any time using order_fields() with a list of field names as in field_order.

class SpeedyMatchProfileBaseForm(DeleteUnneededFieldsMixin, forms.ModelForm):
    ...

    def __init__(self, *args, **kwargs):
        ...

        # Create the localized city field dynamically.
        self.fields[to_attribute('city')] = forms.CharField(label=_('City or locality'), max_length=120, error_messages={'required': _("Please write where you live.")})

        # Rearrange the fields.
        # self.order_fields((
        #     'photo',
        #     to_attribute('profile_description'),
        #     to_attribute('city')),
        #     # Remaining fields are appended according to the default order.
        # )
        self.order_fields(field_order=self.get_fields())

关于django - 有没有办法动态定义字段,但不是最后一个字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56973508/

相关文章:

python - 无法安装 Django - 无法获取索引基 URL https ://pypi. python.org/simple/

python - DJANGO - 赋值前引用的局部变量 'form'

python - 在注册中验证电子邮件不会在 Django 中引发validationError?

python - Django:未找到字段?

mysql - Django DateTimeField 值在线程结束后丢失

python - Django 更新数据库中的对象

python - 如何在 session 过期后自动触发 session 删除?