Reputation: 135
I have a blog with an option to view a specific user's profile showing a list of posts the user posted. The posts are being returned in a ListView. Each post also comes with comments. So currently, my get_queryset() method returns a queryset of the comments for the post ordered by total of highest likes.
urls.py
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
]
template showing only user's posts.
{% for post in posts %}
<h1>{{ post.title }}</h1>
<a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
<h3>{{ post.content }}</h3>
{% for comment in post.comment_list %}
<h4>{{ comment }}</h4>
{% endfor %}
{% endfor %}
models.py
class Post(models.Model):
title = models.CharField(max_length=100, help_text="100 characters or less")
content = models.TextField()
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
liked = models.ManyToManyField(Profile, blank=True, related_name='likes')
class Comment(models.Model):
user = models.ForeignKey(Profile, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
body = models.TextField(max_length=300)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
liked = models.ManyToManyField(Profile, blank=True, related_name='com_likes')
views.py
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
return (
super()
.get_queryset()
# Prefetch comment using a Prefetch object
.prefetch_related(
Prefetch(
"comment_set",
# Specify the queryset to annotate and order by Count("liked")
queryset=Comment.objects.annotate(
like_count=Count("liked")
).order_by("-like_count"),
# Prefetch into post.comment_list
to_attr="comment_list",
)
)
)
How can I return another queryset? I read from another discussion to use chain(), but it didn't work for me. How to return multiple queryset object or add queryset result from get_queryset method in Django
Right now, this returns Post.objects.all() But I want to return
#second queryset
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
I could add this to a context as below but the paginator doesn't work and I'm not sure if that's the correct way to handle this.
def get_context_data(self, *args, **kwargs):
# context = super(UserPostListView, self).get_context_data(*args, **kwargs)
context = super().get_context_data(*args, **kwargs)
user = get_object_or_404(User, username=self.kwargs.get('username'))
context['user_profile'] = User.objects.filter(username=user).first()
context['posts'] = Post.objects.filter(author=user).order_by('-date_posted')
return context
So my questions are:
How would I add the second queryset to the get_queryset() so that I can return both querysets from the Post model with pagination (filtering the posts only by a specific user) and also show the ordered Comment model (comments for the post, ordered by highest likes on top)
Or how would I paginate the
context['posts'] = Post.objects.filter(author=user).order_by('-date_posted')
and what is the correct way to go about this?
Much appreciated..
tried chain() the two querysets but received an error unhashable type: 'slice' also tried below but same error:
# def get_queryset(self):
# user = get_object_or_404(User, username=self.kwargs.get('username'))
# posts = Post.objects.filter(author=user).order_by('-date_posted')
# queryset = {
# "users_posts": posts,
# "comment_list":
# super().get_queryset()
# # Prefetch comment using a Prefetch object gives you more control
# .prefetch_related(
# Prefetch(
# "comment_set",
# # Specify the queryset to annotate and order by Count("liked")
# queryset=Comment.objects.annotate(
# like_count=Count("liked")
# ).order_by("-like_count"),
# # Prefetch into post.comment_list
# to_attr="comment_list",
# )
# )
# }
# return queryset
--------------------------- EDIT------------------------
I now understand that get_queryset can only return one queryset.. So I'm running the code below..
To achieve pagination with the default ListView, I would have to return a queryset of the model as
def get_queryset(self):
return Post.objects.filter(author=user).order_by('-date_posted')
I can add {% for comment in post.comment_set.all %} in my template and this will show the comments for each post ordered by the date commented.
The comments for each post (ordered by the like count) would have to be returned as context by get_context_data().
However, below doesn't work.. every post has the same comment list..
Is there a "prefetch" method with get_context_data() ?? So I can prefetch each post's comment list and order by highest like.. This is a continuation of my first question.. Django -> ListView -> get_context_data() -> Model.objects.filter(self.object)
I did find a question similar to mine Get related objects in Django and how to use Prefetch with related models
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts1.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
def get_context_data(self, *args, **kwargs):
# context = super(UserPostListView, self).get_context_data(*args, **kwargs)
context = super().get_context_data(*args, **kwargs)
user = get_object_or_404(User, username=self.kwargs.get('username'))
context['user_profile'] = User.objects.filter(username=user).first()
#BELOW DOESN'T WORK. EVERY POST HAS THE SAME COMMENT LIST AS THE LATEST POST.. SEE BELOW IMAGE
posts = Post.objects.filter(author=user).order_by('-date_posted').all()
for post in posts:
context['comment_list'] = post.comment_set.all().order_by("-liked")
# Or Something like this???....
# context['comment_list'] = Post.prefetch_related(
# Prefetch(
# "comment_set",
# # Specify the queryset to annotate and order by Count("liked")
# #queryset = Post.objects.annotate(like_count=Count('liked')).order_by('-like_count')
# queryset=Comment.objects.annotate(
# like_count=Count("liked")
# ).order_by("-like_count"),
# # Prefetch into post.comment_list
# to_attr="comment_list",
# )
# )
return context
image link of the homepage showing Post.objects.all() with comments ordered by highest likes
image of a user's profile showing only the user's posts. But every post has the same comment list
Upvotes: 1
Views: 3033
Reputation: 714
I think the easiest way to do this is to filter by author/user in get_queryset
.
class UserPostListView(ListView):
model = Post
template_name = "blog/user_posts.html"
context_object_name = "posts"
ordering = ["-date_posted"]
paginate_by = 5
def get_queryset(self):
return (
super()
.get_queryset()
# Filter by author/user
.filter(author__username=self.kwargs.get('username'))
# Prefetch comment using a Prefetch object gives you more control
.prefetch_related(
Prefetch(
"comment",
# Specify the queryset to annotate and order by Count("liked")
queryset=Comment.objects.annotate(
like_count=Count("liked")
).order_by("-like_count"),
# Prefetch into post.comment_list
to_attr="comment_list",
)
)
)
Upvotes: 2