Представления на основе классов или функции в Django?
В этой статье мы рассмотрим различия между представлениями Django на основе классов (CBV) и представлениями на основе функций (FBV). Мы сравним и противопоставим их, а также рассмотрим плюсы и минусы каждого подхода (наряду со встроенными в Django общими представлениями на основе классов). К концу у вас должно быть хорошее понимание того, когда использовать один подход вместо другого.
Введение
Одним из основных применений Django (и, по сути, любого другого веб-фреймворка) является предоставление HTTP-ответов в ответ на HTTP-запросы. Django позволяет нам делать это с помощью так называемых представлений. Представление - это просто вызываемый объект, который принимает запрос и возвращает ответ.
Django изначально поддерживал только представления на основе функций (FBV), но их было трудно расширять, они не использовали преимущества объектно-ориентированного программирования (ООП) и не были DRY. Именно поэтому разработчики Django решили добавить поддержку представлений на основе классов (CBVs). CBV используют принципы ООП, что позволяет нам использовать наследование, повторно использовать код и в целом писать более качественный и чистый код.
Необходимо помнить, что CBV не предназначены для замены FBV. Все, чего можно достичь с FBV, можно достичь и с CBV. У каждого из них есть свои плюсы и минусы.
Наконец, Django предлагает готовые или общие CBV, которые обеспечивают решение общих проблем. Они имеют удобные для программистов названия и предлагают решения таких проблем, как отображение данных, редактирование данных и работа с данными на основе даты. Они могут использоваться самостоятельно или наследоваться в пользовательских представлениях.
Давайте рассмотрим различные типы представлений и узнаем, когда какой из них уместно использовать.
Представления на основе функций (FBV)
По своей сути FBVs - это просто функции. Их легко читать и работать с ними, поскольку вы можете видеть, что именно происходит. Благодаря своей простоте, они отлично подходят для начинающих пользователей Django. Поэтому, если вы только начинаете работать с Django, рекомендуется иметь некоторые рабочие знания о FBVs, прежде чем погружаться в CBVs.
Плюсы и минусы
Плюсы
- Явный поток кода (у вас есть полный контроль над тем, что происходит)
- Проста в реализации
- Легко понять
- Отлично подходит для уникальной логики представления
- Легко интегрировать с декораторами
Против
- Много повторяющегося (бойлерплейт) кода
- Обработка HTTP методов через условное ветвление
- Не используют преимущества ООП
- Труднее поддерживать
Быстрый пример
Пример FBV выглядит следующим образом:
from django.shortcuts import render, redirect
from django.views import View
def task_create_view(request):
if request.method == 'POST':
form = TaskForm(data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('task-list'))
return render(request, 'todo/task_create.html', {
'form': TaskForm(),
})
Это представление принимает request
, выполняет некоторую логику и возвращает HttpResponse
. Просто взглянув на код, мы можем увидеть первый недостаток: условное ветвление. Для каждого метода HTTP мы должны создать отдельную ветвь. Это может увеличить сложность кода и привести к спагетти-коду.
Следующим недостатком FBV является то, что они плохо масштабируются. По мере того, как ваша кодовая база будет становиться все больше и больше, вы заметите много повторяющегося (boilerplate) кода для работы с моделями (особенно с операциями CRUD). Попробуйте представить себе, насколько представление для создания статей будет отличаться от примера выше... Они были бы практически одинаковыми.
Для того чтобы использовать FBV, мы должны зарегистрировать их в файле urls.py следующим образом:
urlpatterns = [
path('create/', task_create_view, name='task-create'),
]
Вы должны выбрать FBV, если вы работаете над сильно адаптированной логикой представления. Другими словами, FBV - это отличный вариант использования для представления, которое не разделяет много кода с другими представлениями. Несколько реальных примеров использования FBV: представление статистики, представление диаграммы и представление сброса пароля.
Todo App (использование FBVs)
Давайте рассмотрим, как простое приложение todo, позволяющее выполнять CRUD-операции, можно написать, используя только FBVs.
Сначала мы инициализируем наш проект, определим наши модели, создадим HTML-шаблоны, а затем начнем работать над views.py. Вероятно, в итоге у нас получится что-то вроде этого:
# todo/views.py
from django.shortcuts import render, get_object_or_404, redirect
from .forms import TaskForm, ConfirmForm
from .models import Task
def task_list_view(request):
return render(request, 'todo/task_list.html', {
'tasks': Task.objects.all(),
})
def task_create_view(request):
if request.method == 'POST':
form = TaskForm(data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('task-list'))
return render(request, 'todo/task_create.html', {
'form': TaskForm(),
})
def task_detail_view(request, pk):
task = get_object_or_404(Task, pk=pk)
return render(request, 'todo/task_detail.html', {
'task': task,
})
def task_update_view(request, pk):
task = get_object_or_404(Task, pk=pk)
if request.method == 'POST':
form = TaskForm(instance=task, data=request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('task-detail', args={pk: pk}))
return render(request, 'todo/task_update.html', {
'task': task,
'form': TaskForm(instance=task),
})
def task_delete_view(request, pk):
task = get_object_or_404(Task, pk=pk)
if request.method == 'POST':
form = ConfirmForm(data=request.POST)
if form.is_valid():
task.delete()
return HttpResponseRedirect(reverse('task-list'))
return render(request, 'todo/task_delete.html', {
'task': task,
'form': ConfirmForm(),
})
Вы можете получить полный исходный код на GitHub.
В итоге мы получили простую и понятную логику представления. Вы не сможете сильно улучшить этот код.
Представления на основе классов (CBV)
Виды на основе классов, которые были введены в Django 1.3, предоставляют альтернативный способ реализации представлений в виде объектов Python вместо функций. Они позволяют нам использовать принципы ООП (самое главное - наследование). Мы можем использовать CBVs для обобщения частей нашего кода и извлечения их в виде представлений суперкласса.
CBV также позволяют вам использовать встроенные в Django общие представления на основе классов и миксины, которые мы рассмотрим в следующем разделе.
Плюсы и минусы
Плюсы
- Являются расширяемыми
- Они используют преимущества концепций ООП (самое главное - наследование)
- Отлично подходят для написания CRUD представлений
- Более чистый и многократно используемый код
- Встроенные в Django общие CBVs
- Они похожи на представления REST фреймворка Django
Против
- Неявный поток кода (многое происходит в фоновом режиме)
- Используется много миксинов, что может запутать
- Более сложный и трудный для освоения
- Декораторы требуют дополнительного импорта или переопределения кода
Для получения дополнительной информации просмотрите Каковы плюсы и минусы использования представлений на основе классов в Django/Python?
Быстрый пример
Давайте перепишем наш предыдущий пример FBV как CBV:
from django.shortcuts import render, redirect
from django.views import View
class TaskCreateView(View):
def get(self, request, *args, **kwargs):
return render(request, 'todo/task_create.html', {
'form': TaskForm(),
})
def post(self, request, *args, **kwargs):
form = TaskForm(data=request.POST)
if form.is_valid():
task = form.save()
return redirect('task-detail', pk=task.pk)
return self.get(request)
Мы видим, что этот пример не сильно отличается от подхода FBV. Логика более или менее одинакова. Основное отличие заключается в организации кода. Здесь каждый HTTP-метод рассматривается отдельным методом вместо условного ветвления. В CBV вы можете использовать следующие методы: get
, post
, put
, patch
, delete
, head
, options
, trace
.
Еще одним плюсом такого подхода является то, что HTTP-методы, которые не определены, автоматически возвращают 405 Method Not Allowed
ответ.
При использовании FBVs вы можете использовать один из разрешенных декораторов методов HTTP, например
@require_http_methods
, чтобы добиться того же самого.
Поскольку URL-резольвер Django ожидает вызываемую функцию, нам нужно вызвать as_view() при регистрации их в urls.py:
urlpatterns = [
path('create/', TaskCreateView.as_view(), name='task-create'),
]
Поток кода
Поток кода для CBV немного сложнее, потому что некоторые вещи происходят в фоновом режиме. Если мы расширим базовый класс View, то будут выполнены следующие шаги кода:
- Диспетчер URL Django направляет
HttpRequest
наMyView
. - Диспетчер URL Django вызывает
as_view()
наMyView
. as_view()
вызываетsetup()
иdispatch()
.dispatch()
вызывает метод для определенного метода HTTP илиhttp_method_not_allowed()
.- Возвращается
HttpResponse
.
Todo App (использование CBVs)
Теперь давайте перепишем наше приложение todo, чтобы использовать только CBVs:
# todo/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.views import View
from .forms import TaskForm, ConfirmForm
from .models import Task
class TaskListView(View):
def get(self, request, *args, **kwargs):
return render(request, 'todo/task_list.html', {
'tasks': Task.objects.all(),
})
class TaskCreateView(View):
def get(self, request, *args, **kwargs):
return render(request, 'todo/task_create.html', {
'form': TaskForm(),
})
def post(self, request, *args, **kwargs):
form = TaskForm(data=request.POST)
if form.is_valid():
task = form.save()
return redirect('task-detail', pk=task.pk)
return self.get(request)
class TaskDetailView(View):
def get(self, request, pk, *args, **kwargs):
task = get_object_or_404(Task, pk=pk)
return render(request, 'todo/task_detail.html', {
'task': task,
})
class TaskUpdateView(View):
def get(self, request, pk, *args, **kwargs):
task = get_object_or_404(Task, pk=pk)
return render(request, 'todo/task_update.html', {
'task': task,
'form': TaskForm(instance=task),
})
def post(self, request, pk, *args, **kwargs):
task = get_object_or_404(Task, pk=pk)
form = TaskForm(instance=task, data=request.POST)
if form.is_valid():
form.save()
return redirect('task-detail', pk=task.pk)
return self.get(request, pk)
class TaskDeleteView(View):
def get(self, request, pk, *args, **kwargs):
task = get_object_or_404(Task, pk=pk)
return render(request, 'todo/task_confirm_delete.html', {
'task': task,
'form': ConfirmForm(),
})
def post(self, request, pk, *args, **kwargs):
task = get_object_or_404(Task, pk=pk)
form = ConfirmForm(data=request.POST)
if form.is_valid():
task.delete()
return redirect('task-list')
return self.get(request, pk)
Также не забудем сделать наш urls.py вызывающим as_view()
:
# todo/urls.py
from django.urls import path
from .views import TaskListView, TaskDetailView, TaskCreateView, TaskUpdateView, TaskDeleteView
urlpatterns = [
path('', TaskListView.as_view(), name='task-list'),
path('create/', TaskCreateView.as_view(), name='task-create'),
path('<int:pk>/', TaskDetailView.as_view(), name='task-detail'),
path('update/<int:pk>/', TaskUpdateView.as_view(), name='task-update'),
path('delete/<int:pk>/', TaskDeleteView.as_view(), name='task-delete'),
]
Вы можете получить полный исходный код на GitHub.
Мы пожертвовали несколькими строками кода ради более чистого кода. Мы больше не используем условное ветвление. Если мы, например, посмотрим на TaskCreateView
и TaskUpdateView
, то увидим, что они практически одинаковы. Мы могли бы еще больше улучшить этот код, извлекая общую логику в родительский класс. Кроме того, мы могли бы извлечь логику представления и использовать ее в представлениях для других моделей.
Виды в Django, основанные на общих классах
Если бы вы следовали всем предложениям по рефакторингу, упомянутым в предыдущем разделе, то в итоге у вас получилось бы представление, имитирующее некоторые из общих представлений Django, основанных на классах. Общие CBVs в Django отлично подходят для решения таких распространенных проблем, как извлечение, создание, изменение и удаление объектов, а также для пагинации и архивных представлений. Они также ускоряют процесс разработки.
Быстрый пример
Давайте рассмотрим пример:
from django.views.generic import CreateView
class TaskCreateView(CreateView):
model = Task
context_object_name = 'task'
fields = ('name', 'description', 'is_done')
template_name = 'todo/task_create.html'
Мы создали класс с именем TaskCreateView
и унаследовали от него CreateView
. Тем самым мы получили много функциональности, почти без кода. Теперь нам просто нужно установить следующие атрибуты:
model
определяет, с какой моделью Django работает представление.fields
используется Django для создания формы (альтернативно, мы могли бы предоставитьform_class
).template_name
определяет, какой шаблон использовать (по умолчанию/<app_name>/<model_name>_form.html
).context_object_name
определяет контекстный ключ, под которым экземпляр модели передается шаблону (по умолчаниюobject
).success_url
определяет, куда пользователь будет перенаправлен при успехе (альтернативно, вы можете установитьget_absolute_url
в вашей модели).
Для получения дополнительной информации об общих CBV обратитесь к официальной документации.
Как вы, вероятно, догадались, за кулисами с общими CBV происходит гораздо больше магии. Они могут быть запутанными даже для опытных разработчиков Django. Однако, когда вы разберетесь с ними, вы, вероятно, почувствуете себя волшебником.
Вам следует использовать общие CBV для представлений, которые выполняют общие задачи (например, CRUD-операции). Если вашему представлению нужно сделать что-то, что не покрывается CBV, используйте миксины или представление на основе функций.
Встроенные типы CBV в Django
На момент написания статьи Django поставляется с приличным количеством общих CBV, которые мы можем разделить на три категории:
Generic Display Views | Generic Editing Views | Generic Date-based Views |
---|---|---|
Designed to display data. |
Provide a foundation for editing content. |
Allow in-depth displaying of date-based data. |
В ближайшее время мы рассмотрим практические примеры их использования.
View Mixins
Каждое типовое представление создано для решения одной задачи (например, отображение информации, создание/обновление чего-либо). Если ваше представление должно делать что-то большее, вы можете создать свое представление, используя миксины. Миксины - это классы, которые предлагают отдельные функциональные возможности, и их можно комбинировать для решения практически любой задачи.
Вы также можете выбрать в качестве основы общий CBV, а затем включить в него дополнительные миксины.
Даже общие CBV в Django состоят из миксинов. Давайте посмотрим на диаграмму CreateView
:
Мы видим, что он использует ряд миксинов, таких как ContextMixin
, SingleObjectMixin
и FormMixin
. Они имеют удобные для программистов названия, поэтому вы должны иметь общее представление о том, что делает каждый из них, исходя из их названия.
Миксины требуют много времени для освоения и часто могут быть запутанными. Если вы только начинаете работать с миксинами, я бы посоветовал вам начать с чтения Using mixins with class-based views.
Todo App (используя общие CBVs Django)
Теперь давайте перепишем приложение todo в последний раз, используя общие представления Django, основанные на классах:
# todo/views.py
from django.views.generic import ListView, DetailView, DeleteView, UpdateView, CreateView
class TaskListView(ListView):
model = Task
context_object_name = 'tasks'
class TaskCreateView(CreateView):
model = Task
context_object_name = 'task'
fields = ('name', 'description', 'is_done')
template_name = 'todo/task_create.html'
class TaskDetailView(DetailView):
model = Task
context_object_name = 'task'
class TaskUpdateView(UpdateView):
model = Task
context_object_name = 'task'
fields = ('name', 'description', 'is_done')
template_name = 'todo/task_update.html'
class TaskDeleteView(DeleteView):
model = Task
context_object_name = 'task'
success_url = '/'
Вы можете получить полный исходный код на GitHub.
Используя общие CBVs Django, мы разделили наш код пополам. В дальнейшем код становится намного чище и легче поддерживать, но его может быть намного сложнее читать и понимать, если вы новичок в общих CBV.
Заключение
Один тип зрения не лучше другого. Все зависит от ситуации и личных предпочтений. Иногда лучше FBV, а иногда лучше CBV. Постарайтесь запомнить их плюсы и минусы, чтобы принять правильное решение при написании конкретного вида. Если вы все еще не совсем уверены, когда и что использовать, вы можете немного попрактиковаться, преобразуя FBV в CBV и наоборот. Кроме того, вы можете использовать эту блок-схему:
Лично я предпочитаю FBV для небольших проектов (которые невозможно решить с помощью общих CBV) и выбираю CBV при работе с большими кодовыми базами.
Вернуться на верх