Объясните работу функции, пожалуйста!

Есть задача добавить возможность просмотров поста(с возможностью накрутки) в учебных целях. В интернете нашел такой код:

models.py:

class PageHit(models.Model):
    url = models.CharField(max_length=1000, unique=True)
    count = models.PositiveIntegerField(default=0)

views.py:

from django.db.models import F
from functools import wraps
from django.db import transaction

def counted(f):
    @wraps(f)
    def decorator(request, *args, **kwargs):
        with transaction.atomic():
            counter, created = PageHit.objects.get_or_create(url=request.path)
            counter.count = F('count') + 1
            counter.save()
        return f(request, *args, **kwargs)
    return decorator

Этим декоратором оборачивается view для показа поста.

Код работает исправно, и при нажатии на ссылку для перехода на пост количество просмотров действительно увеличивается, но я не понимаю как работает эта функция. Пожалуйста, объясните.

Каждый раз, перед тем как вызывать данный view, обернутый этим декоратором, в PageHit с данным url инкрементируется count:

def counted(f):
    @wraps(f)
    def decorator(request, *args, **kwargs):
        # делаем транзакцию атомической (если что-то зафейлится, изменения ролбэкнутся)
        with transaction.atomic():

             # получаем объект PageHit с данным url (если его еще не существует, создаем)
            counter, created = PageHit.objects.get_or_create(url=request.path)

            # инкрементируем его counter (counter.count += 1 тоже бы сработало)
            counter.count = F('count') + 1

            # сохраняем
            counter.save()

        # наконец вызываем сам view который декорировали
        return f(request, *args, **kwargs)
    return decorator

Советую почитать, как работают декораторы. Вкратце на ваши вопросы:

  1. f - это сама функция, которую мы оборачиваем, в данном случае наш view (поэтому в конце мы ее вызываем). decorator здесь довольно сбивающее с толку название, т.к. это не декоратор. counted в данном примере является декоратором, а то что здесь названо decorator обычно называют wrapper, это функция которая подменяет (оборачивает) декарируемую функцию:

    @counted
    def view(...):
        ...
    

    эквивалентно:

    def view(...):
        ...
    view = counted(view)
    

    F - вспомагательный метод django ORM, для более "низкоуровневой" коммуникации с ДБ через SQL. Как и писал выше, можно использовать более простой метод с +=.

  2. wraps используется для того, чтобы сохранить оригинальноее имя (view.__name__) и оригинальную документацию (view.__doc__) декарируемой функции. Иначе ее имя заменится (в данном случае) на decorator, а документация пропадет, т.к. у decorator нет док-строки.

  3. get_or_create возвращает кортеж из двух значений (object, created) - созданный или найденный объект и булеан был объект создан или найден уже существующий в ДБ по URL. Можно использовать _ для неиспользованных переменных:

counter, _ = PageHit.objects.get_or_create(url=request.path)

но created более наглядно (IMHO).

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