django html template not loading from generic view from model

Tags: , , ,



Introduction

I am currently working on a small piece of the project to understand more about the Django ORM and to display individual objects. Using Generic views to keep the code clean. (I am somewhat new to Django Framework)

Goal

The goal of this piece is to show how many users have written an article. For example, I have total of 4 articles. I need to show how many articles written by each user.

My console output:

User Article Count
testuser 2
testuser1 2
testuser2 0

my output on the HTML template:

User Article Count
testuser 0
testuser1 0
testuser2 0

Problem

I am not understanding why my console output is correct, but my HTML template isn’t showing the results. Please HELP me understand why it is doing this!!!!! If there is a similar post let me know! I have been looking everywhere for the answer, but I couldn’t find it on the internet.

Code

views.py

from django.views.generic import ListView
from .models import Article
from django.contrib.auth import get_user_model


class ArticleListView(ListView):
    model = Article
    template_name = 'article_list.html'
    context_object_name='article_list'
    queryset=Article.objects.order_by('-date')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)        
        users = get_user_model().objects.all()
        context["users"] = users
        articles = Article.objects.all()
        for user in users:
            article_count = articles.filter(author__exact=user).values('author').count()
            # works with and without str() function
            context['article_count'] = str(article_count)
            # My console output is correct
            print(str(user) + ': ' + str(articles.filter(author__exact=user).values('author').count()))

        return context

models.py

from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse


class Article(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('article_detail', args=[str(self.id)])

article_list.html

{% extends 'base.html' %}

{% block title %}Articles{% endblock title %}

{% block content %}

   <!-- This is for checking if the articles exist. Nothing to worry here. Output is 4 -->
  {% if article_count > 0 %}
    <p>{{article_count}} There are articles in this list</p>
  {% else %}
   <p>There are no articles in this list</p>
  {% endif %}

  <!-- Main source of the problem!! -->
  {% for user in users %}
    <p>{{user}} = {{article_count}}</p>
  {% endfor %}

  <!-- Displays data from database. Nothing to worry here. -->
  {% for article in article_list %}
    <div class="card">
      <div class="card-header">
        <span class="font-weight-bold">{{ article.title }}</span> &middot;
        <span class="text-muted">by {{ article.author }} | {{ article.date }}</span>
      </div>
      <div class="card-body">
        {{ article.body }}
      </div>
      <div class="card-footer text-center text-muted">
        <a href="{% url 'article_edit' article.pk %}">Edit</a> |
        <a href="{% url 'article_delete' article.pk %}">Delete</a>
      </div>
    </div>
    <br />
  {% endfor %}
{% endblock content %}

Hopefully is organized enough to understand the problem. Thanks in advance!!!

Answer

You are just rewriting the article_count variable in your for loop, so, the context gets assigned only to the last calculated value. You might create a dictionary something like {"user": {"object": user, "article_count": article_count}} for each user, and assign this dictionary to the context after making calculation for all of them, however, it is still not a clear way.

Best way would be to assign a related name for the connection:

class Article(models.Model):
    ...
    author = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE,
        related_name="articles",
    )

And then, assign users to your context in the view:

context["users"] = get_user_model().objects.all()

In your template, you can now show the result like this:

{% for user in users %}
    <p>{{ user }} = {{ user.articles.count }}</p>
{% endfor %}


Source: stackoverflow