Валидация динамических форм - Django
У меня есть страница с 3 формами, формы по сути одинаковы в плане полей, но имеют разную валидацию, я хотел спросить, есть ли способ вместо нескольких форм иметь одну форму с динамической валидацией, а затем передать ключевое слово форме для использования одной валидации или другой.
Вот мой forms.py
:
class FileTypeOneForm(forms.Form):
statement = forms.FileField(label='')
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# Check statement_file format and raise ValidationError in case of invalid format
# ...
return statement_file
class FileTypeTwoForm(forms.Form):
statement = forms.FileField(label='')
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# Check statement_file format and raise ValidationError in case of invalid format ** required format is different than FileTypeOneForm and FileTypeThreeForm required format **
# ...
return statement_file
class FileTypeThreeForm(forms.Form):
Options = [
('', 'Select'),
('one', 'One'),
('two', 'Two')
]
option = forms.ChoiceField(label='', choices=Options)
statement = forms.FileField(label='')
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# Check statement_file format and raise ValidationError in case of invalid format ** required format is different than FileTypeOneForm and FileTypeTwoForm required format **
# ...
return statement_file
Все три формы имеют одинаковое поле statement_file
, но с разной валидацией, а третья форма имеет второе поле option
.
В настоящее время это views.py
:
form_id = request.POST.get('form_id')
if form_id == 'one':
form_one = FileTypeOneForm(request.POST, request.FILES)
# ... Process form data ...
# Restart all the other forms
form_two, form_three = FileTypeTwoForm(), FileTypeThreeForm()
elif form_id == 'two':
form_two = FileTypeTwoForm(request.POST, request.FILES)
# ... Process form data ...
# Restart all the other forms
form_one, form_three = FileTypeOneForm(), FileTypeThreeForm()
elif form_id == 'three':
form_three = FileTypeThreeForm(request.POST, request.FILES)
# ... Process form data ...
# Restart all the other forms
form_one, form_two = FileTypeOneForm(), FileTypeTwoForm()
Я бы хотел сделать это чище, имея одну форму с динамической валидацией, и возможно опциональное поле option
для третьей формы, есть предложения, как это сделать?
Я не уверен, что вы найдете это полезным, поскольку я не полностью понял ваши требования. Но я могу поделиться простым шаблоном, который я недавно использовал.
Мы можем использовать "контекст" в наших представлениях. Из документации:
словарь, отображающий имена переменных на значения переменных... передается в Template.render() для рендеринга шаблона.
Src: Django: Шаблоны > Контекст
Это похоже на то, что вы ищете ("передать ключевое слово в форму").
Задача: Мне нужна была форма, поддерживаемая в одном HTML-шаблоне. В зависимости от того, добавляет или редактирует пользователь запись в модели, я хотел настроить сообщение в форме.
В моем случае я передаю простую строку для 'add' и 'edit'. Затем они доступны для использования в шаблонах HTML, где вы можете использовать их как часть логики. У меня есть 2x пути URL и 2x функции View, но оба эти представления указывают на один HTML-шаблон для формы. Основываясь на значении контекста, я применяю различную логику внутри HTML-шаблона.
В urls.py
(заявка):
urlpatterns=[
path('add_to_form/', views.add_to_form, name='add_to_form'),
path('edit_to_form/', views.edit_to_form, name='edit_to_form')
]
В views.py
:
def add_to_form(request):
[...]
context={'someModelForm': someModelForm, 'custom_flag': 'add'}
return render(request, 'my_form.html', context)
def edit_to_form(request):
[...]
context={'someModelForm': someModelForm, 'custom_flag': 'edit'}
return render(request, 'my_form.html', context)
В html шаблоне для 'my_form':
{% if 'add' in custom_flag %}
[Do whatever]
{% endif %}
{% if 'edit' in custom_flag %}
[Do something else]
{% endif %}
Вариант 1
Моим первым предложением было бы делегировать инициализацию формы фабрике. Что-то вроде следующего
def FileTypeFormFactory(form_id: str, *args, *kwargs):
forms = {
'one': FileTypeOneForm,
'two': FileTypeOneForm,
'three': FileTypeOneForm,
}
return forms[form_id](*args, **kwargs)
Вместо всех этих операторов if, вы можете просто использовать
form_id = request.POST.get('form_id')
form = FileTypeFormFactory(form_id, request.POST, request.FILES)
Тогда, для самих форм, я предлагаю вам использовать простое наследование с чем-то вроде BaseFileTypeForm
. Каждый дочерний класс может иметь собственный метод clean_statement
и дополнительные поля, если вы хотите.
class BaseFileTypeForm(fors.Form):
statement = forms.FileField(label='')
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# maybe some default behavior here
return statement_file
class FileTypeOneForm(BaseFileTypeForm):
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# something specific to type one
return statement_file
class FileTypeOneForm(BaseFileTypeForm):
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# something specific to type two
return statement_file
class FileTypeOneForm(BaseFileTypeForm):
Options = [
('', 'Select'),
('one', 'One'),
('two', 'Two')
]
option = forms.ChoiceField(label='', choices=Options)
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
# something specific to type three
return statement_file
Даже если это почти столько же строк кода, как и ваш текущий код, я нахожу его более чистым.
Вариант 2
Другим вариантом может быть одна форма, но с большим количеством операторов if
для обработки различной логики. Этот вариант более лаконичен, но может стать очень запутанным, если в будущем потребуется добавить больше логики для каждого типа файлов
class FileTypeForm(BaseFileTypeForm):
Options = [
('', 'Select'),
('one', 'One'),
('two', 'Two')
]
option = forms.ChoiceField(label='', choices=Options, disabled=True)
statement = forms.FileField(label='')
def __ini__(self, form_id, *args, **kwargs):
self.form_id = form_id
if form_id == 'three':
self.fields['option'].disabled = False
def clean_statement(self):
statement_file = self.cleaned_data['statement_file']
if self.form_id == 'one':
# something specific to type one
...
elif self.form_id == 'two':
# something specific to type two
...
elif self.form_id == 'three':
# something specific to type three
...
return statement_file
Вместо того, чтобы иметь 3 отдельных класса Form, создайте один класс Form и используйте метод clean()
для проверки типов файлов на стороне бэкенда. Добавьте ValidationError, если типы файлов совпадают с другими полями файла.
class FileForm(forms.Form):
Options = [
("", "Select"),
("one", "One"),
("two", "Two"),
]
statement_file_1 = forms.FileField(label="File 1")
statement_file_2 = forms.FileField(label="File 2")
option = forms.ChoiceField(label="Options", choices=Options)
statement_file_3 = forms.FileField(label="File 3")
def clean(self):
cleaned_data = super(FileForm, self).clean()
statement_file_1 = cleaned_data.get('statement_file_1')
statement_file_2 = cleaned_data.get('statement_file_2')
statement_file_3 = cleaned_data.get('statement_file_3')
if statement_file_1.content_type == statement_file_2.content_type:
self.add_error(
"statement_file_1", forms.ValidationError(f"same file type({statement_file_1.content_type}). choose different file")
)
self.add_error(
"statement_file_2", forms.ValidationError(f"same file type({statement_file_2.content_type}). choose different file")
)
if statement_file_2.content_type == statement_file_3.content_type:
self.add_error(
"statement_file_2", forms.ValidationError(f"same file type({statement_file_2.content_type}). choose different file")
)
self.add_error(
"statement_file_3", forms.ValidationError(f"same file type({statement_file_3.content_type}). choose different file")
)
if statement_file_1.content_type == statement_file_3.content_type:
self.add_error(
"statement_file_1", forms.ValidationError(f"same file type({statement_file_1.content_type}). choose different file")
)
self.add_error(
"statement_file_3", forms.ValidationError(f"same file type({statement_file_3.content_type}). choose different file")
)
return cleaned_data
Во фронтенде используйте скрипт для проверки типа файла перед отправкой формы или при изменении файла в поле. что-то вроде следующего (поместите скрипт в файл шаблона или js файл и загрузите его в файл шаблона):
f1 = document.getElementById('id_statement_file_1') // id is auto generated by django using the name in FileForm class
f2 = document.getElementById('id_statement_file_2')
f3 = document.getElementById('id_statement_file_3')
f1.onchange = function() { chooseFile() }
f2.onchange = function() { chooseFile() }
f3.onchange = function() { chooseFile() }
function chooseFile() {
if (f1.files.length > 0 && f2.files.length > 0) {
if (f1.files[0].type == f2.files[0].type) {
alert("statement_file_1 and statement_file_2 are of same file type")
}
}
if (f2.files.length > 0 && f3.files.length > 0) {
if (f2.files[0].type == f3.files[0].type) {
alert("statement_file_2 and statement_file_3 are of same file type")
}
}
if (f1.files.length > 0 && f3.files.length > 0) {
if (f1.files[0].type == f3.files[0].type) {
alert("statement_file_1 and statement_file_3 are of same file type")
}
}
}
во views обрабатывает эти 3 файла, когда форма действительна.
def view_name(request):
if request.method == "POST":
form = FileForm(request.POST, request.FILES)
if form.is_valid():
print(form.cleaned_data)
# file processing code
# 3 files have to processed here
else:
print(form.errors)
else:
form = FileForm()