Представьте, что вы создаёте блог, где есть посты и категории. Каждый пост относится к одной или нескольким категориям (связь ManyToMany). Блогеру будет нужен интерфейс, чтобы не только добавить новый пост, но и отредактировать категории прямо в той же форме. Как это сделать? Использовать формы для связанных моделей!
В реальных проектах такие формы используются везде, где есть необходимость работать с данными из нескольких таблиц/моделей базы данных. Это ведь гораздо удобнее для пользователей — один интерфейс для всего.
Подготовка: создадим модели и свяжем их
Перед тем как начать работу с формами, сначала настроим модели. В качестве примера будем работать с блогом.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
categories = models.ManyToManyField(Category, related_name='posts')
def __str__(self):
return self.title
Здесь у нас две модели: Category для категорий и Post для постов. Посты могут быть связаны с несколькими категориями через поле categories (связь типа ManyToMany).
Подход №1: Работаем с отдельными формами
Первый подход к работе со связанными моделями — это использование отдельных форм для каждой модели. Например, форма для постов и форма для категорий. Мы начнем с простого.
Форма для создания постов
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'categories']
Форма для создания категорий
from django import forms
from .models import Category
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ['name']
Каждая модель здесь имеет свою отдельную форму. Поле categories автоматически отображается в форме PostForm как MultipleChoiceField, где пользователь сможет выбирать из существующих категорий.
Шаги в представлениях
Теперь нужно написать представления, чтобы обработать обе формы. Работу с категориями и постами можно разделить.
from django.shortcuts import render, redirect
from .models import Post, Category
from .forms import PostForm, CategoryForm
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect('post_list') # Перенаправляем на список постов
else:
form = PostForm()
return render(request, 'blog/create_post.html', {'form': form})
def create_category(request):
if request.method == 'POST':
form = CategoryForm(request.POST)
if form.is_valid():
form.save()
return redirect('category_list') # Перенаправляем на список категорий
else:
form = CategoryForm()
return render(request, 'blog/create_category.html', {'form': form})
В шаблонах это будет выглядеть примерно так:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Сохранить</button>
</form>
Этот подход удобен, но требует, чтобы пользователь сначала создал категории, а затем добавил посты и выбрал соответствующие категории. Хотите большего удобства? Давайте посмотрим, как сделать одну форму, которая будет поддерживать всё.
Подход №2: Inline формы для связанных моделей
Когда нужно дать пользователю возможность редактировать сразу и пост, и связанные категории, на помощь приходят inline формы. Django предоставляет мощный инструмент под названием inlineformset_factory.
Создание Inline FormSet
from django.forms import inlineformset_factory
from .models import Post, Category
CategoryInlineFormSet = inlineformset_factory(
Post, # Родительская модель
Category, # Связанная модель
fields=('name',), # Поля, которые редактируются
extra=1, # Количество пустых форм для новых объектов
can_delete=True # Возможность удалять связанные объекты
)
Здесь inlineformset_factory создаёт форму, которая позволяет редактировать категории, связанные с конкретным постом.
Представление для работы с Inline FormSet
Теперь нужно написать представление, чтобы отобразить и обработать нашу inline форму.
from django.shortcuts import render, get_object_or_404, redirect
from .models import Post
from .forms import PostForm
from .forms import CategoryInlineFormSet
def edit_post(request, pk):
post = get_object_or_404(Post, pk=pk)
form = PostForm(instance=post)
formset = CategoryInlineFormSet(instance=post)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
formset = CategoryInlineFormSet(request.POST, instance=post)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
return redirect('post_list') # Перенаправляем на список постов
return render(request, 'blog/edit_post.html', {'form': form, 'formset': formset})
Шаблон с основной формой и inline формами
Теперь создадим шаблон, где обе формы будут отображаться вместе.
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<h3>Категории:</h3>
{{ formset.management_form }}
{% for form in formset %}
{{ form.as_p }}
{% endfor %}
<button type="submit">Сохранить</button>
</form>
Как это работает?
PostFormпозволяет редактировать данные поста (title,content).CategoryInlineFormSetпозволяет редактировать категории, связанные с постом.- Если пользователь добавляет категорию или удаляет существующую, это автоматически фиксируется в базе.
Подход №3: Использование виджетов для ManyToMany
Если вам нужно простое решение для связи ManyToMany, можно использовать встроенные виджеты Django. Например, виджет CheckboxSelectMultiple позволяет отображать связанные объекты в виде списка чекбоксов.
Ваша форма будет выглядеть так:
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'categories']
widgets = {
'categories': forms.CheckboxSelectMultiple()
}
Теперь в шаблоне поле categories будет представлено списком чекбоксов, где пользователь сможет выбрать несколько категорий.
Практическое задание
- Реализуйте модель авторов
Author, связанных с постами через полеForeignKey. - Создайте форму, которая позволит редактировать посты и связанные с ними данные об авторах.
- Настройте представление и шаблон для работы с этой формой.
Такой подход даст вам реальное понимание, как работают формы для связанных моделей и какие из них удобнее использовать в различных сценариях.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ