• Информация на сайте предоставляется «как есть», может содержать неточности и не является консультацией или офертой.
  • Перед использованием проконсультируйтесь со специалистом.

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

Сайт использует cookies для работы и сбора аналитики. Вы можете принять или настроить их, а также полностью отключить в настройках браузера.

Настройки cookies
Необходимые для работы веб-сайта

Используются для входа, навигации и защиты (например: sessionid, csrftoken). Без них сайт может работать с перебоями.

Помогают улучшить сайт, собирая статистику посещений (например, через Яндекс Метрику).

Используются для показа персонализированной рекламы и ретаргетинга в рекламных сетях.

Настройки интерфейса

Декоративный эффект свечения при клике по странице.

DEV -
PROGRAM.RU
Контакты

Django CBV: CRUD с ListView, DetailView и другими Views

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

django crud cbv

Используя Django, иногда мы ловим себя на мысли, что код в файле views.py повторяется из раза в раз: достаем объект из базы, проверяем форму и сохраняем.

Порой при работе с таблицей в django требуется изменить только имя модели или путь к шаблону, а остальной код остается такой же.

Прежде мы разобрали:

Теперь посмотрим как именно это применяется в Django с помощью классовых представлений.

Пройдемся по CreateView, UpdateView, FormView, ListView, DetailView и DeleteView и на живых примерах создадим, прочитаем, изменим и удалим объект.

Подготовительная часть

В качестве примера представим, что идет речь о складе производственной компании, в которой периодически работают с админ панелью для создания, просмотра, изменения и удаления товаров.

Модель товара в Django выглядит следующим образом:

python

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: создание объекта

CreateView предназначен для создания новых объектов модели. Он сам предоставляет форму с полями, обрабатывает полученные данные, проверяет их и сохраняет запись при успешной валидации (проверке).

Настройка CreateView

Для создания товара достаточно указать model - модель, fields - разрешенные к редактированию поля, template_name - шаблон и success_url - ссылка, куда пользователь будет перенаправлен после успешного сохранения данных.

python
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. Этот вариант удобно использовать в атрибутах классов.

Как CreateView обрабатывает форму

При 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: обновление объекта

UpdateView используется для редактирования ранее созданного объекта.

В отличие от CreateView, он сначала получает запись из базы данных, а затем подставляет в форму ее значения.

Настройка UpdateView

python
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 определяет объект

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 или иное поле модели, то можно руководствоваться таблицей ниже:

таблица описывает каким способом переопределяется параметр поиска объекта в UpdateView
Поле моделиСпособ
pk или idПо умолчанию
slugslug_field и slug_url_kwarg
uuidПереопределить get_object()
name, email или другое полеПереопределить get_object()

Во избежание ошибок, для параметра поиска объекта, используйте столбцы только с уникальными значениями.

Можно ли назначить один шаблон для CreateView и UpdateView

Если форма создания и изменения очень похожи, то оптимально использовать один html шаблон и для CreateView и для UpdateView.

В django можно передавать отдельные переменные в контекст.

python
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

html

<!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: обработка формы без сохранения модели

FormView предназначен для работы с формами, когда создание или изменение объекта модели не требуется (контактные формы, поиск, фильтры, калькуляторы).

В нашем примере на складе требуется искать товар по штрихкоду.

Создание формы поиска django

Создадим простую django форму:

from django import forms


class ProductSearchForm(forms.Form):
    query = forms.CharField(
        label="Штрихкод товара",
        required=False,
    )

пример создания формы поиска django

Здесь применяется forms.Form, а не forms.ModelForm, потому что не привязана к конкретному объекту модели.

Использование FormView для создания поиска

python
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

Таблица сравнения FormView / CreateView / UpdateView
ОсобенностьFormViewCreateViewUpdateView
НазначениеФорма не изменяет таблицу БДСоздание объектаИзменение объекта
Тип формыОбычно forms.FormВстроенная или самописная формаВстроенная или самописная форма
Получение объектаНе требуется автоматическиСоздаётся новый объектПолучается существующий объект
Сохранение моделиРеализуется вручную при необходимостиВстроеноВстроено

ListView: получение списка объектов

ListView предназначен для отображения списка объектов модели.

В складской системе с его помощью можно вывести вообще все товары или только те, которые подходят по критериям (фильтрам).

Настройка ListView

Одна из особенностей ListView это возможность задать сколько объектов будет на странице, в примере ниже по 20 шт, за счёт paginate_by = 20 .

python
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.

html
<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 %}, это улучшает пользовательский опыт, в случае отсутствия товаров пользователь не останется в догадках перед пустым экраном.

Фильтрация и сортировка товаров в ListView

Чтобы показывать только определенные товары - отфильтрованные, достаточно переопределить 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: просмотр одного объекта

DetailView предназначен для отображения одного объекта. Если ListView показывает каталог товаров, то DetailView открывает карточку одной позиции из базы данных.

Настройка DetailView

python
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: удаление объекта

DeleteView используется для удаления существующих объектов.

Обычно пользователю сперва показывается вопрос «Уверены что хотите удалить?», затем после нажатия кнопки подтверждения отправляется форма для удаления.

Настройка DeleteView

python
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, то есть на страницу списка товаров.

html
<h1>Удаление товара</h1>

<p>
    Удалить товар «{{ product.name }}»?
</p>

<form method="post">
    {% csrf_token %}
    <button type="submit">Удалить</button>
</form>

пример HTML шаблона DeleteView в Django

Обратите внимание для удаления лучше POST запрос, ибо GET переход по ссылке не безопасный и не должен приводить к удалению.

Urls.py для CRUD в django

Прописывая ссылки в urls.py будьте осторожны, порядок имеет значение.

python
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, вы можете обратиться за консультацией к программисту.

Вам может быть интересно

Поделиться
Ничего не найдено
Найдите нужную страницу

Статьи, инструменты и помощь