How to get page view count on wagtail/django?

Viewed 942

I am planning to create a page within a blog website where it arranges and displays the all blog posts based on page view count. Not sure how to pull it off.

models.py

class BlogPost(Page):
    date = models.DateField(verbose_name="Post date")
    categories = ParentalManyToManyField("blog.BlogCategory", blank=True)
    tags = ClusterTaggableManager(through="blog.BlogPageTag", blank=True)
    body = RichTextField(blank=False)

    main_image = models.ForeignKey(
        'wagtailimages.Image', 
        null=True,
        blank=False,
        on_delete=models.SET_NULL, 
        related_name='+')

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        blogposts = self.get_siblings().live().public().order_by('-first_published_at')
        context['blogposts'] = blogposts
        return context

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
        FieldPanel('tags'),
        ImageChooserPanel('main_image'),
        FieldPanel('body', classname="full"),
    ]
3 Answers

As mentioned in another answer, you can add view_count field to your model. Then you can leverage Wagtail's hooks to increment the value in database.

New field in the model:

class BlogPage(Page):
    view_count = models.PositiveBigIntegerField(default=0, db_index=True)

Register the before_serve_page hook:

@hooks.register("before_serve_page")
def increment_view_count(page, request, serve_args, serve_kwargs):
    if page.specific_class == BlogPost:
        BlogPost.objects.filter(pk=page.pk).update(view_count=F('view_count') + 1)

In this approach database takes responsibility to correctly increment view_count so you don't have to worry about locking and incrementing the value yourself.

If you wanted to count views in a slightly more featured way you could use the django-hitcount package.

Your wagtail_hooks.py file would then become:

from hitcount.models import HitCount
from hitcount.views import HitCountMixin

from wagtail.core import hooks

from home.models import BlogPage

@hooks.register("before_serve_page")
def increment_view_count(page, request, serve_args, serve_kwargs):
    if page.specific_class == BlogPage:
        hit_count = HitCount.objects.get_for_object(page)
        hit_count_response = HitCountMixin.hit_count(request, hit_count)

You need to add HitCountMixin to your Page definition, i.e.

from hitcount.models import HitCountMixin

class BlogPage(Page, HitCountMixin):

This allows you to count hits but avoid duplication from the same IP, to reset hits with a management command, and to set the 'active' period for a page.

You will also need to pip install django-hitcount and add it to your INSTALLED_APPS in settings.py.

As a naive solution, you could add a field view_count to your BlogPage model, this would be a IntegerField.

You will need a way to update this value every time the page is served, you can add some logic to the get_context method you have already used. However, the serve method would be more appropriate, be sure to check that the serve is not being called as a preview by checking request.is_preview.

In regards to querying (ordering by this view_count), this can be done by updating your query.

blogposts = self.get_siblings().live().public().order_by('-view_count')

You can make this new field visible, but not easily editable (client side validation only) by adding it via a FieldPanel with a custom widget. Using the Wagtail settings_panels this can be made available in the non content panel.

Example Model

models.py
from django.db import models
from django import forms

from wagtail.core.models import Page


class BlogPage(Page):
    # ... other fields

    view_count = models.IntegerField(blank=False, default=0)

    content_panels = Page.content_panels + [
        # ... existing content panels
    ]

    settings_panels = Page.settings_panels + [
        FieldPanel(
            'view_count',
            #  show the view count in the settings tab but do not allow it to be edited
            #  note: can be easily edited by savvy users, but only if they can also access admin
            widget=forms.NumberInput(attrs={'disabled': 'disabled', 'readonly': 'readonly'})
        )
    ]

Please note: this implementation does not take into consideration any other case where serve may be called but does not mean 'a unique user saw my blog post'. You may want to investigate a proper Django analytics solution or some kind of integration with client side analytics such as Google Analytics or Heap.

Related