I have a Django app where users log in, set various topics, and then leave comments under the said topics. The following models reflect this rudimentary set up:
class Topic(models.Model):
topic_text = models.TextField()
submitted_on = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
comment_text = models.TextField()
which_topic = models.ForeignKey(Topic)
submitted_by = models.ForeignKey(User)
submitted_on = models.DateTimeField(auto_now_add=True)
For each user, I am trying to get all topics where any one of the most recent 5 comments were written by the user. In other words, if a user has not commented among a topic's most recent 5 comments, the topic will be excluded from the queryset.
So how do I go about forming this queryset? Btw I was going to show you what I've tried, but it's woefully inadequate and obviously wrong. Can someone please help?
I haven't tested it, but a subquery should work. Something like this:
Topic.objects.filter(
comment__submitted_by__in=Comment.objects.values(
'submitted_by'
).order_by(
'-submitted_on'
).limit(5),
submitted_by=user
)
(Add .prefetch_related('comment_set') if you plan to access the comments.)
Related
This is a pretty straight forward issue but I don't know why prefetch related isn't working for me. My relevant models:
class Topic:
name = models.CharField(max_length=100, unique=True)
class Aritcle:
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
title_text = models.CharField(max_length=200, unique=True)
class Comment:
num_likes = models.IntegerField(default=0)
pub_date = models.DateTimeField('date published')
word = models.ForeignKey(Article, on_delete=models.CASCADE)
I want to return a list of Articles, and for each article the highest rated comment on that article (max num_likes).
I have a QuerySet[Article] called search_results. I keep trying:
search_results.prefetch_related(
Prefetch('comment_set', queryset=Definition.objects.order_by('-num_likes').first(), to_attr="top_comment")
)
But it doesn't seem to work. I've tried to use the attr and it gives me an attribute error:
for article in search_results:
print(article.top_comment)
generates:
AttributeError: 'Article' object has no attribute 'top_comment'
I've tried with arbitrary query sets, doing Comments.objects.filter('pub_date') but nothing seems to work
I should note that if I change 'comment_set' to something else such as 'comments' it gives me an error, so comment_set must be a valid part of Article
First of all you can't use model object instead of queryset in Prefetch (I mean the line queryset=Definition.objects.order_by('-num_likes').first()) and it should be a queryset object.
I think one way to showing top comment based on each article is to use something like:
search_results.prefetch_related('comment_set') # prefetch related for optimization purpose
and then in your for loop get the top comment like following codes:
for article in search_results:
top_comment = article.comment_set.order_by('-num_likes').first() or "Does not have comments"
print(top_comment)
I would suggest querying the other way to make your life easier.
Comment.objects.order_by('id', '-num_likes').distinct('id').select_related('article')
I have a Django project with ForumPost, ForumComment, ForumPostLike, ForumCommentLike models. Post has multiple comments, multiple forumpostlikes.
Comment has multiple forumcommentlikes. Each user can only like a post or a comment once.
What I want: I want to order posts by calculated = (num_comments*5 + num_postlikes + num_commentlikes)
Questions: With the following code, It seems that I am getting what I wanted. But there are some weird behaviors of Count(). Also, I would like to know if there is a better way of achieving my goal.
models.py
class ForumPost(models.Model):
# author, title .. etc.
class ForumComment(models.Model):
post = models.ForeignKey(ForumPost, on_delete=models.CASCADE, related_name='comments')
#other fields
class ForumPostLike(models.Model):
liker = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='liked_forumposts')
post = models.ForeignKey(ForumPost, on_delete=models.CASCADE, related_name='forumpostlikes')
class ForumCommentLike(models.Model):
liker = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='liked_forumcomments')
comment = models.ForeignKey(ForumComment, on_delete=models.CASCADE, related_name='forumcommentlikes')
views.py
posts= ForumPost.objects.filter(published_date__lte=timezone.now()).\
prefetch_related('comments', 'forumpostlikes')
posts_top = posts\
.annotate(num_commentlikes=Count('comments__forumcommentlikes__liker'))\
.annotate(num_postlikes=Count('forumpostlikes', distinct=True))\
.annotate(num_comments=Count('comments', distinct=True))\
.annotate(calculated=(F('num_comments')*Value(5)) + F('num_postlikes') +F('num_commentlikes')*Value(0.5))\
.order_by('-calculated')
Weird behavior of Count():
annotate(num_commentlikes=Count('comments__forumcommentlikes__liker'))
using this code, I wanted to get the total number of likes from all comments associated with the current post. But the result shows 2*correct number. Once I realized this, I multiplied this number with Value(0.5) to reflect correct number.
If I use distinct as follow:
annotate(num_commentlikes=Count('comments__forumcommentlikes__liker', distinct=True))
this code only gives me the number of likes from the first comment.
Why is this happening? Also, how to improve my code? Thanks!
I have an app within my project called posts, where inside their in the models.py, I have two models: Post and Like.
I want to add a many-to-many-field on the post that references the Like model.
I have executed makemigrations and migrate, however I am getting this error:
NameError: name 'Like' is not defined
models.py:
class Post(models.Model):
file = models.ImageField(upload_to='images/')
summary = models.TextField(max_length=600)
pub_date = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
likes = models.ManyToManyField(Like)
def __str__(self):
return self.user.username
def summary_pretty(self):
return self.summary[:50]
def pub_date_pretty(self):
return self.pub_date.strftime('%b %e %Y')
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.BooleanField(default=False)
it says
NameError: name 'Like' is not defined
because you must move Like model above Post model it hasn't been defined yet in your code in that line (python code is interpreted)
and it's not a good thing to design database models like that based on:
Why should I avoid loops when designing relationships for a database?
so delete the Post foreign key in Like model and you can retrieve a like's post with reverse lookup
you can find more about it in django official docs:
Lookups that span relationships
and also
Many-to-many relationships
You're referencing the class Like before initiating it in your Python file. Hence "Like" is not defined.
likes = models.ManyToManyField(Like)
You need to delete "likes" from your Post class. That will fix the error.
As for your code, I think you're misunderstanding why we use Intermediary Tables. You don't need to reference Like in your Post class. You've already established that relationship in the Like Class:
post = models.ForeignKey(Post, on_delete=models.CASCADE)
Retrieving the users who have liked a post is as simple as writing a filter on the Likes table.
#views.py
from models import Post, Like
post_id = 1
likes = Like.objects.filter(post=post_id)
I have trouble to display the ''saved''/''liked'' posts of my users in django/admin. I would like to have a field in the Adminpage to show which user likes which posts. I made an Userprofile model where all extra information (besides the one on the given django admin user Profile) are stored. so here is my model View:
class UserProfile(models.Model):
user = models.OneToOneField(User, null=True)
#likes = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True,default=1, related_name='likes')
likedPosts=models.ManyToManyField('self')
Field1 = models.CharField(max_length=50,default='Sunny')
Field2 = models.CharField(max_length=50,default='')
class Meta:
ordering =['-user']
#def __unicode__(self):
# return self.user.username
User.profile =property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
right now in the liked post field I have only some usernames or "User object"
I tried all kinds of combinations to get the information into the admin page but as you can see I did not make it.
I tried to change the unicode and of course the liked post line. If you need more information please tell me so. I appreciate every kind of help.
django admin isn't really meant to support many to many relationships from both directions in the django admin. However, the link below contains a workaround that I think should address your problem with a better explanation of why many-to-many relationships are only shown from one side by default.
(many-to-many in list display django).
so for everybody who wants to do something similar this worked for me:
class UserProfile(models.Model):
likedPosts = models.ManyToManyField('self',default=None,blank=True)
def __unicode__(self):
return "{0}".format(self.user.likes.all())
To elaborate
e.g.
I have two models "Subject" and "Question"
class Subject(models.Model):
title = models.CharField(max_length=200,unique=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.title
class Question(models.Model):
title = models.CharField(max_length=500)
is_active = models.BooleanField(default=True)
subject = models.ForeignKey('Subject')
def __str__(self):
return self.title
I want the list of active subjects having at least one active question.
I have done initial search and also checked django queryset api, but did not got answer.
I am not looking for raw sql query option.
I hope this clears the query. I have tried django api, but did not get expected result. I think this is very obvious query and there should be simple answer to it.
Thanks in advance for any help.
Did you try this?
Subject.objects.filter(question__id__isnull=False).distinct()
You might even be able to simplify it to the following, but I'm too lazy to look up if it's correct or try it out:
Subject.objects.filter(question__isnull=False).distinct()