231 lines
9.1 KiB
Markdown
231 lines
9.1 KiB
Markdown
---
|
||
status: processing
|
||
type: concept
|
||
tags:
|
||
- django
|
||
- forms
|
||
- crud
|
||
- validation
|
||
- modelform
|
||
created: 2026-05-30
|
||
updated: 2026-06-05
|
||
title: "Django: Работа с формами и CUD"
|
||
source:
|
||
- '[[lec5-django_cud_form.pdf]]'
|
||
aliases:
|
||
- 'Django: Формы и CUD'
|
||
---
|
||
|
||
# Django: Работа с формами и CUD
|
||
|
||
**CUD (Create, Update, Delete)** — операции изменения данных: создать, обновить, удалить.
|
||
|
||
Форма в Django — это слой между HTML-формой и Python-кодом. Она принимает данные от пользователя, проверяет их, приводит к нормальным типам и помогает сохранить результат.
|
||
|
||
## Типы форм
|
||
- **`forms.Form`** — обычная форма, не привязанная к модели. Подходит для поиска, обратной связи, фильтров, отправки email.
|
||
- **`forms.ModelForm`** — форма на основе модели. Django сам создаёт поля формы по полям модели и умеет сохранять объект через `form.save()`.
|
||
|
||
Если данные не нужно сохранять в таблицу напрямую — чаще подходит `forms.Form`. Если форма создаёт или редактирует модель — проще использовать `forms.ModelForm`.
|
||
|
||
```python
|
||
from django import forms
|
||
|
||
from .models import Blog
|
||
|
||
|
||
class BlogForm(forms.ModelForm):
|
||
class Meta:
|
||
model = Blog
|
||
fields = ["name", "tagline"]
|
||
widgets = {
|
||
"name": forms.TextInput(attrs={"class": "form-control"})
|
||
}
|
||
```
|
||
|
||
В `ModelForm` модель и поля задаются во внутреннем классе `Meta`. `fields` показывает, какие поля модели попадут в форму. `exclude` можно использовать наоборот: указать, какие поля не включать.
|
||
|
||
---
|
||
|
||
## Жизненный цикл формы в представлении
|
||
Обычно view работает так:
|
||
1. При `GET` создаёт пустую форму и показывает страницу.
|
||
2. При `POST` создаёт форму из `request.POST`.
|
||
3. Проверяет данные через `is_valid()`.
|
||
4. Если данные корректные — выполняет действие и делает `redirect`.
|
||
5. Если есть ошибки — снова показывает форму с ошибками.
|
||
|
||
```python
|
||
from django.shortcuts import redirect, render
|
||
|
||
|
||
def contact_view(request):
|
||
if request.method == "POST":
|
||
form = ContactForm(request.POST)
|
||
|
||
if form.is_valid():
|
||
data = form.cleaned_data
|
||
send_email(data["email"], data["message"])
|
||
return redirect("success")
|
||
else:
|
||
form = ContactForm()
|
||
|
||
return render(request, "contact.html", {"form": form})
|
||
```
|
||
|
||
---
|
||
|
||
## Валидация и очистка данных
|
||
- **`is_valid()`** — запускает проверку формы. После него появляются `form.cleaned_data` и `form.errors`.
|
||
- **Автоматическая очистка** — Django превращает строки из HTML-формы в Python-типы: число в `int`, дату в `date`, email в строку после проверки формата.
|
||
- **`clean_<fieldname>()`** — проверка одного конкретного поля.
|
||
- **`clean()`** — проверка всей формы, когда нужно сравнить несколько полей между собой.
|
||
|
||
```python
|
||
from django import forms
|
||
|
||
|
||
class RegisterForm(forms.Form):
|
||
username = forms.CharField(max_length=50)
|
||
password = forms.CharField(widget=forms.PasswordInput)
|
||
password_repeat = forms.CharField(widget=forms.PasswordInput)
|
||
|
||
def clean_username(self):
|
||
username = self.cleaned_data["username"]
|
||
|
||
if username.lower() == "admin":
|
||
raise forms.ValidationError("Это имя нельзя использовать")
|
||
|
||
return username
|
||
|
||
def clean(self):
|
||
cleaned_data = super().clean()
|
||
password = cleaned_data.get("password")
|
||
password_repeat = cleaned_data.get("password_repeat")
|
||
|
||
if password and password_repeat and password != password_repeat:
|
||
raise forms.ValidationError("Пароли не совпадают")
|
||
|
||
return cleaned_data
|
||
```
|
||
|
||
Для проверки связи между полями используют общий `clean()`. Например, "Пароль" и "Повтор пароля" нельзя нормально проверить через `clean_password()`, потому что нужно сравнить два поля.
|
||
|
||
---
|
||
|
||
## Generic Editing Views (CBV для CUD)
|
||
Django предоставляет готовые классы для типовых страниц редактирования:
|
||
- **`CreateView`** — создать объект.
|
||
- **`UpdateView`** — изменить объект; обычно ищет его по `pk` из URL.
|
||
- **`DeleteView`** — удалить объект; требует подтверждения через `POST`.
|
||
|
||
Главная идея: вместо ручной обработки `GET`, `POST`, `is_valid()` и `save()` можно описать модель, форму, шаблон и URL после успеха.
|
||
|
||
```python
|
||
from django.urls import reverse_lazy
|
||
from django.views.generic import CreateView, DeleteView, UpdateView
|
||
|
||
from .forms import BlogForm
|
||
from .models import Blog
|
||
|
||
|
||
class BlogCreateView(CreateView):
|
||
model = Blog
|
||
form_class = BlogForm
|
||
template_name = "blog/blog_form.html"
|
||
success_url = reverse_lazy("blog_list")
|
||
|
||
|
||
class BlogUpdateView(UpdateView):
|
||
model = Blog
|
||
form_class = BlogForm
|
||
template_name = "blog/blog_form.html"
|
||
success_url = reverse_lazy("blog_list")
|
||
|
||
|
||
class BlogDeleteView(DeleteView):
|
||
model = Blog
|
||
template_name = "blog/blog_confirm_delete.html"
|
||
success_url = reverse_lazy("blog_list")
|
||
```
|
||
|
||
## Отображение в шаблоне
|
||
- `{{ form.as_p }}` — быстро вывести поля формы в `<p>`.
|
||
- `{{ form.as_table }}` — вывести поля как строки таблицы.
|
||
- Ручное управление нужно, когда важна HTML-разметка, CSS-классы или отдельный вывод ошибок.
|
||
|
||
```html
|
||
<form method="post">
|
||
{% csrf_token %}
|
||
{{ form.as_p }}
|
||
<button type="submit">Сохранить</button>
|
||
</form>
|
||
```
|
||
|
||
```html
|
||
<form method="post">
|
||
{% csrf_token %}
|
||
|
||
{% for field in form %}
|
||
<div>
|
||
{{ field.label_tag }}
|
||
{{ field }}
|
||
{{ field.errors }}
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<button type="submit">Сохранить</button>
|
||
</form>
|
||
```
|
||
|
||
### CSRF-токен
|
||
**CSRF (Cross-Site Request Forgery)** — атака, при которой чужой сайт пытается отправить запрос от имени уже авторизованного пользователя.
|
||
|
||
Пример: пользователь залогинен в Django-приложении. Он открывает вредную страницу, а она незаметно отправляет `POST`-запрос на удаление объекта. Без защиты браузер приложит cookies пользователя, и сервер может принять запрос как настоящий.
|
||
|
||
`{% csrf_token %}` добавляет в форму скрытое поле с защитным токеном:
|
||
|
||
```html
|
||
<input type="hidden" name="csrfmiddlewaretoken" value="...">
|
||
```
|
||
|
||
При `POST` Django сверяет токен из формы с токеном пользователя. Если токена нет или он неправильный, запрос отклоняется.
|
||
|
||
Короткое правило: во все HTML-формы с `method="post"` добавлять `{% csrf_token %}`.
|
||
|
||
## Простыми словами для экзамена
|
||
|
||
Форма Django делает три вещи:
|
||
|
||
1. Описывает поля.
|
||
2. Валидирует входные данные.
|
||
3. Даёт очищенные данные в `cleaned_data`.
|
||
|
||
Основные поля:
|
||
|
||
- `CharField` — строка;
|
||
- `IntegerField` — целое число;
|
||
- `BooleanField` — `True/False`;
|
||
- `ChoiceField` — выбор из списка;
|
||
- `EmailField` — email;
|
||
- `DateField` — дата;
|
||
- `FileField` — файл.
|
||
|
||
`widget` управляет HTML-представлением поля, а не типом данных. Например, строковое поле можно показать как пароль:
|
||
|
||
```python
|
||
password = forms.CharField(widget=forms.PasswordInput)
|
||
```
|
||
|
||
Подсказка к полю задаётся через `help_text`:
|
||
|
||
```python
|
||
email = forms.EmailField(help_text="Введите рабочую почту")
|
||
```
|
||
|
||
После `form.is_valid()`:
|
||
|
||
- `form.cleaned_data` содержит нормальные Python-значения;
|
||
- `form.errors` содержит ошибки;
|
||
- `form.save()` у `ModelForm` сохраняет объект модели.
|