ModelForm: формы из моделей и save()
ModelForm — мост между формой и моделью. Он строит форму прямо из модели и умеет сохранять введённые данные в базу одной строкой.
Суть: ModelForm генерирует поля из модели, валидирует их и сохраняет объект методом save(). Это убирает дублирование между описанием модели и формы.
Проблема дублирования
Когда форма повторяет поля модели, описывать их дважды — расточительство и источник рассинхронизации. Добавили поле в модель — не забудьте в форму. ModelForm устраняет это: он берёт поля прямо из модели.
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "body", "is_published"]
# или fields = "__all__" — все поля
# или exclude = ["author"] — все, кроме указанных
Вложенный Meta указывает модель и список полей. Django сам определит типы виджетов, валидаторы и метки из определения модели.
Сохранение одной строкой
Главное преимущество — метод save(), который создаёт или обновляет объект модели:
def create_post(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False) # объект, но не в базе
post.author = request.user # доустановим поле
post.save() # теперь в базу
return redirect(post.get_absolute_url())
else:
form = PostForm()
return render(request, "blog/post_form.html", {"form": form})
Параметр commit=False — частый приём: он отдаёт объект без записи в базу, чтобы вы доустановили поля (например, автора из request.user), и только потом вызвали save().
Редактирование существующего объекта
Чтобы редактировать запись, передайте её в форму через instance. Тогда форма предзаполнится, а save() обновит существующую строку, а не создаст новую:
post = get_object_or_404(Post, pk=pk)
form = PostForm(request.POST or None, instance=post)
if form.is_valid():
form.save() # обновит, а не создаст
Связь с CreateView и UpdateView
Вспомните дженерики из раздела про views. CreateView и UpdateView внутри используют именно ModelForm. Указав model и fields, вы получаете готовую страницу создания/редактирования без единой строки логики сохранения — Django делает всё через ModelForm под капотом.
Как это работает под капотом
ModelForm читает поля модели, строит по ним форму и при save() переносит cleaned_data в объект модели. По сути это маппинг «описание модели → поля формы → объект». Вот его модель на чистом Python:
# Попробуй сам ▶ — генерация формы из модели и save()
model_fields = { # описание полей модели
"title": {"type": "char", "max_length": 200, "required": True},
"body": {"type": "text", "required": True},
"is_published": {"type": "bool", "required": False},
}
def build_form_fields(model, include):
return {name: model[name] for name in include} # как Meta.fields
def model_form_save(form_fields, data, commit=True):
obj = {}
for name, spec in form_fields.items():
value = data.get(name)
if spec["required"] and not value:
return None, f"Поле {name} обязательно"
obj[name] = value
return (obj if commit else {"_pending": obj}), None
fields = build_form_fields(model_fields, include=["title", "body", "is_published"])
print("Поля формы:", list(fields))
obj, err = model_form_save(fields, {"title": "Django", "body": "текст", "is_published": True})
print("save():", obj, "| ошибка:", err)
obj2, err2 = model_form_save(fields, {"title": "", "body": "x"})
print("save():", obj2, "| ошибка:", err2)
Django делает это полнее (виджеты, валидаторы, запись в БД), но суть — взять поля модели, провалидировать ввод и собрать объект для сохранения.
Частые ошибки
- Не указать fields или exclude в Meta. Django потребует явно перечислить поля (по соображениям безопасности).
- Использовать fields="__all__" на формах для пользователей. Так можно случайно открыть редактирование чувствительных полей.
- Забыть commit=False, когда нужно доустановить поле. Получите ошибку «поле не может быть пустым».
- Не передать instance при редактировании. Создастся новая запись вместо обновления.
Best practices
- Перечисляйте
fieldsявно, а не"__all__", особенно в публичных формах. - Используйте
commit=Falseдля доустановки служебных полей (автор, дата). - Для редактирования всегда передавайте
instance. - На типовом CRUD доверьте работу
CreateView/UpdateView.
Итоги
ModelForm строит форму из модели и сохраняет данные методом save(), убирая дублирование. commit=False позволяет доустановить поля перед записью, instance — редактировать существующий объект. Дженерики CreateView/UpdateView используют ModelForm под капотом. Дальше — пользователи, вход и защита от атак.