Django count specific items of a many-to-one relationship - python

I have a Django app where users post messages and other users can vote the answers up or down, very similar to SO. I'm having an issue trying to get the "thumbs up" and "thumbs down" counts from within the template and I'm hoping someone can help me. The PostVote is a many-to-one relationship with the Post class. Here is what my model looks like:
class Post(models.Model):
account = models.ForeignKey(Account)
message = models.CharField(max_length=1024)
timestamp = models.DateTimeField('post timestamp')
class PostVote(models.Model):
post = models.ForeignKey(Post)
account = models.ForeignKey(Account)
vote = models.CharField(max_length=16, choices=VOTE_CHOICES)
timestamp = models.DateTimeField('vote timestamp')
Here is how I'm getting my posts:
posts = Post.objects.all().order_by('-timestamp')[:10]
My template looks roughly like:
{% for post in posts %}
<div>Thumbs up count: {{ WHAT_HERE }}</div>
<div>Thumbs down count: {{ WHAT_HERE }}</div>
{% endfor %}
How can I get the counts in there? I'm sure it involves 'annotate' somehow, but I'm having a difficult time coming up with this one. Any help would be greatly appreciated!

You shouldn't really be doing logic in your templates. Add a couple count methods to your Post model:
class Post(models.Model):
account = models.ForeignKey(Account)
message = models.CharField(max_length=1024)
timestamp = models.DateTimeField('post timestamp')
def upvote_count(self):
return self.postvote_set.filter(vote=VOTE_CHOICES[0][0]).count()
def downvote_count(self):
return self.postvote_set.filter(vote=VOTE_CHOICES[1][0]).count()
Then use them in your template:
{% for post in posts %}
<div>Thumbs up count: {{ post.upvote_count }}</div>
<div>Thumbs down count: {{ post.downvote_count }}</div>
{% endfor %}

Related

Getting duplicate users in leaderboard

I'm trying to make a leaderboard for the most liked users by counting the likes of their post and ordering them.
However if the user has 2 or more post, it will duplicate the user in the leaderboard with the total likes that he has. Is there a way to fix this? Thank you.
models.py:
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
content = models.TextField(max_length=1500, verbose_name='Content')
likes = models.ManyToManyField(User)
views.py:
def top_user(request):
top_user = Post.objects.annotate(total_likes=Count('user__post__likes')).order_by('-total_likes')
context = {'users': top_user}
return render(request, 'blog/top_user.html', context)
html:
{% for top in users %}
<h5>{{ top.user }}</h5>
<p>Upvotes: {{ top.total_likes }}</p>
{% endfor %}
You query the wrong way, the top_users you here present are Posts, so that means that per posts, you count the number of total likes of that author. But if an author has many posts, they appear multiple times.
You thus should annotate the users:
top_user = User.objects.annotate(total_likes=Count('post__likes')).order_by('-total_likes')
and thus render it with:
{% for user in users %}
<h5>{{ user }}</h5>
<p>Upvotes: {{ user.total_likes }}</p>
{% endfor %}

Django get user like from friend list

I'm a Django beginner, I have a friend field in Profile Model which list all my friend users. And also these friends liked my post. How do I get the names of only those users in my friends list to display in the post they liked.
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,... )
friends = models.ManyToManyField('Profile',related_name='my_friends')
class FriendRequest(models.Model):
to_user = models.ForeignKey(settings.AUTH_USER_MODEL,... )
from_user = models.ForeignKey(settings.AUTH_USER_MODEL,... )
class Post(models.Model):
poster_profile = models.ForeignKey(settings.AUTH_USER_MODEL,... )
likes = models.ManyToManyField(User, related_name='image_likes')
def home(request):
all_images = Post.objects.filter(poster_profile=request.user)
img = Post.objects.filter(likes__profile__friends__user=request.user)
{% if all_images %}
{% for post in all_images %}
#all_images code here
{{ post. likes.count }} #like count
{% for image in img %}<img src="{{ image.profile.profile_pic.url }}"> {% endfor %}#The profile_pic do not display
{% endfor %}
{% endif %}
if someone isn't your friend you can prevent him to like or even see (or comment) your post , or for each user who likes your post check if he is in your friend list then add him to a new emty list then render this list
Change your queryset in your view to this:
from django.db.models import Count, Q
all_images = Post.objects\
.filter(poster_profile=request.user)\
.annotate(likes_count=Count(
'likes',
distinct=True,
filter=Q(likes__profile__friends=request.user.profile))
then in your template each post has the annotation likes_count so you can do {{ post.likes_count }}.

How to find the children from a django queryset

I have two django models One for blog pages and one for the blog listing: a list of all blogs. The blogpage has a ForeignKey reference to the listing page.
class BlogListingPage(Page):
...
class BlogDetailPage(Page):
blog_listing = models.ForeignKey(BlogListingPage,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name='+',)
In views.py I have tried to look at the queryset object, but I cannot find a refernce to the detail pages.
def blog(request):
context = {'data': BlogListingPage.objects.all()}
query_set = context['data']
for item in query_set:
print(item.__dict__)
It does correctly tell me the number of detail pages in numchild
How can I access the children themselves?
[EDIT]
I have looked at the answer to this question but it doesn't tell us how to generate event_set
{% for blog_listing in data %}
<h2>{{ blog_listing.blog_listing_title }}</h2>
{% for post in blog_listing.blogdetailpage %}
{{ post.blog_title }}
{% endfor %}
{% endfor %}
You can access related objects in this way:
item.blogdetailpage_set.all()

How to optimize number of queries in the Django view?

I show comments in the topic detail view. Exactly two queries happens
in this view: one to get topic and one to get comments list. I
understand that select_related technique can't be used here since
there are two different querysets created in the view. Is there any
way to decrease number of queries to one?
Application code follows below.
app/models.py
class Topic(models.Model):
headline = models.CharField(max_length=400)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, related_name='topics')
class Comment(models.Model):
headline = models.CharField(blank=True, max_length=400)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, related_name='comments')
topic = models.ForeignKey(Topic, related_name='comments')
app/views.py
class TopicDetail(DetailView):
queryset = Topic.objects.select_related('author').all()
context_object_name = 'topic'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
topic = self.object
context['comments'] = topic.comments.select_related('author').all()
return context
app/templates/app/topic_detail.html
{{ topic.headline }}
{{ topic.description }}
{{ topic.created }}
{{ topic.modified }}
{{ topic.author }}
{% for comment in comments %}
{{ comment.headline }}
{{ comment.description }}
{{ comment.author }}
{% endfor %}
Have a look at the prefetch_related method for reversed foreignKey lookups. This won't actually reduce the number of SQL queries, but django will merge in python related comments and Topics.
In the docs : https://docs.djangoproject.com/en/1.8/ref/models/querysets/#prefetch-related
Also, you should make sure to give different related_name to your fields, otherwise you'll have issues calling them :
author = models.ForeignKey(User, related_name='user_comments')
topic = models.ForeignKey(Topic, related_name='topic_comments')
This way you can write a single line query (but 2 SQL hits) :
topics = Topic.objects.prefetch_related('topic_comments').select_related('author').all()
Then for example :
{% for topic in topics %}
{% for comment in topic.topic_comments.all %}
{{ ... }}
{% endfor %}
{% endfor %}
Even in pure SQL such "single" query would be hard.
Basically You would need to repeat all the topic data in each comment! Like in this pseudo code:
Select comment.*, topic.*
From comments
Right Join topic
That is massive data processing/transfer overhead. Whole operation should take more time, resources then separate queries.
If You really need it, then just write custom function in comment topic model, with pure SQL.

How do I use ndb KeyProperty properly in this situation?

I have two models:
class User(ndb.Model):
email = ndb.StringProperty(required=True)
name = ndb.StringProperty(required=True)
class Post(ndb.Model):
created = ndb.DateTimeProperty(auto_now_add=True)
message = ndb.TextProperty(required=True)
user = ndb.KeyProperty(kind=User)
If I want to loop through some posts in the Post model. How I would access the 'name' field in the User model? For example:
for post in posts:
print post.message
print post.user.name
So I'm guessing the last line isn't correct. I tried searching and couldn't figure it out. And to add to that. Is there a way to access that info in a Jinja2 template?
And then finally, am I going about this wrong? Let's say there are 50 posts. And I have 1 query to get those 50 posts. Is accessing the 'name' field on the User model going to be an additional query for each time it loops? So 51 queries?? If so, is it better to not use KeyProperty and just store the User data with each post (even if I have more data like avatar, user_id, etc)?
If I recall correctly, a KeyProperty will return you a ndb.Key. Once you have the key, it is easy to get the model instance (key.get()). So, in your example, you would:
print post.user.get().name
As far as accessing it in a jinja template -- Sure, it'd just be something like:
{% for post in posts %}
{{ post.message }}
{{ post.user.get().name }}
{% endfor %}
And yes, this will interact with the datastore once for each key you have. If you'd rather, you can factor it out into one datastore interaction:
keys = [p.user for p in posts]
users = ndb.get_multi(keys)
user_posts = zip(users, posts)
And then in your jinja template:
{% for user, post in user_posts %}
{{ post.message }}
{{ user.name }}
{% endfor %}

Categories