«Декораторы проверки» для Views

в 19:28, , рубрики: decorator, django, views, метки: , ,

Рассуждаем про декораторы

Каждый из нас не раз использовал декоратор login_required и скорее всего писал похожий декоратор(скажем для проверки пустая ли корзина). Давайте рассмотрим что делает данный декоратор:

«Декораторы проверки» для Views

Если посмотреть на это целиком то можно представить так:

«Декораторы проверки» для Views

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

Так как схема одна и та же, мы можем написать «заготовку» для декоратора:

def check_decorator(view=None,
                    condition_func = lambda request, *args, **kwargs: True,
                    false_func = lambda request, *args, **kwargs: HttpResponse()):
    '''
    Checks by :function:condition_func, if true go to :function: view,
    else go to :function:false_func
    :param condition_func: Should return Boolean value
    :type condition_func: function
    :param false_func: Should return object of :class:`http.HttpResponse'
    :return: :class:`http.HttpResponse`
    '''
    def decorator(view):
        @wraps(view)
        def wrapper(request, *args, **kwargs):
            if not condition_func(request, *args, **kwargs):
                return false_func(request, *args, **kwargs)
            return view(request, *args, **kwargs)
        return wrapper
    return decorator(view) if view else decorator

check_decorator — это базовая функция, которая принимает view, функцию условия(condition_func) и функцию, которая используется при отрицательном результате условия(false_func).

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

  1. Создать функции проверки is_anonymous
  2. Создать функцию, которая будет перекидывать в офис(office_redirect)
  3. C помощью functools.partial и наших функций(is_anonymous, office_redirect) создадим декоратор
from functools import partial

is_anonymous = lambda request, *args, **kwargs: request.user.is_anonymous()
office_redirect = lambda request, *args, **kwargs: redirect('office') # office - имя view в urls

should_be_anonymous = partial(check_decorator,
                              condition_func=is_anonymous,
                              false_func=office_redirect)

Думаю, что написать другую функцию условия или функцию, которая вызывается при не пройденном условии(false_func), у вас не составит труда.

А как же без redirect_url

Многие из нас привыкли, что можно указать куда будем пересылать пользователя, указывая redirect_url:

@login_required(redirect_url=reverse_lazy('my_view_name'))
def some_view(request, *args, **kwargs):
    pass

В данном случае у нас функция false_func всегда производит redirect, а мы должны указывать куда производить url.
Создадим данную функцию на основе нашей «заготовки»:

def redirect_decorator(view=None, redirect_url=None, **kwargs):
    '''
    Uses :function:`check_decorator` with false_func that returns
    :param:`redirect_url`
    '''
    assert not redirect_url is None, "Redirect_url should be setted"
    if 'false_handle_func' in kwargs:
        del kwargs['false_func']

    redirect_func = lambda request, *args, **kwargs: redirect(redirect_url)
    return check_decorator(view=view, false_func=redirect_func,
                           **kwargs)

В функции redirect_decorator мы проверяем передан ли нам redirect_url, и если нам передали
false_func — удаляем её. Затем возвращаем значение функции check_decorator.

Функцию should_be_anonymous можем переписать так:

should_be_anonymous = partial(redirect_decorator,
                              condition_func=is_anonymous)

И использовать в привычной нам форме:

@should_be_anonymouse(redirect_url=reverse_lazy('office'))
def some_view(request, *args, **kwargs):
     pass

Передадим сообщение

В django есть замечательный механизм сообщений django.messages, при помощи которого мы можем передать сообщения нашему пользователю.
Посмотрим как он работает:
Пишем во view:

from django.contrib import messages

def view(request, *args, **kwargs):
      ...
      messages.info("Hello World!") #Передаем сообщение польpователю
      ...

Показываем в шаблоне:

{% if messages %}
    <ul>
        {% for message in messages %}
            <li>{{ message }}</li>
        {% endfor %}
    </ul>
{% endif %}

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

def redirect_message_decorator(view=None, message=None, redirect_url=None,
                              **kwargs):
    '''
    Uses :function: c`check_decorator` with false_func thar returns
    :param:`redirect_url`
    '''
    assert not redirect_url is None, "Redirect_url should be setted"
    assert not message is None, "Message should be setted"
    if 'false_func' in kwargs:
        del kwargs['false_func']

    def redirect_func(request, *args, **kwargs):
        messages.info(request, message)
        return redirect(redirect_url)
    return check_decorator(view=view, false_func=redirect_func,
                           **kwargs)

Функция should_be_anonymous с сообщением выглядит так:

should_be_anonymouse = partial(redirect_message_decorator,
                               condition_func=is_anonymous)

И использование данного декоратора:

@should_be_anonymouse(message=u'Вам туда нельзя',
                      redirect_url=reverse_lazy('office'))
def some_view(request, *args, **kwargs):
     pass

Заключение

Для использование декоратора с CBV можно использовать этот сниппет
Статья не очень сложная, но для новичков, надеюсь, она будет интересна.
Всем приятного изучения/использования/разработки чудесного фреймворка django.

Автор: Zapix

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js