Search code examples
pythondjangodjango-modelsdjango-viewsdjango-generic-views

Django's ListView - How to customise it


I am sorry if this is a duplicate, but I did not find any answer to my question.

My issue below

I am really struggling to understand how to customise my views when using ListView. I read the Django documentation, but I find it very brief.

What I would like to do is to count the questions for each topic and return the topic with the most questions. I would be able to count the number of questions for each topic with the following line of code in my models.py (Topic class):

def questions_count(self):
    return Question.objects.filter(question__topic = self).count()

However, I would like to return only the most popular Topic and the number of questions.

Using a function-based view, I would loop over the topics, and I would create two variables storing the topic name and the number of questions. However, with the ListView I do not know whether to use get_context_data(), get_queryset() or something else.

My view class:

class TopicsView(ListView):
    model = Topic
    context_object_name = 'topics'
    template_name = 'home.html'

My models:

class Topic(models.Model):
    name = models.CharField(max_length=30, unique=True)
    description = models.CharField(max_length=100)

class Question(models.Model):
    ....
    topic = models.ForeignKey(Topic, related_name='questions', on_delete=models.CASCADE)    
    ....

Solution

  • You might want to try using .annotate() to get the count of Questions associated with each Topic:

    from django.db.models import Count
    
    topics_count = Topics.objects.annotate(count_of_questions=Count('question'))
    

    Each Topic object will have a new attribute count_of_questions which is the total number of questions associated with each Topic.

    In your ListView:

    class TopicsView(ListView):
        model = Topic
        context_object_name = 'topics'
        template_name = 'home.html'
    
        def get_queryset(self):
            queryset = super(TopicsView, self).get_queryset()
            queryset = queryset.annotate(count_of_questions=Count('question'))
    

    Then you should be able to use dictsortreversed to do the following in your template, which should return the topic with the most questions first:

    {% for t in topics|dictsortreversed:"count_of_questions" %}
        {{ t.count_of_questions }}
    {% endfor %}
    

    There's a similar question and answer in the following SO Q&A