Как избежать повторения кода в представлениях в Django?

У меня есть несколько представлений на основе функций в проекте django, и я заметил, что в них есть повторяющийся код, фактически, все они делают одно и то же:

def my_view(request):
   form = MyForm(request.POST or None)
   if requqest.POST:
      if form.is_valid():
         do_somethin()
         and_something_else()
         return redirect('another:page')
   return render(request, 'my/tempalate.html', {'form': form})

Единственное, что отличается - это url, на который перенаправляется пользователь в случае успешной валидации формы, шаблон и, возможно, форма в будущем.

Хорошо ли использовать что-то вроде этого, чтобы избежать повторения?:

def a_view(request, success_redirect_url, template):
    form = MyForm(request.POST or None)
    if request.POST:
        if form.is_valid():
           do_something()
           and_something_else()
           return redirect(success_redirect_url)
    return render(request, template, {'form': form})

и затем повторно использовать его в других представлениях, которые имеют повторяющийся код? Например:

def my_view1(request, url='another_page', template='my/template.html'):
    return a_view(request, url, template)

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

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

Ваш вид, например, очень похож на FormView [Django-doc]. Действительно, мы можем определить представление с помощью:

# app_name/views.py

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

class MyView(FormView):
    template_name = 'my/template.html'
    success_url = reverse_lazy('another_page')
    form_class = MyForm

    def form_valid(self, form):
        do_somethin()
        and_something_else()
        return super().form_valid(form)

Здесь представление на основе класса реализовало обработчик POST-запроса, который сначала создаст форму, затем проверит, является ли эта форма действительной, и если она недействительна, то перерисует шаблон с формой, которая теперь содержит ошибки. Это минимизирует количество кода, который нужно написать для обработки простой формы.

Такие представления также четко объясняют, что они делают. Например, представление CreateView своим названием объясняет, что оно используется для создания новых объектов, поэтому оно будет вызывать метод .save() формы, на которой оно работает. А DeleteView, с другой стороны, дает понять, что POST-запрос к этому представлению удалит объект.

Вы также можете переопределить атрибуты в urls.py, указав значение в вызове .as_view(…) [Django-doc]:

# app_name/urls.py

from app_name.views import MyView
from django.urls import path

urlpatterns = [
    # ⋮,
    path('some/path/', MyView(template_name='other/template.html'), name='some-name'),
    # ⋮
]

Таким образом, функция на основе класса действует как шаблон метода

Декораторы

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

Например, декоратор @require_http_methods(…) [Django-doc] [GitHub] сначала проверяет, является ли метод одним из перечисленных. Это реализовано следующим образом:

def require_http_methods(request_method_list):
    # ⋮
    def decorator(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            if request.method not in request_method_list:
                response = HttpResponseNotAllowed(request_method_list)
                log_response(
                    'Method Not Allowed (%s): %s', request.method, request.path,
                    response=response,
                    request=request,
                )
                return response
            return func(request, *args, **kwargs)
        return inner
    return decorator

Здесь декоратор проверяет, является ли request.method членом request_method_list. Если это не так, он вернет ответ HTTP 405 и укажет, что этот метод не разрешен.

Хотя Django предлагает множество декораторов, большинство декораторов имеют аналог mixin для представлений на основе классов, а некоторые реализованы уже в классе View. Например, если View не содержит метода get, то он вернет ответ HTTP 405, поэтому здесь required_http_method не нужен как миксин/декоратор для представления на основе класса.

Скелетные функции

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

Например, если мы хотим сделать функцию, которая рендерит шаблон, мы можем работать с представлением, которое выглядит следующим образом:

from django.shortcuts import render

def render_some_template(request, parameter, context_generator, template='our/template.html'):
    context = context_generator()
    return render(request, template, context)

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

Хотя представление на основе функций может работать со скелетной функцией, оно обычно менее расширяемо, чем аналог на основе классов. Встроенные приложения Django (те, что определены в модуле django.contrib) переходят в основном к представлениям на основе классов, поскольку их легче расширять.

Вы можете сделать намного лучше, если просто дадите имя любой кнопке, которая обедает этой функцией:

<button name='lukaku'>
<button name='ronaldo'>

затем в представлении проверьте наличие имени

if form.get('lukaku'):
    do something
if form.get('ronaldo'):
    do other thing

и так далее. это может быть безгранично

Вернуться на верх