Введение в CBV (Class Based Views). Классы View и TemplateView

Курс по Django: https://stepik.org/a/183363

Архив проекта: 51_sitewomen.zip

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

CBV – Class-Based Views

https://docs.djangoproject.com/en/4.2/ref/class-based-views/

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

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

Если перейти по ссылке:

https://docs.djangoproject.com/en/4.2/topics/class-based-views/intro/

то можно подробно почитать о порядке использования классов представлений, в том числе и класса View. Мы видим, что он поддерживает два метода get() и post() для обработки GET и POST-запросов соответственно. Давайте с этого и начнем – с внедрения класса View в наш проект.

У нас имеется подходящая функция представления addpage(), которая содержит обработку GET и POST-запросов. Определим после нее класс представления с именем AddPage, унаследованный от класса View:

class AddPage(View):
    def get(self, request):
        pass
 
    def post(self, request):
        pass

В этом классе мы объявили два метода get() и post(), которые будут срабатывать при GET и POST-запросах соответственно. Каждый метод должен иметь дополнительный параметр request с данными запроса. Сам же базовый класс View необходимо импортировать из следующей ветки:

from django.views import View

Если перейти внутрь этого класса, то можно увидеть список из всех поддерживаемых им методов. А также специальный метод as_view(), который используется для привязки этого класса к маршрутам URL-адресов.

Но для начала мы с вами определим реализации методов get() и post(). Для этого достаточно скопировать соответствующие строчки из функции addpage(). Получим следующую реализацию класса:

class AddPage(View):
    def get(self, request):
        form = AddPostForm()
        return render(request, 'women/addpage.html', {'menu': menu, 'title': 'Добавление статьи', 'form': form})
 
    def post(self, request):
        form = AddPostForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('home')
 
        return render(request, 'women/addpage.html', {'menu': menu, 'title': 'Добавление статьи', 'form': form})

То есть, по методу get() мы просто отдаем пустую форму, а по методу post() создаем форму с принятыми данными, если данные корректны, то сохраняем их в БД и делаем перенаправление на главную страницу, иначе, возвращаем форму с ранее введенными значениями и списком ошибок ее заполнения.

Как видите, все достаточно очевидно и просто. Всю логику по вызову нужных методов (get, post и др.) базовый класс View берет на себя. Нам остается только разнести код по этим методам и связать класс AddPage с маршрутом добавления новой статьи. Для этого перейдем в файл women/urls.py и в списке urlpatterns для маршрута addpage пропишем класс AddPage:

urlpatterns = [
    path('', views.index, name='home'),
    path('about/', views.about, name='about'),
    path('addpage/', views.AddPage.as_view(), name='add_page'),
    ...
]

Все, мы с вами только что заменили функцию представления addpage() на класс представления AddPage. После запуска тестового веб-сервера и перехода на страницу:

http://127.0.0.1:8000/addpage/

видим срабатывание метода get() и отображение пустой формы для добавления статьи в окне браузера. Если заполнить эту форму и выполнить отправку данных, то мы попадаем во второй метод post(), где эти данные после проверки сохраняются в БД.

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

Класс TemplateView

Во второй части занятия покажу пример использования еще одного класса представления TemplateView. Само название уже говорит нам, что он служит для обработки шаблонов и отправки результата пользователю. Например, у нас есть функция index(), которая возвращает список постов, используя шаблон index.html. Давайте заменим эту функцию классом TemplateView. Для этого ниже мы определим новый класс WomenHome следующим образом:

class WomenHome(TemplateView):
    template_name = 'women/index.html'

Дополнительно должны импортировать базовый класс из ветки:

from django.views.generic import TemplateView

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

Давайте прямо в таком виде свяжем маршрут главной страницы с классом WomenHome и посмотрим, что получится (в файле women/urls.py):

urlpatterns = [
    path('', views.WomenHome.as_view(), name='home'),
    ...
]

Видим отображение пустого шаблона без списка статей и пунктов меню. Почему так произошло? Очевидно по той причине, что мы в шаблон не передаем никаких дополнительных данных. Как выполнить эту передачу? Если перейти в базовый класс TemplateView, то видим здесь реализацию метода get(), в котором формируется контекст (набор данных) с помощью метода get_context_data() c последующей передачей результата в шаблон. Причем этот метод определен в классе миксина ContextMixin. И в нем мы видим дополнительный атрибут extra_context. Само название атрибута говорит о возможности определения через него дополнительного контекста (дополнительных данных).

Возвращаемся в класс WomenHome и определим в нем этот атрибут extra_context следующим образом:

class WomenHome(TemplateView):
    template_name = 'women/index.html'
    extra_context = {
        'title': 'Главная страница',
        'menu': menu,
        'posts': Women.published.all().select_related('cat'),
        'cat_selected': 0,
    }

(Я просто скопировал определение словаря data из функции index.) После обновления главной страницы увидим ее в прежнем виде.

Также в методе as_view() мы можем передавать дополнительные аргументы для классов представлений. Например, используя именованный параметр extra_context, можно передавать в класс любые статические данные, которые будут доступны в шаблоне. По сути, все данные из extra_context будут автоматически присвоены одноименному атрибуту и передаваться в шаблон. Поэтому, строчка:

WomenHome.as_view(extra_context={'title': "Главная страница сайта"})

передает в шаблон параметр title с указанным значением. Но я это все-таки это уберу (было сделано исключительно в учебных целях), оставлю как было.

Обратите еще внимание на то, что словарь extra_context можно использовать именно для заранее сформированных данных, которые, затем, уже никак не меняются. Если же мы собираемся передавать данные, которые формируются динамически в момент поступления запроса, например, менять параметр cat_selected в зависимости от параметра cat_id GET-запроса:

http://127.0.0.1:8000/category/?cat_id=2

то через атрибут extra_context сделать это уже не получится. Для этого потребуется переопределять метод get_context_data базового класса. В принципе, с помощью метода get_context_data() можно передавать и статические и динамические данные, то есть, любую информацию в шаблон. Поэтому нередко можно встретить использование этого метода. В нашем примере им можно воспользоваться так:

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Главная страница'
        context['menu'] = menu
        context['posts'] = Women.published.all().select_related('cat')
        context['cat_selected'] = int(self.request.GET.get('cat_id', 0))
        return context

Если теперь попробовать поменять значения cat_id в GET-запросе, то будем видеть подсветку различных разделов. Правда, само содержимое будет оставаться прежним, т.к. мы этот функционал здесь не прописывали. Я лишь хотел показать отличия между методом get_context_data() и атрибутом extra_context.

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

Курс по Django: https://stepik.org/a/183363

Видео по теме