Cat

Django 38.2. Добавление, редактирование, удаление поста

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

Все статьи

Icon Link

Дополнительные материалы

Icon Link

Реклама

Icon Link
Сайт на Django proDream 19 Декабрь 2023 Просмотров: 1815

В прошлом посте, мы доработали профиль пользователя, превратив его в кабинет автора. Для этого мы написали свой декоратор и расширили шаблон страницы.

В этом посте мы разберёмся, что такое миксины (mixins), для чего они нужны, как применяются и как написать свои. 
Изменим имеющееся представление страницы добавления поста, превратив его в базовый класс, от которого унаследуем два представления: добавления и изменения поста.
Также добавим представление и страницу удаления поста.

Начнём с небольшой теории.

 

Что такое миксины (mixins)?

Миксины (mixins) представляют собой специальные классы, которые содержат методы и функциональность для повторного использования в других классах. Они используются для организации кода, обеспечивая переиспользование функциональности между различными классами представлений, моделей или других компонентов Django.

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

 

Как работают миксины в Django:

Определение миксина:

Миксин - это обычно класс Python, содержащий методы и атрибуты, предназначенные для использования в других классах. Например:

class MyMixin:
    def some_method(self):
        # Логика метода
        pass

 

Использование миксина:

Для применения миксина к классу необходимо добавить его в список наследования:

from django.views import View


class MyView(MyMixin, View):
    def get(self, request):
        # Логика представления
        return HttpResponse('Hello, World!')

 

Здесь MyView наследуется от MyMixin и View. Теперь методы из MyMixin доступны в MyView.

 

Применение миксинов в Django:

  • Представления (Views): Миксины могут использоваться для добавления функциональности к представлениям Django, например, для добавления проверок доступа, кэширования или другой логики.
  • Модели (Models): Миксины также могут содержать методы для работы с моделями Django, например, для добавления общих методов или свойств к нескольким моделям.
  • Формы (Forms): Миксины можно использовать для повторного использования функциональности в формах Django, такой как валидация данных или обработка форм.
  • Другие компоненты: Кроме того, миксины можно использовать практически в любом компоненте Django, где требуется повторное использование функциональности.

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

 

Собственные миксины.

От теории перейдём к практике и напишем два миксина:

  • Миксин проверки принадлежности пользователя к указанной группе.
  • Миксин проверки того, что пользователь и автор поста - один человек.

В директории приложения user_app создадим файл mixins.py. В нём будем писать наши миксины.

 

Миксин проверки принадлежности к группе.

Для того, что бы проверять входит ли пользователь в необходимую группу, в данном случае в группу Автор, можно в каждом представлении прописывать метод dispatch() с проверкой. Чтобы избежать дублирования кода, мы напишем миксин с проверкой.

Создадим класс PermissionGroupRequiredMixin.

В нём пропишем поле group_required и присвоим ему значение None. Это поле с названием необходимой группы будем прописывать в представлении.

Затем создадим метод dispatch(). В методе проверяем, входит ли пользователь в указанную группу, то мы вызываем dispatch() из класса представления. Иначе поднимаем ошибку 403 HttpResponseForbidden с сообщением об отсутствии доступа.

 

Код миксина:

from django.http import HttpResponseForbidden


class PermissionGroupRequiredMixin:  
    group_required = None  

    def dispatch(self, request, *args, **kwargs):  
        if request.user.groups.filter(name=self.group_required).exists():  
            return super().dispatch(request, *args, **kwargs)  
        else:  
            raise HttpResponseForbidden("Вы не имеете доступа к этой странице.")

 

Как видим, достаточно просто.

 

Миксин проверки автора поста.

Для представлений редактирования и удаления поста, необходима проверка того, что автор поста и текущий пользователь - один человек.

Создадим класс PermissionSameAuthorMixin.

Код будет такой же, как и предыдущий. 
Полей у миксина не будет, а в методе dispatch() изменим проверку.

 

Код миксина:

from django.http import HttpResponseForbidden


class PermissionSameAuthorMixin:  

    def dispatch(self, request, *args, **kwargs):  
        if request.user  self.get_object().author:  
            return super().dispatch(request, *args, **kwargs)  
        else:  
            raise HttpResponseForbidden("Вы не имеете доступа к этой странице.")

 

Представления.

В директории приложения user_app откроем файл views.py.

В посте Django 36. Добавление постов пользователем мы написали следующее представление для страницы добавления поста:

class AddPostByAuthorView(UserPassesTestMixin, CreateView):
    template_name = 'user_app/add_post.html'
    form_class = forms.AddPostByAuthorForm
    model = PostModel

    def test_func(self):
        return self.request.user.groups.filter(name='Автор').exists()

    def get_success_url(self):
        return reverse('user_app:user_profile', kwargs={'username': self.request.user.username})

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

Мы его изменим, уберём лишнее и создадим два наследуемых класса.

 

Базовое представление.

Первым делом изменим имя класса на PostByAuthor, убрав все наследования.

К имеющимся полям добавим новое поле group_required со значением необходимой для доступа к представлениям группы. В нашем случае группы Автор.

Удалим метод test_func, так как он нам больше не нужен.

Всё остальное оставляем как есть. Если IDE будет ругаться на некоторые методы, игнорируем. У нас в классе их нет, но они будут в наших классах наследниках.

 

Код класса:

from django.urls import reverse_lazy
from pytils.translit import slugify

from blog.models import PostModel
from user_app import forms

class PostByAuthor:  
    template_name = 'user_app/add_post.html'  
    group_required = 'Автор'  
    form_class = forms.AddPostByAuthorForm  
    model = PostModel  

    def get_success_url(self):  
        return reverse('user_app:user_profile', kwargs={'username': self.request.user.username})  

    def form_valid(self, form):  
        form.instance.slug = slugify(form.instance.title)[:50]  
        form.instance.author = self.request.user  
        return super().form_valid(form)

 

Представления страниц добавления поста и редактирования поста.

Как вы могли заметить в коде выше, среди полей основного класса мы оставили поле с именем шаблона страницы. Это связано с тем, что для добавления и для редактирования поста будем использовать один и тот же шаблон. Нам нет необходимости создавать второй шаблон, т.к. по содержанию они будут идентичны. Разница лишь в том, что при добавлении поста, поля будут пустыми, а при редактировании они будут заполнены.

Создаём два класса:

  • AddPostByAuthorView, унаследованный от PostByAuthor, PermissionGroupRequiredMixin, CreateView.
  • EditPostByAuthorView, унаследованный от PostByAuthor, PermissionSameAuthorMixin, UpdateView.

В первом классе мы наследуемся от нашего основного класса, миксина проверки принадлежности к группе и встроенного класса CreateView.
Во втором классе мы также наследуемся от основного класса, миксина, проверяющего идентичность автора поста и пользователя, и от стандартного UpdateView.

В обоих классах добавляем единственное поле extra_context, в котором прописываем заголовок страницы. Это всё, что будет в двух представлениях.

 

Код представлений:

from django.views.generic import CreateView, UpdateView

from user_app.mixins import PermissionSameAuthorMixin, PermissionGroupRequiredMixin


class AddPostByAuthorView(PostByAuthor, PermissionGroupRequiredMixin, CreateView):  
    extra_context = {'title': 'Добавление поста'}  


class EditPostByAuthorView(PostByAuthor, PermissionSameAuthorMixin, UpdateView):  
    extra_context = {'title': 'Изменить пост'}

 

Представление страницы удаления поста.

Для страницы удаления поста мы напишем отдельное представление.

Создадим класс DeletePostByAuthorView, унаследованный от PermissionSameAuthorMixin и DeleteView.

Прописываем четыре поля:

  • template_name - шаблон страницы.
  • model - модель поста.
  • group_required - требуемая группа для доступа к странице.
  • extra_context - дополнительные данные передаваемые в шаблон. В нашем случае заголовок страницы.

Также переопределяем метод get_success_url(), возвращая на страницу профиля. Он такой же как и в представлениях выше.

 

Код представления:

from django.views.generic import DeleteView
from django.urls import reverse_lazy

from blog.models import PostModel
from user_app.mixins import PermissionSameAuthorMixin


class DeletePostByAuthorView(PermissionSameAuthorMixin, DeleteView):  
    template_name = 'user_app/delete_post_confirm.html'  
    model = PostModel  
    group_required = 'Автор'  
    extra_context = {'title': 'Удалить пост?'}  

    def get_success_url(self):  
        return reverse_lazy('user_app:user_profile', kwargs={'username': self.request.user.username})

 

Шаблон страницы удаления поста.

Создадим файл delete_post_confirm.html в директории с шаблонами.

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

 

Код шаблона:

{% extends 'blog/base.html' %}  
{% block title %}{{ title }}{% endblock %}  

{% block content %}  
    <div class="form-section container mt-3">  
        <h2>Удаление поста {{ object }}</h2>  
        <p>Вы действительно хотите удалить этот пост?</p>  
        <form method="POST">  
            {% csrf_token %}  
            <button class="btn btn-secondary my-btn-danger me-2" type="submit">Да, удалить</button>  
            <a href="{% url 'user_app:user_profile' username=request.user.username %}" class="btn btn-primary my-btn-success">Отмена</a>  
        </form>    </div>{% endblock %}

 

URL-паттерны.

Осталось добавить URL-паттерны представлений.
Кнопки в кабинете автора мы добавили в прошлом посте.

Откроем файл urls.py в директории приложения user_app и добавим следующие строки в список urlpatterns:

path('post/add_post/', views.AddPostByAuthorView.as_view(), name='add_post'),  
path('post/edit_post/<int:pk>', views.EditPostByAuthorView.as_view(), name='edit_post'),  
path('post/delete_post/<int:pk>', views.DeletePostByAuthorView.as_view(), name='delete_post'),

 

Заключение.

В этом посте мы разобрали, что такое миксины. В Django есть ряд встроенных миксинов, таких как LoginRequiredMixin, проверяющий зарегистрирован пользователь или нет. Написание собственных миксинов поможет расширить функционал представлений, без дублирования кода.

Кроме того, мы ещё раз попробовали создать базовый класс и сделать на его основе представления. Мы сделали один большой класс и два буквально двустрочных. Если бы мы не сделали базовый класс, у нас вместо трёх, было бы два представления, однако оба были бы идентичными.

 

Автор

  • 10 сентября 2024 г. 20:01

    Я пытаюсь изменить пост, но вместо этого создается еще одна его копия.

  • 10 сентября 2024 г. 21:10

    в шаблоне изменил на
    <form method="post" enctype="multipart/form-data" action="{% if form.instance.pk %}{% url 'user_app:edit_post' form.instance.pk %}{% else %}{% url 'user_app:add_post' %}{% endif %}">

    и все заработало

  • Реклама