2

我有一个ModelFormDjango 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_encity_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__方法中定义这些字段,同时将它们从fieldsin 中删除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表单本身的硬编​​码定义中的字段列表中删除。但是,该字段被创建为表单中的最后一个字段,我希望它位于中间。有没有办法在中间添加这个字段?

4

4 回答 4

1

更新 2: ...但是,该字段是作为表单中的最后一个字段创建的,我希望它位于中间。有没有办法在中间添加这个字段?

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

如果field_order是字段名称列表,则字段按照列表指定的顺序排序,其余字段按照默认顺序附加。...

您可以随时使用order_fields()字段名称列表重新排列字段,如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())
于 2019-11-30T17:48:00.640 回答
0

我不知道这是否会有所帮助,但我有一个调查应用程序,不同的用户希望在背景信息表单上提供不同的信息。我通过 json 文件提供它。所以在我的表格内

def __init__(self, *args, **kwargs):
    self.curr_context = kwargs.pop('context', None)
    self.filename = self.get_json_filename()
    super().__init__(*args, **kwargs)
    if os.path.isfile(self.filename) :
        rows = []
        selected_fields = []
        fieldsets = json.load(open(self.filename))
        for fieldset in fieldsets:
            fields = []
            for field in fieldset['fields']:
                if 'field' in field: selected_fields.append(field['field'])
                if 'widget_type' in field:
                    if field['widget_type'] == 'Select': self.fields[field['field']].widget = forms.Select()
                if "choices" in field:
                    choices = []
                    for choice in field['choices']:
                        choices.append((choice['key'],choice['value']))
                    self.fields[field['field']].widget.choices = choices    
                    self.fields[field['field']].choices = choices
                    self.initial[field['field']] = getattr(self.instance, field['field'])
                if 'help' in field:
                    self.fields[field['field']].help_text = field['help']
                if 'html' in field:
                    fields.append(HTML(field['html']))
                if 'divs' in field:
                    fields.append(Field(field['field'], css_class="enabler"))
                    for div in field['divs']:
                        fields.append(Div(Field(div['field'], css_class=div["css"]),*div['div'], css_class="dependent"))
                        selected_fields.append(div['field'])
                        for item in div['div'] : selected_fields.append(item)
                elif 'div' in field:
                    fields.append(Field(field['field'], css_class="enabler"))
                    fields.append(Div(*field['div'], css_class="dependent"))
                    for f in field['div']:
                        selected_fields.append(f)
                else:
                    if 'field' in field: fields.append(field['field'])

            rows.append(Fieldset(fieldset['fieldset'], *fields))

我的 json 文件看起来像:

[{
    "fieldset" : "Basic Information",
    "fields" : [
        {
            "field" : "form_filler",
            "div" : ["form_filler_other"]
        },{
            "field" : "child_dob"
        },{
            "field" : "age"
        },{
            "field" : "sex"
        },{
            "field" : "country", 
            "div" : ["zip_code"]
        },{
            "field" : "birth_order"
        }, {
            "field" : "multi_birth_boolean",
            "div" : ["multi_birth"]
        }, {
            "field" : "birth_weight_kg",
            "choices" : [
                {
                    "key" : "",
                    "value" : "--------"
                },{ 
                    "key" : "1.0",
                    "value" : "Henry 1"
                },{ 
                    "key" : "2.0",
                    "value" : "Weight 2"
                },{ 
                    "key" : "3.0",
                    "value" : "Weight 3"
                },{ 
                    "key" : "4.0",
                    "value" : "Weight 4"
                }
            ],
            "widget_type" :  "Select"
        }, {
            "field" : "born_on_due_date", 
            "div" : ["early_or_late", "due_date_diff"]
        }

    ]
}, { ...
}]

field键是输入字段,列表中的项目也是如此div。In this case when the field is selected (BooleanField) the fields within the divlist must be completed which I do through the clean method.

我的所有字段都在模型中指定,因此您只能选择要使用的字段

于 2019-08-15T15:06:56.093 回答
0

真的很难理解你到底有什么问题。如果您能回答我在下面提出的问题,那将非常有帮助。

我正在将一些字段移动到另一个模型

有哪些领域?来自哪个型号?适合哪个型号?我假设您正在将city字段从User模型移动到SiteProfile.

调用make_migrations导致错误,因为当前模型中不存在这些字段

什么错误?current model指的是什么?SiteProfile? 将字段从一个模型移动到另一个模型应该是完全可行的。

我环顾了您的存储库。尤其是您尝试从中迁移django-modeltranslationdjango-translated-fields. django-translated-fields并且还在Github上的存储库中找到了您的问题。

不幸的是,我无法完全理解您遇到的问题。我认为您的问题可以细分为两个独立的问题。

  1. 迁移不适用于 django-translated-fields
  2. 在表单中动态创建翻译字段。

所以也许我们可以从迁移开始。当您说迁移不起作用时,您是什么意思。你能告诉我错误吗?

于 2019-08-18T07:06:38.303 回答
0

如果您希望在更改模型时动态地在 ModelForm 中的所有模型字段,您可以检查这一点。(我试过了,它有效。)

forms.py中

def admin_list_display(model_name):
    list = [field.name for field in model_name._meta.get_fields()]
    return list

class EpisodeForm(forms.ModelForm):
    class Meta:
        model = Episode
        fields = admin_list_display(Episode)

----- 大警告-----

它将显示不可编辑字段的错误,例如(如果您可以更改功能以排除这些类型的字段,请编辑答案。):

created_at = models.DateTimeField(auto_now_add=True)

django.core.exceptions.FieldError: 'created_at' cannot be specified for Episode model form as it is a non-editable field

如果您从中删除auto_now_add=True,它将起作用。

笔记

它将为所有选项的 ForiegnKeyField 创建下拉列表。

于 2019-11-27T06:30:24.083 回答