Hello I am making django app, i would like to add visit counter feature but seperate for each item. I think it would be a nice feature.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.filter(item=self.object)
context['form'] = CommentCreationForm()
num_visits = self.request.session.get('num_visits', 0)
self.request.session['num_visits'] = num_visits + 1
context['visits'] = num_visits
return context
Currently what you are implementing is a counter per session. Indeed this means that if a user starts a session on your page, first they will see zero, later one, and so on. But that will only count how many times that user visits a page in the session. Visits from other users will not make any difference.
If you want to keep track of the total number of visits per user, you will need to let the visiting data persist. You can do that with an extra model that for example each time creates a new record when a (registered) user visits a page, or we can work with a simple counter. If we want to prevent counting the same user multiple times if they visit the same object multiple times, it makes more sense to work with a ManyToManyField
to the user.
IntegerField
A simple implementation that simply counts the number of visits, and thus count the same user twice if that user visits the object twice can be implemented with an extra IntegerField
to count the number of visits, this looks like. We can write an abstract model for that:
class WithVisitCounter(models.Model):
visits = models.IntegerField(editable=False, default=0)
class Meta:
abstract = True
and then let the model inherit from this:
class BlogPost(WithVisitCounter, models.Model):
# ⋮
then we can make a mixin WithVisitCounterMixin
:
from django.views.generic.detail import SingleObjectMixin
class WithVisitCounterMixin(SingleObjectMixin):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
old_visit = obj.visits
obj.visits = F('visits') + 1
obj.save(updated_fields=['visits'])
obj.visits = old_visit + 1
return obj
def get_context_data(self, *args, **kwargs):
cd = super().get_context_data(*args, **kwargs)
cd['visits'] = self.object.visits
return cd
Then we can use this Mixin
in all views that have a SingleObjectMixin
like a DetailView
and an UpdateView
:
class BlogPostDetailView(WithVisitCounterMixin, DetailView):
# ⋮
This will pass the number of visitors as visits
to the context data, so you can render this with {{ visits }}
, or with {{ object.visits }}
if the object is passed to the template.
ManyToManyField
to the user modelThe first option does not take into account users that visit the same object multiple times. This means that the same user can visit the page twenty times, and that will be seen as twenty independent visits.
We can in that case define an abstract model that will add links to users, with:
from django.conf import settings
class WithVisitCounter(models.Model):
visitors = models.ManyToManyField(
to=settings.AUTH_USER_MODEL,
related_name='%(model_name)s_visits'
)
class Meta:
abstract = True
class BlogPost(WithVisitCounter, models.Model):
# ⋮
Then we can define a WithVisitCounterMixin
just like we did for the first option. In this case we will add a link from the object to the logged in user:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import SingleObjectMixin
class WithVisitCounterMixin(SingleObjectMixin):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
obj.visitors.add(self.request.user)
return obj
def get_context_data(self, *args, **kwargs):
cd = super().get_context_data(*args, **kwargs)
cd['visits'] = self.object.visitors.count()
return cd
for that single object, we can then get the visitors by counting the number of records for the .visitors
of the self.object
.
We can thus use that mixin in a DetailView
or UpdateView
as well:
class BlogPostDetailView(WithVisitCounterMixin, DetailView):
# ⋮
We can then again use {{ visits }}
for the number of visitors for that item.