Nested Loops in Django - 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()

Related

Django Display count of database entries related via foreign key

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

How to slice items generated from a forloop with conditionals in django

I have a list of categories as well as a list of products my template is in such a manner that it has category sections each with a display of products that belong to said categories. I created a for loop for categories so as to easily display category sections for each category I create. I then went on to create a forloop for products within the category forloop with a condition so as to match products with their actual category before they are displayed under their category section. how can I slice the resulting products to limit the number of products shown
Models.py
class Category(models.Model):
name = models.CharField(max_length=120)
image_263x629 = models.ImageField(upload_to='cat_imgs')
image_263x629_2 = models.ImageField(upload_to='cat_imgs')
image_263x629_3 = models.ImageField(upload_to='cat_imgs')
img_array = [image_263x629, image_263x629_2, image_263x629_3]
description = models.CharField(max_length=250)
def __str__(self):
return self.name
class SubCategory(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=300)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.name
#def get_absolute_url(self):
# return reverse('subcat_detail', args=[str(self.id)])
class Product(models.Model):
name = models.CharField(max_length=120)
price = models.FloatField()
image_182x182 = models.ImageField(upload_to='pdt_imgs/')
image_1200x1200 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
image_600x600 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
image_600x600_2 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
image_300x300 = models.ImageField(upload_to='pdt_imgs/alt_imgs/')
img_array = [image_1200x1200, image_600x600, image_600x600_2]
sku = models.IntegerField()
available = models.BooleanField(default=True)
discount = models.IntegerField(default = 0)
description = models.CharField(max_length=120, blank=True, null=True)
brand = models.CharField(max_length=120, blank=True, null=True)
category = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
seller = models.ForeignKey(Seller, on_delete=models.CASCADE)
Views
class HomePageView(ListView):
model = SubCategory
template_name = 'home.html'
queryset = SubCategory.objects.all()
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
context['products'] = Product.objects.all()
context['pdts'] = Product.objects.order_by('?')[:12]
context['categories'] = Category.objects.all()
context['subcategories'] = SubCategory.objects.all()
return context
Template
{% for category in categories %}
<div class="ps-block--products-of-category">
<div class="ps-block__categories">
<h3>{{ category.name }}</h3>
<ul>
{% for subcategory in subcategories %}
{% if subcategory.category.name == category.name %}
<li>{{ subcategory.name }}</li>
{% endif %}
{% endfor %}
</ul><a class="ps-block__more-link" href="{% url 'cat_detail' category.id %}">View All</a>
</div>
<div class="ps-block__slider">
<div class="ps-carousel--product-box owl-slider" data-owl-auto="true" data-owl-loop="true"
data-owl-speed="7000" data-owl-gap="0" data-owl-nav="true" data-owl-dots="true" data-owl-item="1"
data-owl-item-xs="1" data-owl-item-sm="1" data-owl-item-md="1" data-owl-item-lg="1" data-owl-duration="500"
data-owl-mousedrag="off">
<img src="{{ category.image_263x629.url }}" alt="">
<img src="{{ category.image_263x629_2.url }}" alt="">
<img src="{{ category.image_263x629_3.url }}" alt="">
</div>
</div>
<div class="ps-block__product-box">
{% for product in products %}
{% if product.category.category.name == category.name %}
<div class="ps-product ps-product--simple">
<div class="ps-product__thumbnail"><a href="{% url 'pdt_detail' product.id %}"><img src="{{ product.image_300x300.url }}"
alt=""></a>
{% if product.discount > 0 %}
<div class="ps-product__badge">-{{ product.discount }}%</div>
{% endif %}
{% if product.available == False %}
<div class="ps-product__badge out-stock">Out Of Stock</div>
{% endif %}
</div>
<div class="ps-product__container">
<div class="ps-product__content" data-mh="clothing"><a class="ps-product__title"
href="{% url 'pdt_detail' product.id %}">{{ product.name }}</a>
<div class="ps-product__rating">
<select class="ps-rating" data-read-only="true">
<option value="1">1</option>
<option value="1">2</option>
<option value="1">3</option>
<option value="1">4</option>
<option value="2">5</option>
</select><span>01</span>
</div>
<p class="ps-product__price sale">UGX{{ product.price }}</p>
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
Please do not filter in the template. You should filter in the view. A template implements rendering logic, not business logic*.
You can filter and slice in the view with:
def my_view(request):
# …
products = Product.objects.filter(category__name='specified category')[:10]
context = {
'products': products
}
return render(request, 'my_template.html', context)
This is not only the place where filtering belongs, it is also more efficient since we here will filter and slice on the database side. Typically a database can do this more efficient, and it furthermore limits the bandwidth from the database to the Django/Python layer.
Note (based on #SLDem's comment):
If you aim to filter children, you make use of a Prefetch object [Django-doc]. Indeed, imagine that we have a QuerySet of Categorys and we want to only retain Products that are available, we can use:
from django.db.models import Prefetch
categories = Category.objects.prefetch_related(
Prefetch(
'product_set',
Product.objects.filter(available=True),
to_attr='available_products'
)
)
then in the template we can render this with:
{% for category in categories %}
{% for product in category.available_products %}
…
{% endfor %}
{% endfor %}

How can I include a template inside another template?

Im new to programming and I looking if there is there a way that I can include a template inside other template. Im working on a project that I want to display on the same page a content of a certain topic and flashcards to test my knowledge of this topic, and Im stuck when trying to display my cards on the same page (I could only make it work using another url). Here is what I have so far:
models.py:
class Topic(models.Model):
author = models.ForeignKey(
User, related_name="topic", on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=100)
body = RichTextUploadingField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(max_length=120)
class Meta:
ordering = ["title"]
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('topic:topic-detail', kwargs={
"topic_slug": self.slug,})
class Deck(models.Model):
deckTopic = models.ForeignKey(
Topic, null=True, blank=True, on_delete=models.CASCADE)
description = models.CharField(max_length=510, null=False, blank=True)
is_active = models.BooleanField(default=False)
def __str__(self):
return self.description
def get_number_of_cards(self):
'''
Returns the number of cards in the decks related card_set
'''
return self.card_set.count()
get_number_of_cards.short_description = 'Card Count'
class Card(models.Model):
parentDeck = models.ForeignKey(Deck, on_delete=models.CASCADE)
front = models.TextField()
back = models.TextField()
def __str__(self):
return self.front
def has_prev_card(self):
'''
Returns true if card is not thee first card in the deck.
'''
first_card_in_deck = self.parentDeck.card_set.first()
if self == first_card_in_deck:
return False
return True
def get_prev_card(self):
'''
Return previous card in deck
'''
return self.parentDeck.card_set.filter(id__lt=self.id).last()
def has_next_card(self):
'''
Returns true if card is not the last card in the deck.
'''
last_card_in_deck = self.parentDeck.card_set.last()
if self == last_card_in_deck:
return False
return True
def get_next_card(self):
'''
Return next card in deck
'''
return self.parentDeck.card_set.filter(id__gt=self.id).first()
views.py:
class TopicDetailView(DetailView):
model = Topic
def viewDeck(request, deck_id):
'''
Gets the deck from the database.
Return first card in deck unless card_id is specified in url.
'''
deck_obj = get_object_or_404(Deck, id=deck_id)
card_list = deck_obj.card_set.all()
card_obj = card_list.first()
if request.method == 'GET' and 'card' in request.GET:
card_obj = get_object_or_404(Card, id=request.GET['card'])
context = {
'deck_obj': deck_obj,
'card_obj': card_obj,
}
return render(request, 'topic/viewDeck.html', context)
topic_detail.html:
{% extends 'base.html' %}
{% block content %}
<div class="topic-title">
<h5 class="mb-0">
{{object.title}}
</h5>
</div>
<div class="topic-body">
<p class="mb-0">
{{object.body}}
</p>
</div>
<!--Here is where I want to include the flashcard--!>
<div class="topic-deck">
{% for deck in object.deck_set.all %}
{{deck.description}}
Flashcard
{% endfor %}
</div>
{% endblock %}
viewDeck.html:
{% extends 'base.html' %}
{% block content %}
<div class="deck-container">
<div class="deck-header">
<div class="deck-header-title">
<h1>{{deck_obj}}</h1>
</div>
<div class="deck-header-actions">
<p>Return to Topic</p>
</div>
</div>
<hr>
{% if card_obj %}
<div class="notecard">
<div class="notecard-nav text-center">
{% if card_obj.has_prev_card %}
<a href="{% url 'topic:viewDeck' deck_obj.id %}?card=
{{card_obj.get_prev_card.id}}">Prev</a>
{% endif %}
{% if card_obj.has_next_card %}
<a href="{% url 'topic:viewDeck' deck_obj.id %}?card=
{{card_obj.get_next_card.id}}">Next</a>
{% endif %}
</div>
<div class="notecard-front">
<p class="text-center">Front</p>
<p>{{card_obj.front}}</p>
</div>
<div class="notecard-back">
<p class="text-center">Back</p>
<p>{{card_obj.back}}</p>
</div>
</div>
{% else %}
<p>No card found.</p>
{% endif %}
</div>
{% endblock %}
Is there a way to see deckView inside topic_detail.html? Sorry if I didn't made myself clear.
Update:
I tried inserting {% include 'viewDeck.html' %} as passing the context deck_obj, card_list and card_obj to topic_detail.html, but it gives me this error: NameError: name 'deck_id' is not defined. How can I fix this?
Thank you.
Did you build the url correctly ?
To be able to pass the deck_id like you did in your view, you have to tell your url that you want to pass some data in, by writing something like this in your urls.py :
from name_of_your_app import views
url(r'deck/(?P<deck_id>\d+)/$', views.viewDeck , name='name_of_you_url'),
(?P<deck_id>\d+) is the important part, as it indicates that you will receive a numerical data which can be called with the name "deck_id".

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.

For loop for ManytoMany Field Type

How should I create a for loop in my template so that I can show a ManyToMany relationship?
Given that:
in models.py I have
class Cycle(models.Model):
cycle_name = models.CharField(max_length=150)
cycle_description = models.CharField(max_length=250)
def __str__(self):
return self.cycle_name + " -- " + self.cycle_description
class Program(models.Model):
program_name = models.CharField(max_length=50)
program_description = models.CharField(max_length=250)
cycles = models.ManyToManyField(Cycle)
is_favourite = models.BooleanField(default="False")
def get_absolute_url(self):
return reverse('programs:program', kwargs={'pk': self.pk})
def __str__(self):
return self.program_name
in views.py
class AllProgramsView (generic.ListView):
template_name = 'programs/index.html'
context_object_name = 'programs_list'
def get_queryset(self):
return Program.objects.all()
class ProgramDetailView (generic.DetailView):
model = Program
template_name = 'programs/program.html'
in urls.py
#list of all programs
url(r'^$', views.AllProgramsView.as_view(), name='index'),
#single program page
url(r'^(?P<pk>[0-9]+)/$', views.ProgramDetailView.as_view(), name='program'),
In the single program page I need to list all the cycles that are contained in that specific program.
I've been trying so many different things but none seems to be right.
Here's my current take for the template, which however does not work:
program.html
<div class="bg-white">
<div class="container text-center text-muted">
<div class="row">
{% if cycle %}
{% for cycle in program.cycles.all() %}
<div class="col-sm-4 py-4">
<div class="card">
<p><h5>{{ cycle.cycle_name }}</h5></p>
<p class="card-text">{{ cycle.cycle_description }}</p>
Modify it
</div>
</div>
{% endfor %}
{% else %}
<div class="col-sm-12">
<p>No cycles found</p>
</div>
{% endif %}
</div>
</div>
Can someone help, please? Thanks.
I think that you get no context because the if statement is wrong.
Instead of
{% if cycle %}
it should be
{% if program.cycles %}