我有一个ModelForm
Django 2.1,我将一些字段移动到另一个模型。调用make_migrations
会导致错误,因为这些字段在当前模型中不存在。我在表单中添加了一些字段,但其中一个字段是TranslatedField
(来自django-translated-fields),因此目前有 2 个字段,未来可能会更多,具体取决于语言的数量。该字段的名称是 city,目前我收到一条错误消息“ Unknown field(s) (city_en, city_he) specified for SiteProfile
”(因为我使用 2 种语言 - “en”和“he”) - 但我想使用 for 循环动态创建所有字段我们在项目中使用的语言。__new__
我可以覆盖(这是一种很好的编程方法)该方法还是有其他方法?我不喜欢硬编码特定的字段名称(city_en
和city_he
) 因为它们将来可能会改变,这取决于我们使用多少种语言。
你可以在 GitHub 上看到我当前的提交(不工作)。
以及这个分支的当前代码。
__init__
我想知道在保存字段的 ModelForm 中定义动态字段列表的最佳编程方法是什么(它们都是相同的,只会使用其中一个,在方法中删除另一个)另一种型号(有两种型号,但只有一种形式)。
由于运行 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__
方法中定义这些字段,同时将它们从fields
in 中删除class Meta
,但这是一个好方法吗?定义不在列表中的字段fields
?
Django警告不要明确定义字段。
强烈建议您使用 fields 属性显式设置应在表单中编辑的所有字段。如果表单意外允许用户设置某些字段,尤其是在向模型添加新字段时,不这样做很容易导致安全问题。根据表单的呈现方式,问题甚至可能在网页上不可见。
另一种方法是自动包含所有字段,或仅将一些字段列入黑名单。众所周知,这种基本方法的安全性要低得多,并导致主要网站(例如 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
表单本身的硬编码定义中的字段列表中删除。但是,该字段被创建为表单中的最后一个字段,我希望它位于中间。有没有办法在中间添加这个字段?