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> · <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!!!
Advertisement
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 %}