Создание сайта на Django: Урок 22, CRUD запросы: создание, обновление, удаление статьи на сайте Django
avatar
7 | (offline)
❤️‍🔥Notehunter Developer
Добавлено:
Категория: Руководства «Django»
Комментариев: 0

CRUD запросы в Django 4.1 

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

Напоминаю, что я использую декомпозицию проекта для своего удобства, поэтому я создам папку forms, а в ней __init__.py, с файлом articles.py

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

  • modules
    • blog
      • forms
        • __init__.py
        • articles.py
      • migrations
      • models
      • views

Выглядит это следующим образом:

Кстати, чтоб создавать папку сразу с __init__.py в PyCharm, просто выберете создать Python Package

Создадим форму для создания статей на сайте:

modules/blog/forms/articles.py

from django import forms

from modules.blog.models import Article


class ArticleCreateForm(forms.ModelForm):

    """
    Форма добавления статей на сайте
    """
    class Meta:
        model = Article
        fields = (
            'title',
            'slug',
            'category',
            'short_description',
            'full_description',
            'thumbnail',
            'meta_title',
            'meta_keywords',
            'meta_description',
            'is_published',
        )

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы
        """
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({
                'class': 'form-control',
                'autocomplete': 'off'
            })
            self.fields['meta_title'].widget.attrs.update({
                'placeholder': 'Введите мета-название для поисковой системы'
            })
            self.fields['title'].widget.attrs.update({
                'placeholder': 'Заголовок статьи'
            })
            self.fields['slug'].widget.attrs.update({
                'placeholder': 'Ссылка статьи (необязательно)'
            })
            self.fields['meta_description'].widget.attrs.update({
                'placeholder': 'Введите небольшое описание в 300 символов для поисковой системы'
            })
            self.fields['meta_keywords'].widget.attrs.update({
                'placeholder': 'Введите ключевые слова через запятую для поиска'
            })
            self.fields['category'].empty_label = 'Выберите категорию'
            self.fields['is_published'].widget.attrs.update({
                'class': 'form-check-input'
            })

Пояснения:

  • Наследуемся от ModelForm.
  • В мета прописываем необходимые поля при заполнении статьи с сайта.
  • Модель - Article
  • Не добавляем created_at, updated_at, т.к они создаются автоматически.
  • В методе инициализации def __init__ мы добавляем стили, добавляем placeholder для полей, чтоб было более понятно для пользователей. 
  • Вместо "-" для категорий мы будем выводить надпись "Выберите категорию"

Если вы используете структуру как у меня, то добавляем в __init__.py следующее:

from modules.blog.forms.articles import ArticleCreateForm

__all__ = '__all__'

Давайте перейдем к созданию представлений.

CreateView в Django 4.1

Импортируем сразу необходимые представления, и создаем ArticleCreateView

modules/blog/views/articles.py

from django.views.generic import CreateView, DeleteView, UpdateView
from modules.blog.forms import ArticleCreateForm

class ArticleCreateView(CreateView):
    """
    Представление: создание материалов на сайте
    """
    model = Article
    template_name = 'modules/blog/articles/article-create.html'
    form_class = ArticleCreateForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Добавление статьи на сайт'
        return context

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.save()
        return super().form_valid(form)

Пояснения:

  • Импортируем нашу форму
  • В валидации формы, мы добавляем автора за текущего пользователя и проверяем ее на ошибки.
  • Если ошибок нет, сохраняем форму.

Напоминаю, что если вы проходили все уроки, то на данный момент у вас views/articles.py должен выглядеть так:

from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView

from modules.blog.forms import ArticleCreateForm
from modules.blog.models import Article, Category


class ArticleListView(ListView):
    model = Article
    template_name = 'modules/blog/articles/article-list.html'
    context_object_name = 'articles'
    paginate_by = 1
    queryset = Article.objects.all().filter(is_published=True)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Главная страница'
        return context


class ArticleDetailView(DetailView):
    model = Article
    template_name = 'modules/blog/articles/article-detail.html'
    context_object_name = 'article'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = self.object.title
        return context


class ArticleByCategoryListView(ListView):
    model = Article
    template_name = 'modules/blog/articles/article-list.html'
    context_object_name = 'articles'
    category = None

    def get_queryset(self):
        self.category = Category.objects.get(pk=self.kwargs['pk'])
        queryset = Article.objects.all().filter(category_id=self.category.id)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Статьи из категории: {self.category.title}'
        return context


class ArticleCreateView(CreateView):
    """
    Представление: создание материалов на сайте
    """
    model = Article
    template_name = 'modules/blog/articles/article-create.html'
    form_class = ArticleCreateForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Добавление статьи на сайт'
        return context

    def form_valid(self, form):
        form.instance.author = self.request.user
        form.save()
        return super().form_valid(form)

Шаблон html для создания статьи:

templates/modules/blog/articles/article-create.html

{% extends 'main.html' %}

{% block content %}
<div class="card mb-3 border-0 nth-shadow">
    <div class="card-body">
        <div class="card-title nth-card-title">
            <h4>Добавление статьи</h4>
        </div>
        <form method="post" action="{% url 'article-create-view' %}" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <div class="d-grid gap-2 d-md-block mt-2">
                <button type="submit" class="btn btn-dark">Добавить статью</button>
            </div>
        </form>
    </div>
</div>
{% endblock %}

Пояснения:

  • Обязательно добавляем enctype="multipart/form-data" для загрузки изображений/медиа файлов.
  • {% csrf_token %} для защиты от подмены данных.
  • Метод POST, так как мы добавляем.
  • И action="{% url 'article-create-view' %}" в форме указывающий на представление.
  • Остальное - Bootstrap стили.

Отлично, осталось добавить в urls.py созданное представление:

modules/blog/urls.py

from django.urls import path

from modules.blog.views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, ArticleCreateView

urlpatterns = [
    path('', ArticleListView.as_view(), name='article-list'),
    path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
    path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
    path('articles/create/', ArticleCreateView.as_view(), name='article-create-view')
]

Проверяем на сайте!

Вот так выглядит форма, самая простая, без особых заморочек

После нажатия добавить нас перекидывает на добавленную статью:

А если перейдем в список:

А теперь создадим форму для обновления статьи, а также представление.

ArticleUpdateForm

modules/blog/forms/articles.py

class ArticleUpdateForm(ArticleCreateForm):

    class Meta:
        model = Article
        fields = ArticleCreateForm.Meta.fields + ('reason', 'is_fixed')

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы
        """
        super().__init__(*args, **kwargs)
        self.fields['is_fixed'].widget.attrs.update({
                'class': 'form-check-input'
        })

Пояснения:

  • В принципе, вы могли использовать форму создания статьи, но так как я хочу добавить кнопку для фиксации статьи и причину обновления, я наследуюсь от нашей формы для создания и добавляю необходимые поля.
  • Задаю стиль для чекбокса в стиле Bootstrap.

UpdateView в Django 4.1

Теперь добавим представление обновления.

class ArticleUpdateView(UpdateView):
    """
    Представление: обновления материала на сайте
    """
    model = Article
    template_name = 'modules/blog/articles/article-update.html'
    context_object_name = 'article'
    form_class = ArticleUpdateForm

    def form_valid(self, form):
        form.instance.updated_by = self.request.user
        form.save()
        return super().form_valid(form)

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Обновление статьи: {self.object.title}'
        return context

Пояснения:

  • Как помните, мы в модели указали автора обновления. Я снова получаю его из текущего пользователя и сохраняю форму.
  • В контекст я передаю название редактируемой статьи.

Давайте добавим в urls.py ссылку на обновление

modules/blog/urls.py

from django.urls import path

from modules.blog.views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, ArticleCreateView, \
    ArticleUpdateView

urlpatterns = [
    path('', ArticleListView.as_view(), name='article-list'),
    path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
    path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
    path('articles/create/', ArticleCreateView.as_view(), name='article-create-view'),
    path('articles/<str:slug>/update/', ArticleUpdateView.as_view(), name='article-update-view'),
]

Передаем обязательно slug, или же pk. В моем случае я передаю слаг для определения объекта и его редактирования.

Теперь создадим шаблон для обновления:

templates/modules/blog/articles/article-update.html

{% extends 'main.html' %}

{% block content %}
<div class="card mb-3 border-0 nth-shadow">
    <div class="card-body">
        <div class="card-title nth-card-title">
            <h4>Обновление статьи: {{ article.title }}</h4>
        </div>
        <form method="post" enctype="multipart/form-data">
            {% csrf_token %}
            {{ form.as_p }}
            <div class="d-grid gap-2 d-md-block mt-2">
                <button type="submit" class="btn btn-dark">Обновить статью</button>
            </div>
        </form>
    </div>
</div>
{% endblock %}

Пояснения:

  • Обязательно добавляем enctype="multipart/form-data" для загрузки изображений/медиа файлов.
  • {% csrf_token %} для защиты от подмены данных.
  • Метод POST, так как мы добавляем/обновляем.
  • Остальное - Bootstrap стили.

Проверяем на сайте:

Как видите, добавились поля из новой формы! Давайте попробуем вывести причину обновления и того, кто обновил?

В article-detail.html:

{% extends 'main.html' %}

{% block content %}
    <div class="card border-0">
        <div class="card-body">
            <div class="row">
                <div class="col-md-4">
                    <figure>
                        <img src="{{ article.get_thumbnail }}" width="200" alt="{{ article.title }}">
                    </figure>
                </div>
                <div class="col-md-8">
                     <h5 class="card-title">
                        {{ article.title }}
                    </h5>
                    <small class="card-subtitle">
                        {{ article.created_at }} / {{ article.category }}
                    </small>
                    <div class="card-text">
                        {{ article.full_description }}
                    </div>
                    <hr/>
                    {% if article.reason %}{{ article.reason }} / обновил: {{ article.updated_by }}{% endif %}
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Добавил следующее:

<hr/>
{% if article.reason %}{{ article.reason }} / обновил: {{ article.updated_by }}{% endif %}

Обновляем с задачей причины, и нажимаем обновление.

Отлично, представление обновления работает. Осталось последнее - представление удаления.

DeleteView в Django 4.1

modules/blog/views/articles.py

from django.urls import reverse_lazy

class ArticleDeleteView(DeleteView):
    """
    Представление: удаления материала
    """
    model = Article
    success_url = reverse_lazy('article-list')
    context_object_name = 'article'
    template_name = 'modules/blog/articles/article-delete.html'

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Удаление статьи: {self.object.title}'
        return context

Пояснения:

  • В параметре success_url при удалении добавил редирект на страницу со статьями

Добавим представление в urls.py

modules/blog/urls.py

from django.urls import path

from modules.blog.views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, ArticleCreateView, \
    ArticleUpdateView, ArticleDeleteView

urlpatterns = [
    path('', ArticleListView.as_view(), name='article-list'),
    path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
    path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
    path('articles/create/', ArticleCreateView.as_view(), name='article-create-view'),
    path('articles/<str:slug>/update/', ArticleUpdateView.as_view(), name='article-update-view'),
    path('articles/<str:slug>/delete/', ArticleDeleteView.as_view(), name='article-delete-view'),
]

Давайте добавим шаблон, и попробуем удалить статью:

templates/modules/blog/articles/article-update.html

{% extends 'main.html' %}

{% block content %}
<div class="card mb-3 border-0 nth-shadow">
    <div class="card-body">
        <div class="card-title nth-card-title">
            <h4>Удаление статьи</h4>
        </div>
        <form method="post">
            {% csrf_token %}
            <div class="alert alert-warning" role="alert">
                <i class="fas fa-info-circle"></i> Вы собираетесь удалить статью: <strong>{{ article.title }}</strong>, вы подтверждаете свое действие?
            </div>
            <div class="d-grid gap-2 d-md-block mt-2">
                <button type="submit" class="btn btn-dark">Удалить статью</button>
                <a href="{{ article.get_absolute_url }}" class="btn btn-primary">Отменить удаление</a>
            </div>
        </form>
    </div>
</div>
{% endblock %}

Жмем удалить и перемещаемся на список статей:

Все, наша статья была успешно удалена. На этом этот урок большой закончен, в следующем уроке мы рассмотрим миксины для безопасности CRUD методов. Надеюсь у вас все получилось и было все более чем понятно :)

Комментарии к статье 0
Комментариев нет
Форма добавления комментария (необходима регистрация)