Django Formsets управляє складними повторюваними полями форм у поданні. Використовуючи формуляри, ви можете дізнатися скільки форм було спочатку, які були змінені, а які повинні бути видалені.
Подібно до форм і моделей форм, Django пропонує набори моделей форм, які спрощують завдання створення набору форм для форми, що обробляє кілька екземплярів моделі.
Django також надає вбудовані набори форм, які можна використовувати для обробки набору об'єктів, що належать до загального зовнішнього ключа.
У наведених нижче прикладах моделей ми можемо написати inline-formset для обробки всіх дочірніх елементів, батьків або всіх адрес дочірніх елементів.
# models.py class Parent(models.Model): name = models.CharField(max_length=255) class Child(models.Model): parent = models.ForeignKey(Parent) name = models.CharField(max_length=255) class Address(models.Model): child = models.ForeignKey(Child) country = models.CharField(max_length=255) state = models.CharField(max_length=255) address = models.CharField(max_length=255)
# forms.py from django.forms.models import inlineformset_factory ChildrenFormset = inlineformset_factory(models.Parent, models.Child, extra=1) AddressFormset = inlineformset_factory(models.Child, models.Address, extra=1)
Використовуючи наведені вище набори форм, ви можете обробляти всі дочірні елементи для батьків на одній сторінці та обробляти всі адреси дочірніх елементів на іншій сторінці. Якщо ви хочете дозволити користувачам додавати або редагувати всі дочірні елементи разом з адресами на одній сторінці, то у вас має бути повний набір форм адрес для кожної дочірньої форми в дочірньому наборі форм.
І тут виникає сенс використовувати вкладені форми. Вкладений formset - це звичайний inline-formset.
Для обробки вкладених форм необхідно зробити такі кроки:
Крок 1: Створення базового inline-formset
# forms.py from django.forms.models import BaseInlineFormSet class BaseChildrenFormset(BaseInlineFormSet): pass ChildrenFormset = inlineformset_factory(models.Parent, models.Child, formset=BaseChildrenFormset, extra=1)
Крок 2. Прикріплення вкладеного набір форм кожної форми, як показано нижче. Супер-клас BaseInlineFormSet визначає метод add_fields, який відповідає за додавання полів для кожної форми у наборі форм. Також тут ми можемо написати логіку, щоб зв'язати вкладений набір форм.
# forms.py class BaseChildrenFormset(BaseInlineFormSet): def add_fields(self, form, index): super(BaseChildrenFormset, self).add_fields(form, index) # save the formset in the 'nested' property form.nested = AddressFormset( instance=form.instance, data=form.data if form.is_bound else None, files=form.files if form.is_bound else None, prefix='address-%s-%s' % ( form.prefix, AddressFormset.get_default_prefix()), extra=1)
Примітка: Тут ми створили нову властивість під назвою "form.nested", яка містить вкладений набір форм (AddressFormset).
Крок 3. Обробка набору форм та вкладених наборів форм у уявленнях
# views.py def manage_children(request, parent_id): """Edit children and their addresses for a single parent.""" parent = get_object_or_404(models.Parent, id=parent_id) if request.method == 'POST': formset = forms.ChildrenFormset(request.POST, instance=parent) if formset.is_valid(): formset.save() return redirect('parent_view', parent_id=parent.id) else: formset = forms.ChildrenFormset(instance=parent) return render(request, 'manage_children.html', { 'parent':parent, 'children_formset':formset})
Крок 4: Візуалізація вкладеного набору форм шаблону
# manage_children.html (Just formset display part) {{ children_formset.management_form }} {{ children_formset.non_form_errors }} {% for child_form in children_formset.forms %} {{ child_form }} {% if child_form.nested %} {{ child_form.nested.management_form }} {{ child_form.nested.non_form_errors }} {% for nested_form in child_form.nested.forms %} {{ nested_form }} {% endfor %} {% endif %} {% endfor %}
Є кілька моментів, на які слід звернути увагу:
Перевірка. При перевірці форми у наборі форм нам також необхідно перевірити її підформи, що знаходяться у вкладеному наборі форм.
Збереження даних. При збереженні форми також необхідно зберігати доповнення та зміни до форм у вкладеному наборі форм.
Коли сторінку надіслано, ми викликаємо formset.is_valid() для перевірки форм. Ми перевизначаємо is_valid у нашому наборі форм, щоб додати перевірку для вкладених наборів форм.
# forms.py class BaseChildrenFormset(BaseInlineFormSet): ... def is_valid(self): result = super(BaseChildrenFormset, self).is_valid() if self.is_bound: for form in self.forms: if hasattr(form, 'nested'): result = result and form.nested.is_valid() return result
На цьому перевірка форм та вкладених форм завершується. Тепер нам потрібно опрацювати збережене. Для цього необхідно перевизначити метод save для збереження батьківського набору форм та всіх вкладених наборів форм.
# forms.py class BaseChildrenFormset(BaseInlineFormSet): ... def save(self, commit=True): result = super(BaseChildrenFormset, self).save(commit=commit) for form in self.forms: if hasattr(form, 'nested'): if not self._should_delete_form(form): form.nested.save(commit=commit) return result
Метод save відповідає за збереження форм набору форм, а також всіх форм у вкладеному наборі форм кожної форми.
Докладніше про пакет з відкритим вихідним кодом Django CRM (Керування відносинами з клієнтами) можна за цим посиланням (https://github.com/MicroPyramid/Django-CRM).
Спасибо за полезную статью. Подскажите пожалуйста, что делать если нужно реализовать большее количество вложенных форм?
Например если на модель Address ссылается fk другой модели, на которую в свою очередь ссылается еще одна модель.
И нужно создавать все объекты с одной страницы.
В этом примере и так два уровня вложенности, всё остальное делается подобным способом.