Теория

Декоратор login_required

Применим декораторы на практике.
Некоторые страницы сайта должны быть доступны только залогиненным пользователям. Например, страница добавления поста, подписки на автора или форма отправки комментария. Можно делать проверку внутри каждой view-функции, приблизительно так:
Скопировать кодPYTHON
def only_user_view(request): if not request.user.is_authenticated(): return response.redirect( # если пользователь не авторизован - отправляем его на страницу логина to_page = '/auth/login', # в GET-параметре передадим переменную next, в ней сохраним адрес текущей страницы, # чтобы вернуть на неё пользователя после авторизации params = {'next': current_page) ) # остальной полезный код функции
И такая проверка должна быть в каждой view-функции, работающей с пользовательскими страницами.
На сайте будет десяток view-функций для пользовательских страниц, в каждой из них придётся повторять этот код. Это будет загромождать проект, а при необходимости внести изменения придётся править десяток функций.
Но можно один раз написать декоратор, а потом одной строкой в коде «оборачивать» в него любую view-функцию:
Скопировать кодPYTHON
def user_only(func): def check_user(request, *args, **kwargs): # Мы знаем, что у view-функций первый аргумент всегда request if not request.user.is_authenticated(): return response.redirect( to_page = '/auth/login', params = {'next': current_page) ) func(request, *args, **kwargs) return check_user
Теперь нам надо лишь добавить декоратор к view-функциям:
Скопировать кодPYTHON
from .utils import user_only @user_only def only_user_view(request): # Только полезный код!
В Django уже есть встроенный декоратор для таких случаев, он называется login_required. С его помощью можно закрыть страницы от неавторизованных пользователей.
Этот декоратор устроен несколько сложнее, чем наш: он умеет принимать параметры, работать с Class Based Views и различать, оборачивает ли он функцию или метод класса.
Декоратор login_required импортируется из django.contrib.auth.decorators:
Скопировать кодPYTHON
from django.contrib.auth.decorators import login_required @login_required def my_view(request): ...

Декоратор @login_required и шаблон login.html

Измените шаблон users/templates/registration/login.html — добавьте блок с проверкой переменной next. Если незалогиненный пользователь обратится к странице проекта, доступной только для авторизованных пользователей, мы направим его на страницу авторизации. Если он авторизуется — мы вернём его на ту страницу, с которой он пришёл. Адрес этой страницы будет передан в GET-параметре в переменной next: /auth/login?next=any-page-url
Текст из блока {% if next %} будет отображён именно при переадресации пользователя на страницу авторизации.
Скопировать кодHTML
{% extends "base.html" %} {% block title %}Войти{% endblock %} {% block content %} {% load user_filters %} <div class="row justify-content-center"> <div class="col-md-8 p-5"> <div class="card"> <div class="card-header">Войти на сайт</div> <div class="card-body"> {% if form.errors %} <div class="alert alert-danger" role="alert"> Имя пользоваетеля и пароль не совпадают. Введите правильные данные. </div> {% endif %} {% if next %} <div class="alert alert-info" role="alert"> Вы обратились к странице, доступ к которой возможен только для залогиненных пользователей.<br> Пожалуйста, авторизуйтесь. </div> {% else %} <div class="alert alert-info" role="alert"> Пожалуйста, авторизуйтесь. </div> {% endif %} <form method="post" action="{% url 'login' %}"> {% csrf_token %} <input type="hidden" name="next" value="{{ next }}"> <div class="form-group row"> <label for="{{ form.username.id_for_label }}" class="col-md-4 col-form-label text-md-right">Имя пользователя</label> <div class="col-md-6"> {{ form.username|addclass:"form-control" }} </div> </div> <div class="form-group row"> <label for="{{ form.password.id_for_label }}" class="col-md-4 col-form-label text-md-right">Пароль</label> <div class="col-md-6"> {{ form.password|addclass:"form-control" }} </div> </div> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> Войти </button> <a href="{% url 'password_reset' %}" class="btn btn-link"> Забыли пароль? </a> </div> </form> </div> <!-- card body --> </div> <!-- card --> </div> <!-- col --> </div> <!-- row --> {% endblock %}
Теперь view-функции тех страниц, куда разрешён доступ только под логином (это, например, страница создания нового поста), можно «обернуть» декоратором @login_required — и автоматически будет проводиться проверка и переадресация неавторизованных пользователей на страницу логина.
Задача
Напишите декоратор, оптимизирующий работу декорируемой функции. Декоратор должен сохранять результат работы функции на ближайшие 3 запуска и вместо выполнения функции возвращать сохранённый результат.
Подсказка
Создайте в декораторе переменную-кеш, сохраните в ней результат выполнения декорируемой функции. Создайте в декораторе переменную, хранящую счётчик запросов. Пока значение счётчика ниже предельного — отдавайте результат, сохранённый в кеше. Когда число запросов к функции превысит предел и пора будет снова высчитывать результат выполнения функции — сбросьте счётчик, выполните декорируемую функцию и заново сохраните результат её выполнения в переменную-кеш. В предыдущем уроке мы рассказывали, как создавать переменные в декораторах, этот пример пригодится и здесь.
Код
Результат