Django CBVs: reuse query from get_queryset in get_context_data method in ListView

237 Views Asked by At

I need to pass a Tag object to the template, but without an additional query to the database in get_context_data method.
Can you please tell me if there is a more elegant way to get the value from the get_queryset method in the get_context_data. And if there is a more elegant way, is it correct to declare your own fields in Django views

class PostListView(ListView):
    model = Post
    paginate_by = 3
    context_object_name = 'posts'
    template_name = 'blog/post/list.html'

    tag = None

    def get_queryset(self):
        data = super().get_queryset()
        if tag_slug := self.kwargs.get('tag_slug'):
            self.tag = get_object_or_404(Tag, slug=tag_slug)
            data = data.filter(tags__in=[self.tag])
        return data

    def get_context_data(self, *, object_list=None, **kwargs):
        data = super().get_context_data(**kwargs)
        data['tag'] = self.tag
        return data
2

There are 2 best solutions below

0
On

Based on reply I edit my code for the following:

  • because I need tag name and it can be different from tug slug I override get method to get tag object and not execute uselees code later

  • removed query from get_queryset

  • replaced "object_list" with "posts" due to iterating through posts in my template

    class PostListView(ListView):
     model = Post
     paginate_by = 3
     context_object_name = 'posts'
     template_name = 'blog/post/list.html'
    
     tag = None
    
     def get(self, request, *args, **kwargs):
         if tag_slug := self.kwargs.get('tag_slug'):
             self.tag = get_object_or_404(Tag, slug=tag_slug)
         return super(PostListView, self).get(request, *args, **kwargs)
    
     def get_queryset(self):
         query = Q()
         if self.tag:
             query = Q(tags__slug=self.tag)
         return super(PostListView, self).get_queryset().filter(query)
    
     def get_context_data(self, *args, **kwargs):
         response = super().get_context_data(*args, **kwargs)
    
         if len(response['posts']):
             response['tag'] = self.tag
         else:
             raise Http404
         return response
    
1
On

get queryset should only prepare queryset without any hit in database. In your case you do many not needed things:

 def get_queryset(self):
    query =  Q()
    if self.kwargs.get('tag_slug'):
        query = Q(tags__slug=self.kwargs['tag_slug']) 
    return super().get_queryset().filter(query).select_related('tag')

in reality function above is One-liner. You don't need any tag in context, but if you want:

def get_context_data(self, *args, **kwargs):
    response = super().get_context_data(*args, **kwargs)
    if len(response['object_list']) : # only one hit in database
        response['tag'] = response['object_list'][0].tag
    else:
        raise Http404
    return response

len made ask in database, and get all objects.