Chipmunk
Chipmunk

Reputation: 23

Best way to monitor number of views on a blog post in Django

I am working on a blog webapp and I would like to monitor the number of views a post has. I have decided to use the post detail view to count. And to keep the counter from incrementing for a particular user, I am going to allow only logged users to count and a user can count only once.

this is my post model

class Post(models.Model):
    title = models.CharField(max_length=150)
    content = models.TextField(max_length=3000, null=True, blank=True)
    date_posted = models.DateTimeField(default=timezone.now)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    viewers = models.TextField(default="", null=True, blank=True)
    numViews = models.IntegerField(default=0)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})

I am basically using a field 'viewers' to track users already counted and 'numviews' as the counter.

My views.py function

class PostDetailView(DetailView):
    model = Post

    def get(self, request, *args, **kwargs):
        # increment post views whenever request is made
        postObject = self.get_object()
        user = request.user

        if postObject.viewers == None:
            postObject.viewers = ""
            postObject.save()

        if user.is_authenticated and user.username not in postObject.viewers:
            # increment 
            postObject.numViews += 1
            postObject.save()
            # add username to viewers list
            postObject.viewers+=user.username
            postObject.save()
            
        return super().get(request, *args, **kwargs)

In the views function, I am checking if the username is already appended to the viewers string, else append it and increment the counter. Is there a more efficient way of doing this? In the case the blog gets thousands of views, it is going to be a very long string of usernames.

Upvotes: 2

Views: 1530

Answers (4)

manish jangid
manish jangid

Reputation: 1

How to get and update view count in blog Api using post method. This is very best and Simple

this is my model.py code

    class Blog(BaseModel,SlugModel,SeoModel):
        author = 
       models.ForeignKey(User,null=True,on_delete=models.SET_NULL,blank=True)
        author_name=models.CharField(max_length=255,null=True,blank=True)
        title = models.CharField(max_length=250)
        image = models.ImageField(upload_to=blog_image_directory)
        views_count = models.IntegerField(default=0)

This is my views.py code 

from django.db.models import F

class BlogCountViewset(APIView):
    def post(self,request,*args,**kwargs):
        slug=kwargs.get('slug')
        blog=Blog.objects.filter(slug=slug)
        if blog.exists():
            blog.update(views_count=F('views_count')+1)
            return Response({"views_count":blog.last().views_count},status=status.HTTP_200_OK)
        return Response({"message":"blog does not exists"},status=status.HTTP_404_NOT_FOUND)

This is my url code

path("blog-view-count/<slug>",
         BlogCountViewset.as_view(),
         name="blog-view-count"),  

This is only required code

Upvotes: 0

benAv
benAv

Reputation: 55

A slightly more concise way of counting views would be to use the .Count method. This would allow the count to take place in the database and not in memory.

from django.db.models import Count

Post(models.Model):
# ...
viewers = models.ManyToManyField(
    settings.AUTH_USER_MODEL,
    related_name='viewed_posts'
    editable=False
)

@property
def num_views(self):
    return self.views.count()

and then when adding the user to the views use.

if request.user.is_authenticated:
   post.viewers.add(request.user)

Upvotes: 0

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

In the case the blog gets thousands of views, it is going to be a very long string of usernames.

It will not only be inefficient, but also can result in the wrong result. If you have three users foo, bar and foobar, then if foo visits the page, and bar visits the page, then if foobar visits the page, their view will not be taken into account.

An other problem is that there can be race conditions here: if two users visit the same post approximately at the same time, it is possible that the database ends up only updating the object for one user, and thus it is like the other user never visited the page.

Normally you determine who has viewed the post through a ManyToManyField [Django-doc]:

from django.conf import settings

class Post(models.Model):
    title = models.CharField(max_length=150)
    content = models.TextField(max_length=3000, null=True, blank=True)
    date_posted = models.DateTimeField(default=timezone.now)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    viewers = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='viewed_posts'
        editable=False
    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})

Then we can implement the view as:

from django.db.models import Count

class PostDetailView(DetailView):
    model = Post
    queryset = Post.objects.annotate(
        num_views=Count('viewers')
    )

    def get_context_data(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            __, created = Post.viewers.through.objects.get_or_create(
                post=self.object,
                user=self.request.user
            )
            if created:
                self.object.num_views += 1
        return super().get_context_data(*args, **kwargs)

Upvotes: 4

Chipmunk
Chipmunk

Reputation: 23

I modified my code with @Willem Van Onsem and it seems to be working correctly. only corection made was self.user -> self.request.user in the post detail view . Also the number of viewers can be access ina post object in html template by ((post.viewers.count}} and in the detail view template as {{object.num_views}}

Upvotes: 0

Related Questions