ckp7blessed
ckp7blessed

Reputation: 135

Django - How to return multiple querysets with get_queryset() - ListView

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:

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 image of a user's profile showing only the user's posts. But every post has the same comment list pt2 image of a user's profile showing only the user's posts. But every post has the same comment list pt2

Upvotes: 1

Views: 3033

Answers (1)

Jimmy Pells
Jimmy Pells

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

Related Questions