1

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 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

ckp7blessed
  • 105
  • 2
  • 10
  • `get_queryset` returns exactly one primary queryset for some common actions in a view that expect one queryset. It makes little sense to return more than one queryset there, since the functions calling `get_queryset` expect only one queryset to be returned. If you want to output additional data, you need to customise the specific view method itself, or add that as additional context (as you did). – deceze Apr 06 '22 at 08:44
  • not sure if I should get_queryset the Post model and have the ListView paginate it by default or get_queryset the Comment model and figure out how to paginate the Post context data... I edited the question.. – ckp7blessed Apr 06 '22 at 10:02
  • Can you share your urls and your templates? I don't understand how you plan to paginate two lists simultaneously, what if one list is 5 elements but the second list is 500 elements... – Jimmy Pells Apr 06 '22 at 16:48
  • Hey Jimmy, thanks for your response. I added the urls and template example. Also edited to help clarify as much as possible. Regarding the pagination, I'm trying to paginate the user's posts only, 5 posts per page (this is for the user's profile page) . I do not plan to paginate the comment list for each post (ordered by highest like). your help is much appreciated – ckp7blessed Apr 07 '22 at 03:09

1 Answers1

2

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",
                )
            )
        )
Jimmy Pells
  • 674
  • 1
  • 5
  • 12