Lila25mila
25 січня 2019 р. 14:28

Як використовувати вкладені форми в Django

Django Formsets управляє складними повторюваними полями форм у поданні. Використовуючи формуляри, ви можете дізнатися скільки форм було спочатку, які були змінені, а які повинні бути видалені.
Подібно до форм і моделей форм, Django пропонує набори моделей форм, які спрощують завдання створення набору форм для форми, що обробляє кілька екземплярів моделі.


Django також надає вбудовані набори форм, які можна використовувати для обробки набору об'єктів, що належать до загального зовнішнього ключа.
У наведених нижче прикладах моделей ми можемо написати inline-formset для обробки всіх дочірніх елементів, батьків або всіх адрес дочірніх елементів.

  1. # models.py
  2.  
  3. class Parent(models.Model):
  4. name = models.CharField(max_length=255)
  5.  
  6. class Child(models.Model):
  7. parent = models.ForeignKey(Parent)
  8. name = models.CharField(max_length=255)
  9.  
  10. class Address(models.Model):
  11. child = models.ForeignKey(Child)
  12. country = models.CharField(max_length=255)
  13. state = models.CharField(max_length=255)
  14. address = models.CharField(max_length=255)
  1. # forms.py
  2.  
  3. from django.forms.models import inlineformset_factory
  4.  
  5. ChildrenFormset = inlineformset_factory(models.Parent, models.Child, extra=1)
  6. AddressFormset = inlineformset_factory(models.Child, models.Address, extra=1)

Використовуючи наведені вище набори форм, ви можете обробляти всі дочірні елементи для батьків на одній сторінці та обробляти всі адреси дочірніх елементів на іншій сторінці. Якщо ви хочете дозволити користувачам додавати або редагувати всі дочірні елементи разом з адресами на одній сторінці, то у вас має бути повний набір форм адрес для кожної дочірньої форми в дочірньому наборі форм.
І тут виникає сенс використовувати вкладені форми. Вкладений formset - це звичайний inline-formset.
Для обробки вкладених форм необхідно зробити такі кроки:

Крок 1: Створення базового inline-formset

  1. # forms.py
  2.  
  3. from django.forms.models import BaseInlineFormSet
  4.  
  5. class BaseChildrenFormset(BaseInlineFormSet):
  6. pass
  7.  
  8. ChildrenFormset = inlineformset_factory(models.Parent,
  9. models.Child,
  10. formset=BaseChildrenFormset,
  11. extra=1)

Крок 2. Прикріплення вкладеного набір форм кожної форми, як показано нижче. Супер-клас BaseInlineFormSet визначає метод add_fields, який відповідає за додавання полів для кожної форми у наборі форм. Також тут ми можемо написати логіку, щоб зв'язати вкладений набір форм.

  1. # forms.py
  2. class BaseChildrenFormset(BaseInlineFormSet):
  3.  
  4. def add_fields(self, form, index):
  5. super(BaseChildrenFormset, self).add_fields(form, index)
  6.  
  7. # save the formset in the 'nested' property
  8. form.nested = AddressFormset(
  9. instance=form.instance,
  10. data=form.data if form.is_bound else None,
  11. files=form.files if form.is_bound else None,
  12. prefix='address-%s-%s' % (
  13. form.prefix,
  14. AddressFormset.get_default_prefix()),
  15. extra=1)

Примітка: Тут ми створили нову властивість під назвою "form.nested", яка містить вкладений набір форм (AddressFormset).

Крок 3. Обробка набору форм та вкладених наборів форм у уявленнях

  1. # views.py
  2.  
  3. def manage_children(request, parent_id):
  4. """Edit children and their addresses for a single parent."""
  5.  
  6. parent = get_object_or_404(models.Parent, id=parent_id)
  7.  
  8. if request.method == 'POST':
  9. formset = forms.ChildrenFormset(request.POST, instance=parent)
  10. if formset.is_valid():
  11. formset.save()
  12. return redirect('parent_view', parent_id=parent.id)
  13. else:
  14. formset = forms.ChildrenFormset(instance=parent)
  15.  
  16. return render(request, 'manage_children.html', {
  17. 'parent':parent,
  18. 'children_formset':formset})

Крок 4: Візуалізація вкладеного набору форм шаблону

  1. # manage_children.html (Just formset display part)
  2.  
  3. {{ children_formset.management_form }}
  4. {{ children_formset.non_form_errors }}
  5.  
  6. {% for child_form in children_formset.forms %}
  7. {{ child_form }}
  8.  
  9. {% if child_form.nested %}
  10. {{ child_form.nested.management_form }}
  11. {{ child_form.nested.non_form_errors }}
  12.  
  13. {% for nested_form in child_form.nested.forms %}
  14. {{ nested_form }}
  15. {% endfor %}
  16.  
  17. {% endif %}
  18.  
  19. {% endfor %}

Є кілька моментів, на які слід звернути увагу:

  1. Перевірка. При перевірці форми у наборі форм нам також необхідно перевірити її підформи, що знаходяться у вкладеному наборі форм.

  2. Збереження даних. При збереженні форми також необхідно зберігати доповнення та зміни до форм у вкладеному наборі форм.

Коли сторінку надіслано, ми викликаємо formset.is_valid() для перевірки форм. Ми перевизначаємо is_valid у нашому наборі форм, щоб додати перевірку для вкладених наборів форм.

  1. # forms.py
  2.  
  3. class BaseChildrenFormset(BaseInlineFormSet):
  4. ...
  5.  
  6. def is_valid(self):
  7. result = super(BaseChildrenFormset, self).is_valid()
  8.  
  9. if self.is_bound:
  10. for form in self.forms:
  11. if hasattr(form, 'nested'):
  12. result = result and form.nested.is_valid()
  13.  
  14. return result

На цьому перевірка форм та вкладених форм завершується. Тепер нам потрібно опрацювати збережене. Для цього необхідно перевизначити метод save для збереження батьківського набору форм та всіх вкладених наборів форм.

  1. # forms.py
  2.  
  3. class BaseChildrenFormset(BaseInlineFormSet):
  4. ...
  5.  
  6. def save(self, commit=True):
  7.  
  8. result = super(BaseChildrenFormset, self).save(commit=commit)
  9.  
  10. for form in self.forms:
  11. if hasattr(form, 'nested'):
  12. if not self._should_delete_form(form):
  13. form.nested.save(commit=commit)
  14.  
  15. return result

Метод save відповідає за збереження форм набору форм, а також всіх форм у вкладеному наборі форм кожної форми.
Докладніше про пакет з відкритим вихідним кодом Django CRM (Керування відносинами з клієнтами) можна за цим посиланням (https://github.com/MicroPyramid/Django-CRM).

По статті запитували1питання

4

Вам це подобається? Поділіться в соціальних мережах!

КГ
  • 22 червня 2022 р. 15:02
  • (відредаговано)

Спасибо за полезную статью. Подскажите пожалуйста, что делать если нужно реализовать большее количество вложенных форм?
Например если на модель Address ссылается fk другой модели, на которую в свою очередь ссылается еще одна модель.
И нужно создавать все объекты с одной страницы.

Evgenii Legotckoi
  • 24 серпня 2022 р. 17:39

В этом примере и так два уровня вложенности, всё остальное делается подобным способом.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…