Reputation: 23
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
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
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
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
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