Django. Увеличение количества просмотров объекта без изменения его поля updated_at

У меня следующая модель:

class Announcement(models.Model):
    ...
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    views = models.PositiveIntegerField(default=0, editable=False)

Мое мнение:

class AnnouncementDetailView(DetailView):
    model = Announcement
    context_object_name = 'announcement'
    template_name = 'web/index.html'

    def get(self, *args, **kwargs):
        announcement = get_object_or_404(Announcement, id=kwargs['id'])
        announcement.views += 1
        announcement.save()
        return super().get(self, *args, **kwargs)

Проблемы следующие:

  1. Я знаю о выражении F, я буду использовать его ниже, но я хочу знать другой способ обновления поля и не обновлять поле updated_at.
  2. Я хочу сохранить изменения поля модели views, но каждый раз, когда я сохраняю его, оно устанавливает новое значение в поле updated_at. Это означает, что каждый раз, когда я обновляю страницу, она показывает текущее время в поле updated_at, что очень плохо.

Я изменил код моего представления следующим образом:

...
def get(self, *args, **kwargs):
    Announcement.objects.filter(id=kwargs['id']).update(views=F('views') + 1)
    return super().get(self, *args, **kwargs)

Теперь все работает нормально, но у меня есть несколько вопросов. Как сохранить новое значение поля views без вызова метода save из """"Django"""""? Как далеко я могу зайти с этим. Какова лучшая практика изменения некоторых полей модели без вызова метода save?

Простым способом сделать это может быть указание update_fields в .save(…) методе [Django-doc]:

class AnnouncementDetailView(DetailView):
    model = Announcement
    context_object_name = 'announcement'
    template_name = 'web/index.html'

    def get(self, *args, **kwargs):
        try:
            return super().get(self, *args, **kwargs)
        finally:
            object = getattr(self, 'object', None)
            if object:
                object.views = F('views') + 1
                object.save(update_fields=('views',))

Однако лучше работать с F-выражением, чтобы избежать race-условий.

Использование:

Announcement.objects.filter(id=kwargs['id']).update(views=F('views') + 1)

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

UPDATE appname_announcement
SET view = view + 1
WHERE id = id

Если вам не нужно загружать объект в память, это будет более эффективно, так как он совершает один поход в базу данных.

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