Django ModelForm - Есть ли способ определить поля динамически? (Например, при использовании django-translation-fields)
У меня есть ModelForm
с Django 1.11, и я перемещаю несколько полей в другую модель. призвание make_migrations
вызывает ошибку, потому что эти поля не существуют в текущей модели. Я добавил некоторые поля в форму, но одно из полей TranslatedField
и поэтому в настоящее время есть 2 поля, и в будущем их может быть больше, в зависимости от количества языков. Название поля - город, и в настоящее время я получаю сообщение об ошибке "Unknown field(s) (city_en, city_he) specified for SiteProfile
"(потому что я использую 2 языка - "en"и"he") - но я хочу создать все поля динамически с помощью цикла for для языков, которые мы используем в проекте. Могу ли я переопределить (и это хорошо? метод программирования) __new__
метод или есть другой способ? Я предпочитаю не кодировать конкретные имена полей (city_en
а также city_he
) потому что они могут измениться в будущем, в зависимости от того, сколько языков мы используем.
Вы можете увидеть мой текущий коммит (не работает) на GitHub.
И текущий код этой ветки.
Я хотел бы знать, каков наилучший метод программирования для определения динамического списка полей (все они идентичны, и только одно из них будет использоваться, а другие удаляются в __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
Обновление: я думаю об определении этих полей в __init__
метод при удалении их из fields
в class Meta
, но это хороший подход? Определить поля, которых нет в списке fields
?
Джанго предостерегает от явного определения полей.
Настоятельно рекомендуется явно указать все поля, которые должны редактироваться в форме, используя атрибут 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
2 ответа
Обновление 2:... Но поле создается как последнее поле в форме, и я хочу, чтобы оно было посередине. Есть ли способ добавить это поле посередине?
Из https://docs.djangoproject.com/en/2.1/ref/forms/api/:
Если
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())
Не знаю, поможет ли это, но у меня есть приложение для опроса, в котором разные пользователи хотят получать разную информацию в форме справочной информации. Я предоставляю это через файл 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
список. В этом случае, когда поле выбрано (BooleanField), поля внутри div
Список должен быть завершен, что я делаю с помощью чистого метода.
Все мои поля указаны в модели, так что вы можете только выбрать, какие поля использовать
Действительно сложно понять, с чем именно у тебя проблемы. Было бы очень полезно, если бы вы могли ответить на вопросы, которые я задаю ниже.
Я перемещаю несколько полей в другую модель
Какие поля? От какой модели? К какой модели? Я предполагаю, что вы двигаетесь city
поле из User
модель для SiteProfile
,
призвание
make_migrations
вызывает ошибку, потому что эти поля не существуют в текущей модели
Какая ошибка? Что такое current model
ссылаясь на? SiteProfile
? Это должно быть вполне выполнимо для перемещения поля от одной модели к другой.
Я посмотрел вокруг вашего хранилища. Особенно ветки, из которых вы пытались мигрировать django-modeltranslation
в django-translated-fields
, А также нашел вашу проблему на django-translated-fields
хранилище на Github.
К сожалению, я не могу полностью понять, в чем проблема у вас. Я думаю, что вашу проблему можно разделить на две независимые.
- Миграция не работает для django-translation-fields
- Динамическое создание переведенных полей в формах.
Так что, возможно, мы можем начать с миграции. Что вы имеете в виду, когда говорите, что миграция не работает? Можете ли вы показать мне ошибки?
Если вы хотите, чтобы все поля модели в 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 всех параметров.