Django CBV: CRUD с ListView, DetailView и другими Views
Разбираем CRUD в Django с помощью CBV: ListView, DetailView, CreateView, UpdateView, FormView и DeleteView.


Используя Django, иногда мы ловим себя на мысли, что код в файле views.py повторяется из раза в раз: достаем объект из базы, проверяем форму и сохраняем.
Порой при работе с таблицей в django требуется изменить только имя модели или путь к шаблону, а остальной код остается такой же.
Прежде мы разобрали:
Теперь посмотрим как именно это применяется в Django с помощью классовых представлений.
Пройдемся по CreateView, UpdateView, FormView, ListView, DetailView и DeleteView и на живых примерах создадим, прочитаем, изменим и удалим объект.
В качестве примера представим, что идет речь о складе производственной компании, в которой периодически работают с админ панелью для создания, просмотра, изменения и удаления товаров.
Модель товара в Django выглядит следующим образом:
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200, verbose_name="Название")
description = models.TextField(verbose_name="Описание")
price = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name="Цена"
)
quantity = models.PositiveIntegerField(
default=0,
verbose_name="Остаток на складе"
)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.nameпример модели товара производственной компании в django коде
CreateView предназначен для создания новых объектов модели. Он сам предоставляет форму с полями, обрабатывает полученные данные, проверяет их и сохраняет запись при успешной валидации (проверке).
Для создания товара достаточно указать model - модель, fields - разрешенные к редактированию поля, template_name - шаблон и success_url - ссылка, куда пользователь будет перенаправлен после успешного сохранения данных.
from django.urls import reverse_lazy
from django.views.generic import CreateView
from .models import Product
class ProductCreateView(CreateView):
model = Product
fields = [
"name",
"description",
"price",
"quantity",
]
template_name = "products/product_form.html"
success_url = reverse_lazy("product_list")
пример views.py для CreateView в django CBV
reverse_lazy() позволяет указать имя маршрута, который Django сам переведет в URL. Этот вариант удобно использовать в атрибутах классов.
При GET-запросе (первом запросе) CreateView отображает пустые поля в форме.
После нажатия кнопки с type="submit" происходит отправка POST-запроса, Django проверяет полученные данные и если они проходят правила модели, то происходит сохранение.
Если данные не прошли проверку, пользователь увидит форму повторно с сообщениями об ошибках.
Для отображения формы можно использовать следующий шаблон:
<h1>Добавление товара</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Создать товар</button>
</form>пример шаблона для CreateView в django CBV
Тег {% csrf_token %} необходим для защиты формы от CSRF-атак.
Выражение {{ form.as_p }} выводит поля формы вместе с их названиями и подсказками, а так же отобразит ошибки.
UpdateView используется для редактирования ранее созданного объекта.
В отличие от CreateView, он сначала получает запись из базы данных, а затем подставляет в форму ее значения.
from django.urls import reverse_lazy
from django.views.generic import UpdateView
from .models import Product
class ProductUpdateView(UpdateView):
model = Product
fields = [
"name",
"description",
"price",
"quantity",
]
template_name = "products/product_form.html"
success_url = reverse_lazy("product_list")пример views.py для UpdateView в django CBV
Настройка практически повторяет CreateView. Основное различие заключается в том, что UpdateView работает с уже существующим объектом.
UpdateView определяет, какой объект нужно изменить, с помощью метода get_object(), который уже есть в классе.
По умолчанию Django получает значение первичного ключа из URL-параметра pk и ищет соответствующую запись в базе данных.
path(
"products/<int:pk>/update/",
ProductUpdateView.as_view(),
name="product_update",
)Пример urls.py для UpdateView в Django CBV
Если объект с указанным ключом отсутствует, Django вернёт ответ 404 Not Found.
Если требуется использовать иной параметр например slug или иное поле модели, то можно руководствоваться таблицей ниже:
| Поле модели | Способ |
|---|---|
| pk или id | По умолчанию |
| slug | slug_field и slug_url_kwarg |
| uuid | Переопределить get_object() |
| name, email или другое поле | Переопределить get_object() |
Во избежание ошибок, для параметра поиска объекта, используйте столбцы только с уникальными значениями.
Если форма создания и изменения очень похожи, то оптимально использовать один html шаблон и для CreateView и для UpdateView.
В django можно передавать отдельные переменные в контекст.
class ProductCreateView(CreateView):
# остальной код
template_name = "products/product_form.html"
extra_context = {
"title": "Создание товара",
"button_text": "Создать",
}
class ProductUpdateView(UpdateView):
# остальной код
template_name = "products/product_form.html"
extra_context = {
"title": "Редактирование товара",
"button_text": "Сохранить изменения",
}views пример использования одного шаблона для CreateView и UpdateView
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">
{{ button_text }}
</button>
</form>
</body>
</html>HTML пример использования одного шаблона для CreateView и UpdateView
Теперь используя один шаблон, пользователь будет видеть на каждой из страниц заданный заголовок и название кнопки.
FormView предназначен для работы с формами, когда создание или изменение объекта модели не требуется (контактные формы, поиск, фильтры, калькуляторы).
В нашем примере на складе требуется искать товар по штрихкоду.
Создадим простую django форму:
from django import forms
class ProductSearchForm(forms.Form):
query = forms.CharField(
label="Штрихкод товара",
required=False,
)пример создания формы поиска django
Здесь применяется forms.Form, а не forms.ModelForm, потому что не привязана к конкретному объекту модели.
from django.views.generic import FormView
from .models import Product
class ProductSearchView(FormView):
template_name = "products/product_search.html"
form_class = ProductSearchForm
def form_valid(self, form):
query = form.cleaned_data["query"]
products = Product.objects.filter(
name__icontains=query
)
return self.render_to_response(
self.get_context_data(
form=form,
products=products,
)
)пример кода с FormView для поиска
В form_class задается форма для использования.
Метод form_valid() вызывается после успешной проверки отправленных данных, и в нашем случае вернет найденные товары по штрихкоду.
Здесь намеренно не используется success_url, заместо перенаправления сразу отображаются результаты поиска.
| Особенность | FormView | CreateView | UpdateView |
|---|---|---|---|
| Назначение | Форма не изменяет таблицу БД | Создание объекта | Изменение объекта |
| Тип формы | Обычно forms.Form | Встроенная или самописная форма | Встроенная или самописная форма |
| Получение объекта | Не требуется автоматически | Создаётся новый объект | Получается существующий объект |
| Сохранение модели | Реализуется вручную при необходимости | Встроено | Встроено |
ListView предназначен для отображения списка объектов модели.
В складской системе с его помощью можно вывести вообще все товары или только те, которые подходят по критериям (фильтрам).
Одна из особенностей ListView это возможность задать сколько объектов будет на странице, в примере ниже по 20 шт, за счёт paginate_by = 20 .
from django.views.generic import ListView
from .models import Product
class ProductListView(ListView):
model = Product
template_name = "products/product_list.html"
context_object_name = "products"
paginate_by = 20пример кода с ProductListView файла views.py
Django получает объекты модели и передаёт их в шаблон под заданным выше именем products.
<h1>Товары на складе</h1>
<ul>
{% for product in products %}
<li>
<a href="{% url 'product_detail' product.pk %}">
{{ product.name }}
</a>
— {{ product.price }}
</li>
{% empty %}
<li>Товары не найдены.</li>
{% endfor %}
</ul>пример шаблона ListView в django
Если объекты отсутствуют, сработает блок {% empty %}, это улучшает пользовательский опыт, в случае отсутствия товаров пользователь не останется в догадках перед пустым экраном.
Чтобы показывать только определенные товары - отфильтрованные, достаточно переопределить get_queryset().
В примере ниже показываем только товары с положительным остатком на складе.
class ProductListView(ListView):
model = Product
template_name = "products/product_list.html"
context_object_name = "products"
def get_queryset(self):
return Product.objects.filter(
quantity__gt=0
).order_by("name")пример отфильтрованного ListView
Через get_queryset() также можно реализовать фильтрацию по параметрам запроса в ссылке, ограничить доступ смотрящему пользователю или прописать иную логику.
DetailView предназначен для отображения одного объекта. Если ListView показывает каталог товаров, то DetailView открывает карточку одной позиции из базы данных.
from django.views.generic import DetailView
from .models import Product
class ProductDetailView(DetailView):
model = Product
template_name = "products/product_detail.html"
context_object_name = "product"
При обращении к маршруту с параметром pkпример views.py с DetailView в Django
При обращении к маршруту с параметром pk Django получает соответствующий товар и передаёт его в шаблон под указанным выше именем product.
Как и в случае с UpdateView, логику получения объекта можно изменить через get_object().
<h1>{{ product.name }}</h1>
<p>{{ product.description }}</p>
<p>Цена: {{ product.price }}</p>
<p>Остаток на складе: {{ product.quantity }}</p>пример HTML для DetailView в Django
В шаблоне заместо product можно использовать object.
DeleteView используется для удаления существующих объектов.
Обычно пользователю сперва показывается вопрос «Уверены что хотите удалить?», затем после нажатия кнопки подтверждения отправляется форма для удаления.
from django.urls import reverse_lazy
from django.views.generic import DeleteView
from .models import Product
class ProductDeleteView(DeleteView):
model = Product
template_name = "products/product_confirm_delete.html"
context_object_name = "product"
success_url = reverse_lazy("product_list")пример views.py DeleteView в Django
В этом коде аналогично UpdateView и DetailView объект ищется по pk из url и передается в шаблон подтверждения, после успешного удаления пользователя направляют на страницу указанную в success_url, в нашем случае наListView, то есть на страницу списка товаров.
<h1>Удаление товара</h1>
<p>
Удалить товар «{{ product.name }}»?
</p>
<form method="post">
{% csrf_token %}
<button type="submit">Удалить</button>
</form>пример HTML шаблона DeleteView в Django
Обратите внимание для удаления лучше POST запрос, ибо GET переход по ссылке не безопасный и не должен приводить к удалению.
Прописывая ссылки в urls.py будьте осторожны, порядок имеет значение.
urlpatterns = [
path(
"products/",
ProductListView.as_view(),
name="product_list",
),
path(
"products/create/",
ProductCreateView.as_view(),
name="product_create",
),
path(
"products/search/",
ProductSearchView.as_view(),
name="product_search",
),
path(
"products/<slug:slug>/update/",
ProductUpdateView.as_view(),
name="product_update",
),
path(
"products/<slug:slug>/delete/",
ProductDeleteView.as_view(),
name="product_delete",
),
path(
"products/<slug:slug>/",
ProductDetailView.as_view(),
name="product_detail",
),
]пример порядка urls.py для CRUD в Django
Если разместить "products/search/" под "products/<slug:slug>/" , django может принять search за slug и выдать ошибку, кроме того невозможно будет попасть на действующую страницу search.
Если у вас остались вопросы по теме CRUD в django с использованием Class Based View, вы можете обратиться за консультацией к программисту.
По необходимости вы можете обратиться за консультацией
Вам может быть интересно

Ваш первый сайт на Django: обзор фреймворка на Python

Почему Django показывает желтый фон и какие-то ошибки

Как дополнить файл поставщика для маркетплейса

CRUD в разработке программ: что это и как работает
Django CBV: что такое Class-Based Views и зачем они
