Django: How to keep pagination consistent with a growing queryset - django

I've turned to the experts at stackoverflow in hopes I could figure this out.
Let's look at a popular application like Instagram for example. Let's say you decide to look for posts with the hashtag #love (about 551,677,074 posts and growing every second!)
Each page contains 15 results in descending order of post time. As you scroll down and load more, it returns the next set of 15 results and so on. One might ask, well, if the queryset is growing every second, by the time I'm ready to view page 2, how can I be sure that page 2 will contain the next set of results in order relative to the results I just got in page 1? If the queryset is growing, isn't there a chance that I might see some or all of the results I previously got from page 1? In the eyes of the user, its as if the queryset is constantly being pushed forward relative to where they we're last time.
Well that right there is my dilemma folks. If the queryset is growing, how can assure that the next page I request will start off from where I left off on page 1?
I've created the following model below:
class Blog(models.Model):
author = models.ForeignKey(User)
published_time = models.DateTimeField(auto_now_add=True)
text = models.CharField(max_length=1000, blank=True)
I would like to create a view that returns 10 blogs objects at a time ordered by published_time.
from django.core.paginator import Paginator
def BlogView(request):
if request.META.has_key('HTTP_AUTHORIZATION') and request.method == 'GET':
authmeth, auth = request.META['HTTP_AUTHORIZATION'].split(' ', 1)
if authmeth.lower() == 'basic':
auth = auth.strip().decode('base64')
username, password = auth.split(':', 1)
authuser = authenticate(username=username, password=password)
if authuser:
if authuser.is_active:
queryset = Blog.objects.all().order_by('-published_time') #descending order
paginator = Paginator(queryset, 10) # 10 objects per page
blogs = paginator.page(page_number)
data = []
for blog in blogs:
data.append(
{'id': blog.pk,
'author_id': blog.author_id,
'text': blog.text
'published_time': blog.published_time})
return HttpResponse(json.dumps({'results':data}), content_type="application/json")
Very simple setup, yet, I can't figure out on how to do this, any suggestions and examples would extremely be appreciated!

This question is answered in this post on how to set a cache in django:
Caching query results in django

Related

Django query to return percentage of a users with a post

Two models Users (built-in) and Posts:
class Post(models.Model):
post_date = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='user_post')
post = models.CharField(max_length=100)
I want to have an API endpoint that returns the percentage of users that have posted. Basically I want SUM(unique users who have posted) / total_users
I have been trying to play around with annotate and aggregate, but I am getting the sum of posts for each users, or the sum of users per post (which is one...). How can I get the sum of posts returned with unique users, divide that by user.count and return?
I feel like I am missing something silly but my brain has gone to mush staring at this.
class PostParticipationAPIView(generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_queryset(self):
start_date = self.request.query_params.get('start_date')
end_date = self.request.query_params.get('end_date')
# How can I take something like this, divide it by User.objects.all().count() * 100, and assign it to something to return as the queryset?
queryset = Post.objects.filter(post_date__gte=start_date, post_date__lte=end_date).distinct('user').count()
return queryset
My goal is to end up with the endpoint like:
{
total_participation: 97.3
}
Thanks for any guidance.
BCBB
EDIT
OK, I am still struggling a bit. I tried to create a serializer that just had a decimal field for participation_percentage like:
percentage_participation = serializers.DecimalField(max_digits=5, decimal_places=2, max_value=100, min_value=0)
Then I calculate in the view, but I get an error:
Got AttributeError when attempting to get a value for field percentage_participation on serializer ParticipationSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the str instance.
Original exception text was: 'str' object has no attribute 'percentage_participation'.
Error was the same if I made it a CharField (in case there was some string coercion?).
So then I tried to move it to a Serializer Method and put all the calculation logic in there. This calculated fine, but if I had to provide a query_set in the view. If provided a model object, it just returned the percentage as many times as the query (say Posts.objects.all() had a total of 100 posts, it returned the percentage 100 times).
So then I tried to override the get_queryset in the view, but I HAVE to return something. If I just return { "meh", "hello" } then I return the percentage from the SerializerMethodField one time and the end result is exactly what I want.
I just have no idea as to WHY or how to do this correctly.
Thanks for your help.
EDIT #2
OK so I realized why I was only getting one, it was iterating over the string I returned, which was one character. When I returned "meh" it gave me three of the percentage, iterating over each character in the string...
I am not understanding from playing around, reading the docs, or using GoogleFu how to do this properly. I just want to be able to perform some kind of summary logic on records from the DB - how can I do this properly?!?!
Thank you for all your time.
BCBB
something like this should work
# get total user count
total_users = User.objects.count()
# get unique set of users with post
total_users_who_posted = Post.objects.filter(...).distinct("user").count()
# calculate_percentage
percentage = {
"total_participation": (total_users_who_posted*100)/ total_users
}
# take caution of divion by zero
I don't think it is possible to use djangos orm to do this completely but you can use the orm to get the user counts (with posts and total):
from django.db.models import BooleanField, Case, Count, When, Value
counts = (User
.objects
.annotate(posted=Case(When(user_post__isnull=False,
then=Value(True)),
default=Value(False),
output_field=BooleanField()))
.values('posted')
.aggregate(posted_users=Count('pk', filter=Q(posted=True)),
total_users=Count('pk', filter=Q(posted__isnull=False)))
# This will result in a dict containing the following:
# counts = {'posted_users': ...,
# 'total_users': ....}

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.

How extract individual values from POST request in Django Model Form?

Would like to be able to access data in a post request directly as well as processing it in the normal way. First created form:
class TransactionForm(ModelForm):
class Meta:
model = Transaction
fields = ['dish', 'customer', 'grams', 'amount_payable']
('customer' is the pk of another model, Customer.)
Then process form:
#csrf_exempt
def create_transaction(request):
print(request.POST)
user_input = TransactionForm(request.POST)
print (user_input)
if user_input.is_valid():
user_input.save()
#customerobject = Customer.objects.get(pk= PK-TAKEN FROM POST)
#customerobject.account_balance -= (amount_payable TAKEN FROM POST)
#customerobject.save()
return HttpResponse('AOK~')
else:
return HttpResponse(user_input) #'ERROR: transaction not valid~')
Am struggling to correctly formulate the commented lines above. (The rest works fine.)
Would like to be able to extract the value 'customer' from the POST in order to find the customer. Then to extract the value 'amount_payable' from the POST in order to deduct it from the customer's balance.
Eventually stumbled upon the relevant command:
cust = user_input.cleaned_data.get('customer')
customerobject = Customer.objects.get(pk=cust.id)
customerobject.account_balance -= user_input.cleaned_data.get('amount_payable')
customerobject.save()
Low-level languages are easier for sieve-heads like me.

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.

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

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