Evgenii Legotckoi
Evgenii LegotckoiMarch 19, 2023, 8:56 p.m.

Django - Tutorial 061. Adding a Unique View Count

At the very beginning of creating articles and questions on the forum, I added a simple counter of views on this content.
This counter was an ordinary field of integer type and each time a page was requested it was incremented by one.
But not so long ago, I replaced this counter with a counter model for registering unique views. I just did it because I think it's prettier.

The counter counts unique visitors either by IP address, if the user is not authorized on the site, or by user account, if this user is authorized on the site.

So let's learn how to do it.

Viewer model

Responsible for counting unique users who viewed the site.

class Viewer(models.Model):
    ipaddress = models.GenericIPAddressField("IP address", blank=True, null=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True)

As you can see, there are only two fields in this model, the first is the IP Address, and the second is the foreign key to the user model

Adding a counter to the content model

And now let's add a counter to some site content model, for example, to the article model Article

class Article(models.Model):
    viewers = models.ManyToManyField(Viewer)

    # Some another code

Adding code is done as a Many-To-Many relationship. Since the unique visitor in this case will be one, but he can view many other articles. At the same time, the article can be viewed by many other users.

How to increase the content view counter

Most likely, some view class or view function will be responsible for displaying articles on your site. Personally, I prefer to use modern Class Based View in Django.

Therefore, I will write a mixin that can be used with DetailView to register unique views of content on the site.

class CountViewerMixin:

    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)
        if hasattr(self.object, 'viewers'):
            viewer, created = Viewer.objects.get_or_create(
                ipaddress=None if request.user.is_authenticated else get_client_ip(request),
                user=request.user if request.user.is_authenticated else None

            if self.object.viewers.filter(id=viewer.id).count() == 0:

        return response

It can be seen from the code that the mixin overrides the get method, checks that the content object exists in the View and only after that gets a unique visitor, and after checking that the visitor does not yet exist among the visitors who have viewed the content, adds it to the content views.

Thus, the content object itself is not modified, unlike the counter in the form of an integer field in the content model. What can actually complicate the code is if you have content model save method overrides or you have written receiver functions to handle save signals from the model. In the case of the Viewer counter, which is presented in this article, signals such as post_save on the content model simply do not work.

In the Content View, the mixin connection will look like this:

class ArticleView(CountViewerMixin, DetailView):
    # some another code

Usage in template

Inside the template, you can get the number of views as follows

{{ object.viewers.count }}


The mixin also has a function to get an ip address from an HTTP request. I have already described this special function for getting the user's ip address from a request in one of the articles .

And in my latest revision for Django 3, it looks like this.

def get_client_ip(request):
    Get client ip address from HTTP request

    :param request: HTTP request
    :return: IP Address
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    return x_forwarded_for.split(',')[-1].strip() if x_forwarded_for else request.META.get('REMOTE_ADDR')


Such a counter will make it easier to maintain the code if you have reactions to saving content, and will also reduce the impact of cheats on content counters.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!


Only authorized users can post comments.
Please, Log in or Sign up

C++ - Test 001. The first program and data types

  • Result:53points,
  • Rating points-4

C++ - Тест 003. Условия и циклы

  • Result:57points,
  • Rating points-2

C++ - Test 005. Structures and Classes

  • Result:100points,
  • Rating points10
Last comments
JonnyJoMay 25, 2023, 2:24 p.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Евгений, благодарю!
Evgenii Legotckoi
Evgenii LegotckoiMay 25, 2023, 4:49 a.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Код на строчка 184-198 вызывает перерисовку области на каждый 4-й такт счётчика. По той логике не нужно перерисовывать объект постоянно, достаточно реже, чем выполняется игровой слот. А слот вып…
JonnyJoMay 21, 2023, 10:49 a.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Евгений, благодарю! Всё равно не совсем понимаю :( Если муха двигает ножками только при нажатии клавиш перемещение, то что, собственно, делает код со строк 184-198 в triangle.cpp? В этих строчка…
Evgenii Legotckoi
Evgenii LegotckoiMay 21, 2023, 5:57 a.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Добрый день. slotGameTimer срабатывает по таймеру и при каждой сработке countForSteps увеличивается на 1, это не зависит от нажатия клавиш, нажатая клавиша лишь определяет положение ножек, котор…
JonnyJoMay 20, 2023, 11:27 a.m.
How to make game using Qt - Lesson 2. Animation game hero (2D) Евгений, здравствуйте! Подскажите, а почему при нажатии одной клавиши переменная countForSteps увеличивается не на 1, на 4, ведь одно действие даёт увеличение этой переменной только на единицу? …
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiApril 16, 2023, 4:07 a.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Да, это возможно. Но подобные вещи лучше запускать через celery. То есть drf принимает команду, и после этого регистрирует задачу в celery, котроый уже асинхронно всё это выполняет. В противном …
Алексей БобровDec. 14, 2021, 7:03 p.m.
Sorting the added QML elements in the ListModel I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …
Evgenii Legotckoi
Evgenii LegotckoiMarch 29, 2023, 4:11 a.m.
Замена поля ManyToMany Картинки точно нужно хранить в медиа директории на сервере, а для обращения использовать ImageField. Который будет хранить только путь к изображению на сервере. Хранить изображения в базе данных…
Evgenii Legotckoi
Evgenii LegotckoiApril 24, 2023, 3:22 a.m.
Пакеты данных между сервером и клиентами Привет. Если классы имеют что-то общее в полях, а также общую идеологию и их можно вписать в иерархию наследования, то в первую очередь переписать так, чтобы один базовый класс объединял в…

Follow us in social networks