Django Display count of database entries related via foreign key - django

I have two models, ProjectNotes and ProjectNoteComments. ProjectNoteComments are related to ProjectNotes via a foreign key. I want to display the number of comments each note has on a listview. I am just learning Django and so far I have not been able to figure out how to retrieve and display the comment count.
My view:
(I do import count)
class ProjectNotesList(ListView):
model = ProjectNotes
template_name = 'company_accounts/project_notes.html'
comments = ProjectNotes.comments
def related_project(self, **kwargs):
project = get_object_or_404(Project, id=self.kwargs.get('pk'))
notes = ProjectNotes.objects.all
return notes
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['project'] = get_object_or_404(Project, id=self.kwargs.get('pk'))
return context
commentscount = ProjectNotes.objects.annotate(num_comments=Count('comments'))
My template:
{% extends 'base.html' %}
{% block content %}
<div class="section-container container">
<h1>Notes for {{ project }}</h1>
{% if project.notes.all %}
{% for note in project.notes.all %}
<div class ="projectnotes-entry">
<div class="col-sm-8">
<div class="row-sm-6">
<div class="card mb-2">
<div class="card-body">
<div class="card-title">{{ note.title }}</div>
<div class="card-text">{{ note.body | safe | truncatewords:"20"|linebreaks }}
read more</div>
</div>
</div>
</div>
</div>
</div>
<h2>comments count</h2>
{{ commentscount }}
{% endfor %}
{% else %}
<p>No notes have been have been added yet.</p>
{% endif %}
</div>
{% endblock content %}
The models:
class ProjectNotes(models.Model):
title = models.CharField(max_length=200)
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
project = models.ForeignKey(Project, default=0, blank=True, on_delete=models.CASCADE, related_name='notes')
def __str__(self):
return self.title
class ProjectNoteComments(models.Model):
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
projectnote = models.ForeignKey(ProjectNotes, default=0, blank=True, on_delete=models.CASCADE, related_name='comments')

Short version:
{{ note.comments.all.count }} # possibly works also without 'all' but can't check right now
I've just answered similar problem with simple explanation of relationships.
https://stackoverflow.com/a/70955851/12775662
Read official docs, it's really rewarding. https://docs.djangoproject.com/en/4.0/topics/db/models/#relationships

Related

Django displaying related objects

I have models for ProjectNotes and for ProjectNotesComments. ProjectNotesComments have a foreign key that is the ProjectNotes id. I am able to save comments on notes. I can see them in the admin panel.
However I have not been able to figure out how to display the comments on the notes.
Here are the models:
class ProjectNotes(models.Model):
title = models.CharField(max_length=200)
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
project = models.ForeignKey(Project, default=0, blank=True, on_delete=models.CASCADE, related_name='notes')
def __str__(self):
return self.title
class ProjectNoteComments(models.Model):
body = tinymce_models.HTMLField()
date = models.DateField(auto_now_add=True)
projectnote = models.ForeignKey(ProjectNotes, default=0, blank=True, on_delete=models.CASCADE, related_name='comments')
Here is the view:
class ProjectNotesDetailView(DetailView):
model = ProjectNotes
id = ProjectNotes.objects.only('id')
template_name = 'company_accounts/project_note_detail.html'
comments = ProjectNotes.comments
This is the template I am currently using to test getting the comments to display:
{% extends 'base.html' %}
{% block content %}
<div class="section-container container">
<div class="project-entry">
<h2>{{ projectnotes.title }}</h2>
<p>{{ projectnotes.body | safe }}</p>
</div>
<div>
</div>
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.body | linebreaks }}
</div>
{% endfor %}
<h2>add note</h2>
{% endblock content %}
I don't think this actually works: comments = ProjectNotes.comments. You would need to override the get_context_data method, and set comments on the context_data to accomplish what you are attempting to do there.
However, you don't need to do that at all, since you can get to the comments from the ProjectNotes object, and the ProjectNotes object is already in the context. Simply change your for loop to this:
{% for comment in projectnotes.comments %}

Nested Loops in Django

I have in my models Projects and Tasks. I'd like to display on the template:
Project 1
Task 1:1
Task 1:2
Task 1:n
Project 2
Task 2:1
Task 2:n
Project n
Here's the model
class Projects(models.Model):
slug_project = models.CharField(max_length=200)
project_name = models.CharField(max_length=200)
project_due_date = models.DateTimeField()
project_lead_time = models.IntegerField()
project_assignee = models.CharField(max_length=200)
project_status = models.CharField(max_length=200)
def __str__(self):
return self.project_name
class Tasks(models.Model):
slug_task = models.CharField(max_length=200)
task_name = models.CharField(max_length=200)
task_desc = models.TextField(null=True)
task_channel = models.CharField(max_length=200)
task_id = models.ForeignKey(Projects, on_delete=models.CASCADE)
task_due_date = models.DateTimeField('Due Date')
task_lead_time = models.IntegerField()
task_assignee = models.CharField(max_length=200)
def __str__(self):
return self.task_name
I'm not sure how to construct the view properly but here's my code:
class somePage(generic.ListView):
template_name = 'dashboard/index.html'
context_object_name = 'project_object'
def get_queryset(self):
"""Return the last five published Coupons."""
return Projects.objects.order_by('project_name')
def get_context_data(self, **kwargs):
context = super(somePage, self).get_context_data(**kwargs)
# context['tasks'] = Tasks.objects.order_by('task_name') #this would display ALL tasks
context.update({
'all_project': Projects.objects.all(),
'all_tasks': Tasks.objects.filter(task__id=self.object),
})
return context
And I'm also not confident how construct the template:
{% if project_object %}
{% for project_name in project_object %}
<div class="card_row_h1">
<a href="{% url 'dashboard:task_list' project_name.id %}">
{{ project_name }}
</a>
</div>
{% if all_tasks %}
{% for task_name in tasks %}
<div class="card_row_h2" style="width: 100%; padding-left: 30px;">
<small>{{ task_name }}</small>
</div>
{% endfor %}
{% else %}
<div class="card_row_h2" style="width: 100%;">
No Tasks for This Project
</div>
{% endif %}
{% endfor %}
The result is that the Projects display correctly, but under each project I get nothing for 'all_tasks' and it displays 'No tasks for this project' or if I use 'tasks' (see commented line in view), it displays all tasks for all the projects over and over.
So there are two questions here:
how do I construct the view, and
how do I construct the template?
I'm a newbie but I've been stuck on this for over a day. Thanks in advance.
Good start! I think you can simplify this a little. You can get the set of tasks for a project using the reverse look up for foreign keys. Without setting the related_name argument in the task models ForeignKey, you can access the tasks associated to a project using some_project.task_set.all(). You can even do this in the template so you dont need to worry about overriding the context data for the view:
views.py
class MainPage(ListView):
template_name = 'dashboard/index.html'
context_object_name = 'projects'
def get_queryset(self):
return Projects.objects.order_by('project_name')
index.html
{% for project in projects %}
<div class="card_row_h1">
<a href="{% url 'dashboard:task_list' project.id %}">
{{ project.project_name }}
</a>
</div>
{% if project.task_set.all %}
{% for task in project.task_set.all %}
<div class="card_row_h2" style="width: 100%; padding-left: 30px;">
<small>{{ task.task_name }}</small>
{% endfor %}
{% else %}
<div class="card_row_h2" style="width: 100%;">
No Tasks for This Project
</div>
{% endif %}
{% endfor %}
If you wanted, in your Tasks model, you could change
task_id = models.ForeignKey(Projects, on_delete=models.CASCADE)
to project = models.ForeignKey(Projects, on_delete=models.CASCADE, related_name="tasks"
This would make the field name a little more intuitive, and also let you access the Tasks for a project just by doing:
my_project_instance.tasks.all()

How to list my categories and forums related to it? Django

Model
class Category(models.Model):
class Meta():
verbose_name_plural = "Categories"
cat_name = models.CharField(max_length=50)
description = models.TextField()
def get_forums(self):
get_forum = Forum.objects.filter(category=self)
return get_forum
def __str__(self):
return f"{self.cat_name}"
class Forum(models.Model):
class Meta():
verbose_name_plural = "Forums"
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="forums")
parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE)
forum_name = models.CharField(max_length=50)
description = models.TextField()
def __str__(self):
return f"{self.forum_name}"
Views
class Home(ListView):
model = Category
template_name = 'forums/index.html'
context_object_name = 'category'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['cat'] = Category.objects.all()
return context
HTML
{% block content %}
{% for cat in category %}
<div class="row">
<div class="bg-success rounded-top border border-dark" style="width:100%; padding-left:8px;">
{{cat.cat_name}}
</div>
</div>
<div class="row">
<div class="bg-secondary border border-dark" style="width:100%; padding-left:16px;">
Forums_Go_Here
</div>
</div>
{% endfor %}
{% endblock content %}
I am trying to get a homepage where I would be able to list my categories and show the forums in those categories.
The template I have is running a for loop which is looping through all Categories.
In the shell i am able to get the forums with the: Category.objects.get(pk=2).get_forums() command. But this limits it to one category.
You can use related name for that, no need to use additional method:
{% block content %}
{% for cat in category %}
<div class="row">
<div class="bg-success rounded-top border border-dark" style="width:100%; padding-left:8px;">
{{cat.cat_name}}
</div>
</div>
{% for forum in cat.forums.all %}
<div class="row">
<div class="bg-secondary border border-dark" style="width:100%; padding-left:16px;">
{{forum.forum_name}}
</div>
</div>
{% endfor%}
{% endfor %}
{% endblock content %}
Also you have a mistake there:
context['category'] = Category.objects.all()
If you want to access it as category in template put it there with that key, not cat.

Trying to show latest record - Django

Models
class Category(models.Model):
class Meta():
verbose_name_plural = "Categories"
cat_name = models.CharField(max_length=50)
description = models.TextField()
def get_forums(self):
get_forum = Forum.objects.filter(category=self)
return get_forum
def __str__(self):
return f"{self.cat_name}"
class Forum(models.Model):
class Meta():
verbose_name_plural = "Forums"
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="forums")
parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE)
forum_name = models.CharField(max_length=50)
description = models.TextField()
def __str__(self):
return f"{self.forum_name}"
class Thread(models.Model):
class Meta():
verbose_name_plural = "Threads"
get_latest_by = "date_posted"
forum = models.ForeignKey(Forum, on_delete=models.CASCADE, related_name="threads")
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.title} by: {self.author}"
View
class Home(ListView):
model = Category
template_name = 'forums/index.html'
context_object_name = 'category'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the Cat
context['category'] = Category.objects.all()
return context
HTML
{% block content %}
{% for cat in category %}
<div style="padding-top: 20px;">
<div class="row">
<div class="bg-success rounded-top border border-dark" style="width:100%; padding-left:8px;">
{{ cat.cat_name }}
</div>
</div>
{% for forum in cat.forums.all %}
<div class="row">
<div class="bg-secondary border border-dark" style="width:100%; padding-left:16px;">
{{ forum.forum_name }}
{% for threads in forum.threads.all %}
<div class="float-right" id="latest-post">
<p>{{ threads.title }}</p>
<p> {{ threads.author.username }} </p>
</div>
{% endfor %}
</div>
</div>
{% endfor%}
</div>
{% endfor %}
{% endblock content %}
Problem
I am building a forums and am trying to get my homepage to show the last post in a forum.
I have got it to work to show all threads, but i just want the latest one to show on the latest post div.
I setup the get_latest_by on the Thread model so that it gets the latest one by the time created.
How would i be able to get this to display the latest thread?
You can set a property on the Form model, and then call it in the template.
views.py
class Form(models.Model):
...
#property
def get_newest_thread(self):
return self.threads.all().order_by('date_posted').first()
html
{% with threads=forum.get_newest_thread %}
<div class="float-right" id="latest-post">
<p>{{ threads.title }}</p>
<p> {{ threads.author.username }} </p>
</div>
{% endwith %}

Passing Two Models in a Single View

I'm trying to show information from two models, in a single view in a Django project.
I have 2 models: Main (parent), Visits (child)
I would like to show a details view of Main (name, date of birth) and then show a list of the Visits. Effectively, show one record from parent table, and all the related children tables. But the children tables are only partially showing up (see the image).
Also, can someone tell me how the Django code knows to render only the child records that are associated with the parent record (where/when are foreign keys filtered?)
Image showing the problem
eg:
Main.name
Visit.date - Visit.type
Visit.date - Visit.type
Visit.date - Visit.type
views.py
class MainDetailView(generic.DetailView):
model = Main
template_name = "myapp/main-detail.html"
def get_context_data(self, **kwargs):
context = super(MainDetailView, self).get_context_data(**kwargs)
context['visit'] = Visit.objects.all()
# And so on for more models
return context
models.py
class Visit(models.Model):
fk_visit_patient = models.ForeignKey(Main, on_delete=models.CASCADE,
verbose_name=('Patient Name'))
visit_date = models.DateField()
visit_label = models.CharField(max_length=256, blank=True, null=True)
visit_specialty_list = (
(str(1), 'General Practice'),
(str(2), 'Internal Medicine'),
(str(3), 'Surgery'),
visit_specialty = models.CharField(
max_length=256,
choices=visit_specialty_list,
default=1, )
def __str__(self):
return str(self.visit_label)
def get_absolute_url(self):
return reverse('myapp:main-detail', kwargs={'pk': self.pk})
template.html
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-md-7">
<div class="'panel panel-default">
<div class="panel-body">
<h1>{{ main.name }}</h1>
<p><strong>Date of Birth:</strong> {{ main.date_of_birth }}</p>
<div style="margin-left:20px;margin-top:20px">
<h4>Visits</h4>
{% for visit in main.visit_set.all %}
<li>{{ Visit.visit_date }} - {{ Visit.visit_specialty }}</li>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
You loop defines the variable as visit, but inside you refer to Visit. You need to be consistent.
{% for visit in main.visit_set.all %}
<li>{{ visit.visit_date }} - {{ visit.visit_specialty }}</li>
{% endfor %}
Note, there is no need to pass the Visits separately to the template as you are doing in get_context_data - you should remove that method completely.