Home > OS >  Django. Increment views count of object without affecting its updated_at field
Django. Increment views count of object without affecting its updated_at field

Time:11-13

I have the following model:

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)

my view:

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)

The problems are:

  1. I know about the F expression, I'll use it down below, but I want to know another ways of updating field and not updating the updated_at field.
  2. I want to save the changes of model's views field but everytime I save it, it sets the new value to updated_at field. It means everytime I refresh the page it shows the current time on updated_at field, which is very bad.

I have changed my view's code to:

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

Now it works fine but I have several questions. How it saves the new value of views field without calling the save method of """"Django"""""? How far can I go with it. What is the best practices of changing some fields of model without hitting the save method?

CodePudding user response:

An easy way to do this, might be to specify update_fields in the .save(…) method [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',))

It is however better to work with an F-expression to avoid race-conditions.

Making use of:

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

will let the database make a query that looks like:

UPDATE appname_announcement
SET view = view   1
WHERE id = id

If you thus do not have to load the object in memory, this will be more efficient, since it makes a single roundtrip to the database.

  • Related