Track the number of "page views" or "hits" of an object? - django

I am sure that someone has a pluggable app (or tutorial) out there that approximates this, but I have having trouble finding it: I want to be able to track the number of "views" a particular object has (just like a question here on stackoverflow has a "view count").
If the user isn't logged in, I wouldn't mind attempting to place a cookie (or log an IP) so they can't inadvertently run up the view count by refreshing the page; and if a user is logged in, only allow them one "view" across sessions/browsers/IP addresses. I don't think I need it any fancier than that.
I figure the best way to do this is with Middleware that is decoupled from the various models I want to track and using an F expression (of sorts) -- other questions on StackOverflow have alluded to this (1), (2), (3).
But I wonder if this code exists out in the wild already -- because I am not the savviest coder and I'm sure someone could do it better. Smile.
Have you seen it?

I am not sure if it's in the best taste to answer my own question but, after a bit of work, I put together an app that solves the problems in earnest: django-hitcount.
You can read about how to use it at the documentation page.
The ideas for django-hitcount came came from both of my two original answers (Teebes -and- vikingosegundo), which really got me started thinking about the whole thing.
This is my first attempt at sharing a pluggable app with the community and hope someone else finds it useful. Thanks!

You should use the django built-in session framework, it already does a lot of this for you. I implemented this in the following way with a Q&A app where I wanted to track views:
in models.py:
class QuestionView(models.Model):
question = models.ForeignKey(Question, related_name='questionviews', on_delete=models.CASCADE)
ip = models.CharField(max_length=40)
session = models.CharField(max_length=40)
created = models.DateTimeField(default=datetime.datetime.now())
in views.py:
def record_view(request, question_id):
question = get_object_or_404(Question, pk=question_id)
if not QuestionView.objects.filter(
question=question,
session=request.session.session_key):
view = QuestionView(question=question,
ip=request.META['REMOTE_ADDR'],
created=datetime.datetime.now(),
session=request.session.session_key)
view.save()
return HttpResponse(u"%s" % QuestionView.objects.filter(question=question).count())
Vikingosegundo is probably right though that using content-type is probably the more reusable solution but definitely don't reinvent the wheel in terms of tracking sessions, Django already does that!
Last thing, you should probably have the view that records the hit be either called via Ajax or a css link so that search engines don't rev up your counts.
Hope that helps!

You could create a generic Hit model
class Hit(models.Model):
date = models.DateTimeField(auto_now=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
In your view.py you write this function:
def render_to_response_hit_count(request,template_path,keys,response):
for key in keys:
for i in response[key]:
Hit(content_object=i).save()
return render_to_response(template_path, response)
and the views that you are interested in return
return render_to_response_hit_count(request, 'map/list.html',['list',],
{
'list': l,
})
This approach gives you the power, not only to count the hit, but to filter the hit-history by time, contenttype and so on...
As the hit-table might be growing fast, you should think about a deletion strategy.

I know this question is an old one and also thornomad has put an app to solve the problem and inspire me with me solution. I would like to share this solution since I didn't find much information about this topic and it may help someone else.
My approach is to make a generic model can be used with any view based on the view path (url).
models.py
class UrlHit(models.Model):
url = models.URLField()
hits = models.PositiveIntegerField(default=0)
def __str__(self):
return str(self.url)
def increase(self):
self.hits += 1
self.save()
class HitCount(models.Model):
url_hit = models.ForeignKey(UrlHit, editable=False, on_delete=models.CASCADE)
ip = models.CharField(max_length=40)
session = models.CharField(max_length=40)
date = models.DateTimeField(auto_now=True)
views.py
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def hit_count(request):
if not request.session.session_key:
request.session.save()
s_key = request.session.session_key
ip = get_client_ip(request)
url, url_created = UrlHit.objects.get_or_create(url=request.path)
if url_created:
track, created = HitCount.objects.get_or_create(url_hit=url, ip=ip, session=s_key)
if created:
url.increase()
request.session[ip] = ip
request.session[request.path] = request.path
else:
if ip and request.path not in request.session:
track, created = HitCount.objects.get_or_create(url_hit=url, ip=ip, session=s_key)
if created:
url.increase()
request.session[ip] = ip
request.session[request.path] = request.path
return url.hits

I did this by creating a model PageViews and making a column "Hits" in it. Every time when Homepage url is hit. I increment the first and only row of column Hit and render it to the template. Here how it looks.
Views.py
def Home(request):
if(PageView.objects.count()<=0):
x=PageView.objects.create()
x.save()
else:
x=PageView.objects.all()[0]
x.hits=x.hits+1
x.save()
context={'page':x.hits}
return render(request,'home.html',context=context)
Models.py
class PageView(models.Model):
hits=models.IntegerField(default=0)

I did it using cookies. Don't know if it's a good idea to do that or not. The following code looks for an already set cookie first if it exists it increases the total_view counter if it is not there the it increases both total_views and unique_views. Both total_views and unique_views are a field of a Django model.
def view(request):
...
cookie_state = request.COOKIES.get('viewed_post_%s' % post_name_slug)
response = render_to_response('community/post.html',context_instance=RequestContext(request, context_dict))
if cookie_state:
Post.objects.filter(id=post.id).update(total_views=F('total_views') + 1)
else:
Post.objects.filter(id=post.id).update(unique_views=F('unique_views') + 1)
Post.objects.filter(id=post.id).update(total_views=F('total_views') + 1)
response.set_cookie('viewed_post_%s' % post_name_slug , True, max_age=2678400)
return response

Related

Showing ads between posts django ? Trying to show ads after specific number of posts

I want to implement this thing, Like Facebook showing ads when we are scrolling posts, after 4th or 5th post we see some ads, how can I do that please can someone tell me? If any video link is available please share
Between two post like this
I am showing all the post by for loop, if any nested for loops available please share how can I loop two different model in single loop , showing 1st item of one model after 5th item of another model loop
**I am not using google ad sense, you can imagine its like I will create my own model , maybe same post model where is_ads=True be a field like that.
This is my post model-
class Post(models.Model):
postuuid = models.UUIDField(default=uuid.uuid4,unique=True,editable=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=150,blank=False)
text = models.TextField(null=True,blank=False)
image = models.ImageField(upload_to='post_images/',null=True,blank=True,default="")
created_at = models.DateTimeField(auto_now_add=True, null=True)
likes = models.ManyToManyField(User, blank=True, related_name="post_like")
tag= models.CharField(max_length=150,blank=True)
post_url=models.URLField(max_length=150,blank=True)
video = models.FileField(upload_to='post_videos/',null=True,blank=True,default="")
# community = models.ForeignKey(communities,on_delete=models.CASCADE)
def __str__(self):
return self.title
Looking at this, I think your best option would be to create a view function that creates two querysets and merges them together into a single one that you can pass to the context. It would be something like this:
# settings.py
AD_POST_FREQUENCY = 5 # Use this to control the frequency of ad posts appearing in the list of posts.
# views.py
from settings import AD_POST_FREQUENCY
def post_list(request):
ad_posts = Post.objects.filter(is_ad=True)
non_ad_posts = Post.objects.filter(is_ad=False)
posts = []
non_ad_post_section_count = 0
for i, post in enumerate(non_ad_posts):
posts.append(post)
if i % AD_POST_FREQUENCY == 0:
ad_post = ad_posts[non_ad_post_section_count]
posts.append(ad_post)
non_ad_post_section_count += 1
context = {
'posts': posts,
}
return render(request, 'your_template.html', context=context)
Using this approach, you would be able to essentially create a single list of posts to return to your template. Then you would only need to loop through the list in the template to display all the posts.
I haven't tried this myself but hopefully, it helps you.

Django app goes haywire when deployed. Race conditions?

I wrote a django app for quizzing, and it checks the user's answers and updates scores as soon as the user submits an answer. Here is the corresponding view to do this -
current_question_key = 0 #This is a global variable.
def check_answer(request):
current_user = request.user
current_team = Team.objects.get(user = current_user)
current_score = current_team.score
if request.method == "POST":
answer = request.POST.get('answer')
question = Question.objects.get(id = current_question_key)
if answer == question.answer:
if question in current_team.questions_answered.all(): #This is required to prevent the score from increasing if the somebody submits a correct answer to the same question more than once
pass
else:
current_team.score = current_score + question.score_increment
current_team.questions_answered.add(question)
current_team.save()
else:
# This is required to prevent the score from decreasing if someone has answered it correctly earlier
if question in current_team.questions_answered.all():
pass
else :
current_team.score = current_score - question.score_increment//negative_marking_factor
current_team.save()
return HttpResponse(status=204) #This means that the server has successfully processed the request and is not going to return any data.
else:
return HttpResponse("Error404")
The value of current_question_key is changed from the view used to send the question to the front end -
def game(request):
if request.method == "POST":
key = request.POST.get('questionKey')
global current_question_key
current_question_key = key
question = Question.objects.get(id = key)
question_text = question.question_text
data = {
'question_text':question_text
}
return JsonResponse(data)
else:
current_user = request.user
current_team = Team.objects.get(user = current_user)
score = current_team.score
name = current_user.username
return render(request, 'Base/main.html', {'teamname':name, 'score':score})
When tested on django's development server, this worked perfectly fine even when around 10 people were using it simultaneously. But, as soon as I tried to serve it with nginx (hosted on my laptop, with 5 simultaneous users), the app went totally haywire and even correct answers were evaluated as wrong.
I tried apache too and had the same problem with it. Almost all requests were handled incorrectly. Could this be related to race conditions? What exactly might be going on here?
You cannot use a global variable like this in Django. A Django application usually runs in multiple server processes which do not share memory. Calling the game view would only set the global variable current_question_key in one of the processes. All other processes would still have old values. As a request can be served by any process, you get more or less random results.
The Django development server uses multi-threading instead of multi-processing. Threads, as opposed to processes, share the same memory, so all request see the same value for current_question_key.
You have to store current_question_key for each user in a way that is accessible to all processes. The most obvious solution would be to store this information in the user's session:
request.session['current_question_key'] = ...
Alternatively, you could store it in the database, e.g. with ForeignKey in a customer user model, or if you want to keep track of games any in a separate table like this:
from django.contrib.auth import get_user_model
from django.db import models
class Game(model.Model)
user = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
You can then get the current game for a user by sorting by creation date:
Game.objects.filter(user=request.user).order_by('-created_at').first()
Depending on how often the current question changes, you could also consider using a key-value like Redis, although that complicates things a bit.

geodjango foreignkey by distance

i'm struggling with complicated Django queries, mainly because I try to do something that might be complicated.
Basically, I got this :
models.py :
from django.contrib.gis.db import models
class Show(models.Model):
name = models.CharField()
class Venue(models.Models):
name = models.CharField()
coords = models.PointField()
objects = models.GeoManager()
class Representation(models.Model):
datetime = models.DateTimeField()
venue = models.ForeignKey(Venue)
show = models.ForeignKey(Show)
Now, what I want to do is, get 5 incoming shows that are close to user (user.geoloc is a Point). One of the complicated thing is that some of my users may live in area where there are no venue, and, my solution is just, if there is not enough shows close to them, to search in a bigger area.
view.py :
from django.contrib.gis.measure import D
DISTANCE_CLOSE = 1000 #arbitrary number
## this thing is not working, because it's not how it should be done
## but the logic is clearer in this
def get_nearest_shows_not_working(request):
list_shows = {}
while len(list_shows<5)
list_shows = Show.representation_set.filter(venue__coords__distance_lte=(user.geoloc, D(m=DISTANCE_CLOSE))).order_by('datetime'):[5]
DISTANCE_CLOSE = int(DISTANCE_CLOSE*1.2)
return render(request, 'template.html', locals())
def get_nearest_shows_ducktape(request):
list_shows = set()
while len(list_show) < 5:
list_rep = Representation.objects.filter(venue__coords__distance_lte=(user.geoloc, D(m=DISTANCE_CLOSE))).order_by('datetime')
for rep in list_rep:
list_shows.add(rep.show)
DISTANCE_CLOSE = int(DISTANCE_CLOSE*1.2)
list_shows = list_shows[:5]
return render(request, 'template.html', locals())
What am I missing ? in Python, there shoud be one and only right way to do something, and here, I'm just messing with complicated things, this looks unpythonic to me.
You would need something like this:
Show.representation_set.distance(user.geoloc, field_name='venue__coords') \
.order_by('distance')
Unfortunately reversed one to one or one to many relationships are not supported with GeoQuerySet methods (including distance). I already created a feature request ticket for this.
So for now I think you're stuck with:
some hack in Python, not always efficient (like you wrote)
using raw sql queries

Django Application zum loggen

Let's say I have Two Models:
class Thinclient(models.Model):
hostname = models.TextField(_('hostname'),unique=True, \
editable=False)
logs = models.ManyToManyField(Log, blank=True, null=True)
class Log(models.Model):
logname = models.TextField(editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
As you can see I have thin clients. Now If one of those thin clients boots it is supposed to send a POST request to my app with the thin client name and a logname (i.e. "booting ...").
Now my view will handle all the work and that's where I have my problem. It currently looks like this:
def log(request):
if request.method == 'POST':
form = ThinclientForm(request.POST)
if form.is_valid():
message = form.cleaned_data['logname']
Log.objects.get_or_create(logname=message)
return HttpResponse(content="", mimetype=None, status=200)
else:
return HttpResponse(content="Unsuccessful", mimetype=None,
status=400)
return render_to_response('thin/status', {
'form': form, })
However that won't wok because I have to assign a message to one specific thin client. I suppoose I have to write my own form with hostname, logname and that is where i have my problem how can I save my models in a way that the message is assigned to a thin?
I hope I could explain what I need to know, if not tell me. And Thanks for any help in this
Assuming your Thinclient name is also included in the POST, you can just get it from there and use it to look up the actual object, then assign that Log message to it.
log = Log.objects.get_or_create(logname=message)
client = Thinclient.objects.get(hostname=request.POST['clientname']
client.logs.add(log)
(One note: you shouldn't use TextFields for things like client names - these are stored as BLOB/TEXT objects in the database, which is far less efficient than normal VARCHARs. Use CharField instead.)
There are several problems here. The first is in the design of your models. The link from Log to ThinClient should be a ForeignKey from Log to ThinClient
class Thinclient(models.Model):
hostname = models.TextField(_('hostname'),unique=True, \
editable=False)
class Log(models.Model):
thin_client = models.ForeignKey(Thinclient)
logname = models.TextField(editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
Do you even need a form here? Aren't your thin clients just doing a post to this url? Are they really doing a GET to get the form? The benefit of forms is the ability to turn them into HTML and data validation. I don't think you need either of those here.
The host name of the remote client is stored in the REMOTE_HOST request header so you can use this to pull it out.
def log(request):
thin_client, _ = Thinclient.get_or_create(hostname=request.META["REMOTE_HOST"])
Log(thin_client=thin_client, logname=request.POST["logname"]).save()
return HttpResponse(content="OK", mimetype="text/plain")
As a side note you should always return some content. Some proxies do not like zero byte responses. You should also always specify a mimetype, even if it's the default of text/html.

Django - Lazy results with a context processor

I am working on a django project that requires much of the common page data be dynamic. Things that appear on every page, such as the telephone number, address, primary contact email etc all need to be editable via the admin panel, so storing them in the settings.py file isn't going to work.
To get around this, I created a custom context processor which acts as an abstract reference to my "Live Settings" model. The model looks like this:
class LiveSetting(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255, blank=False, null=False)
description = models.TextField(blank=True, null=True)
key = models.CharField(max_length=100, blank=False, null=False)
value = models.CharField(max_length=255, blank=True)
And the context processor like so:
from livesettings.models import LiveSetting
class LiveSettingsProcessor(object):
def __getattr__(self, request):
val = LiveSetting.objects.get(request)
setattr(self, val.key, val.value)
return val.value
l = LiveSettingsProcessor()
def livesetting_processors(request):
return {'settings':l}
It works really nicely, and in my template I can use {{ settings.primary_email }} and get the corresponding value from the database.
The problem with the above code is it handles each live setting request individually and will hit the database each time my {{ settings.*}} tag is used in a template.
Does anyone have any idea if I could make this process lazy, so rather than retrieve the value and return it, it instead updates a QuerySet then returns the results in one hit just before the page is rendered?
You are trying to invent something complex and these is no reason for that. Something as simple as this will work fork you good enough:
def livesetting_processors(request):
settings = LiveSetting.objects.get(request)
return {'settings':settings}
EDIT:
This is how you will solve your problem in current implementation:
class LiveSettingsProcessor(object):
def __getattr__(self, request):
val = getattr(self, '_settings', LiveSetting.objects.get(request))
setattr(self, val.key, val.value)
return val.value
#Hanpan, I've updated my answer to show how you can to solve your problem, but what I want to say is that things you are trying to achieve does not give any practical win, however it increase complexity ant it takes your time. It might also be harder to setup cache on all of this later. And with caching enabled this will not give any improvements in performance at all.
I don't know if you heard this before: premature optimization is the root of evil. I think this thread on SO is useful to read: https://stackoverflow.com/questions/211414/is-premature-optimization-really-the-root-of-all-evil
Maybe you could try Django's caching?
In particular, you may want to check out the low-level caching feature. It seems like it would be a lot less work than what you plan on.