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