Rendering ForeignKey objects in template with Django - django

I'm having trouble rendering related objects in a template. I have a Page model, that has a ForeignKey relationship to itself to define parent-child relationships.
class Page(BaseModel):
title = models.CharField(
max_length=280,
blank=False,
null=False,
)
description = models.CharField(
max_length=280,
blank=False,
null=False,
)
slug = models.SlugField(
blank=False,
null=False,
)
is_home = models.BooleanField(
default=False,
)
is_parent = models.BooleanField(
default=False,
)
parent = models.ForeignKey(
'self',
on_delete=models.PROTECT,
default='Home',
blank=True,
null=True,
related_name='children',
)
content = RichTextField(
blank=False,
null=False,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('page_detail', args=[str(self.slug)])
My views.py filters out non-parent pages:
class PageListView(ListView):
queryset = Page.objects.filter(is_parent=True, is_home=False)
template_name = 'pages/page_list.html'
context_object_name = 'page'
But when it comes to rendering 'child' objects in my template, I'm stuck. I've figured that I need a loop within a loop (first for parent pages, second for child pages per parent page), but can't get the second loop to work. Here was my latest attempt, which got me a "'Page' object is not iterable" error.
{% extends '_base.html' %}
{% block content %}
<div class="container">
{% for page in page %}
<p>{{ page.title }}</p>
{% for children in page %}
<p>{{ page.title }}</p>
{% endfor %}
{% endfor %}
</div>
{% endblock content %}

You can iterate over the manager constructed by the related_name:
{% block content %}
<div class="container">
{% for page in page %}
<p>{{ page.title }}</p>
{% for child in page.children.all %}
<p>{{ child.title }}</p>
{% endfor %}
{% endfor %}
</div>
{% endblock content %}
You can boost efficiency with a .prefetch_related(…) [Django-doc] clause:
class PageListView(ListView):
queryset = Page.objects.filter(
is_parent=True, is_home=False
).prefetch_related('children')
template_name = 'pages/page_list.html'
context_object_name = 'page'
I would furthermore advise not to create an extra field is_parent, since this is a form of data duplication. It turns out that keeping fields in sync, even on the same database, might be harder than what it appears to be at first sight. You can check if the object is a parent with:
class PageListView(ListView):
queryset = Page.objects.filter(
children__isnull=False, is_home=False
).distinct().prefetch_related('children')
template_name = 'pages/page_list.html'
context_object_name = 'page'

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

How to delete a record only if the user posted is the logged in user

I have multiple users in my project
my models.py file is
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
class Teacher(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,primary_key=True,related_name='Teacher')
name = models.CharField(max_length=250)
subject_name = models.CharField(max_length=250)
email = models.EmailField(max_length=254)
phone = models.IntegerField()
teacher_profile_pic = models.ImageField(upload_to="classroom/teacher_profile_pic",blank=True)
def __str__(self):
return self.name
class Announcement(models.Model):
title = models.CharField(max_length=30)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
def __str__(self):
return self.title
If the logged in user is a teacher it is allowed to create an announcement
Now i want that only the teacher who posted the announcement should be able to see the delete button
My html file is
{% extends "classroom/base.html" %}
{% block content %}
<h1>Announcements</h1>
{% for announcement in announcements %}
<!-- starting loop (posts is keyword from view) -->
<div style="border-style: solid;">
{% if object.teacher.id == request.teacher %}
<div>
Delete
</div>
{% endif %}
<a class="mr-2">Posted by: {{ announcement.teacher }}</a>
<h2><a class="article-title">{{ announcement.title }}</a></h2>
<p class="article-content">{{ announcement.content}}</p>
</div>
{% endfor %}
{% endblock content %}
the if statement is supposed to be true if logged in teacher is the teacher who originally posted it. However the delete button is visible for every announcement
my views.py has
class AnnouncementListView(ListView):
context = {
'announcements' : Announcement.objects.all()
}
model = Announcement
template_name = 'classroom/all_announcements.html'
context_object_name = 'announcements'
Try using this.
{% if announcement.teacher.user == request.user %}
<div>
Delete
</div>
{% endif %}
Your models are a bit "unconventional".
However, this should work:
{% if announcement.teacher.user == request.user %}
...
{% endif %}

nothing is displayed on the page with all products in Django

There is a model with three classes of category-subcategory-products
class Category(models.Model):
name_category = models.CharField(verbose_name = 'name cat', max_length = 100, null=True)
image = models.ImageField(null=True, blank=True, upload_to="media/", verbose_name='pic')
slug = models.SlugField(max_length=160, unique=True, null=True)
def __str__(self):
return self.name_category
class Subcategory(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='категория', related_name='sub')
name_subcategory = models.CharField(verbose_name = 'name subcat', max_length = 100, null=True)
image = models.ImageField(null=True, blank=True, upload_to="media/", verbose_name='pic')
slug = models.SlugField(max_length=160, unique=True, null=True)
def __str__(self):
return self.name_subcategory
class Product(models.Model):
subcategory = models.ForeignKey(Subcategory, on_delete=models.CASCADE, verbose_name='категория',related_name='prod')
name_product = models.CharField(verbose_name = 'name product', max_length = 100, null=True)
image = models.ImageField(null=True, blank=True, upload_to="media/", verbose_name='pic')
price = models.IntegerField('price')
def __str__(self):
return self.name_product
views.py
class CategoryView(ListView):
"""all category"""
model = Category
class CategoryDetailView(DetailView):
"""all sub category"""
model = Category
class SubcategoryView(ListView):
"""all product"""
model = Subcategory
url.py
urlpatterns = [
path("", views.CategoryView.as_view()),
path('<slug:slug>/', views.CategoryDetailView.as_view(), name='category_detail'),
path('<slug:slug>/<slug:slug_sub>/', views.SubcategoryView.as_view(), name='subcategory_list'),
]
page template from which I go to the page with all products (category_detail.html)
{% extends 'base.html' %}
{% block content %}
<h2>{{ category.name_category }}</h2>
{% for sub in category.sub.all %}
{{sub.name_subcategory}}
<img src="{{sub.image.url}}" width="100px" height="100px">
{% endfor %}
{% endblock %}
page template (subcategory_list.html) with all products (here I did not write the output of the products because even the name of the subcategory is not transmitted)
{% extends 'base.html' %}
{% block content %}
{{sub.name_subcategory}}
{% endblock %}
I just can’t understand why it doesn’t display anything on the last page. Perhaps the problem is in the classes in views since previously tried to write only through functions
By default, on DetailView, Django sends an object to the context and therefore that's what you should access in your template. You can see it here
{% extends 'base.html' %}
{% block content %}
<h2>{{ object.name_category }}</h2>
{% for sub in object.sub.all %}
{{ sub.name_subcategory }}
<img src="{{ sub.image.url }}" width="100px" height="100px">
{% endfor %}
{% endblock %}
You also will need a SubCategoryDetailView if you want to see details of that. If you want to override how Django passes data to the context, you can declare context_object_name with whatever you like. Please also have a look here

Not able to display in Template the foreign key using ListView

I'm trying to display a photographic session with all of it photos, so I have the session name in one model and I have the pictures in
another model linked by a foreign key. I'm not able to display it in
the HTML I'm not sure if I'm using the ListView get_context_data
correctly and I'm certainly sure the the html code is not correct but
I have not found how to do it.
views.py
class SessionPictures(generic.ListView):
model = PostSession
template_name = 'photoadmin/gallery.html'
def get_context_data(self, **kwargs):
context = super(SessionPictures, self).get_context_data(**kwargs)
context['picture'] = Images.objects.all()
return context
models.py
class PostSession(models.Model):
session_name = models.CharField(max_length=25)
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.session_name)
class Images(models.Model):
name = models.ForeignKey(
PostSession, related_name='images', on_delete=models.CASCADE, null=True, blank=True)
picture = models.ImageField(upload_to='pictures')
html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<h2>Images</h2>
<ul>
{% for session in object_list %}
<li>{{ session.session_name }}</li>
<ul>
<li>{{session.picture_set.all.url}}</li>
</ul>
{% endfor %}
</ul>
{% endblock %}
I'm expecting this:
Woods
picture1.url
picture2.url
picture3.url
Beach
Picture4.url
picture5.rul
As you already defined related_name="images" in Images model, so, session.images_set attribute won't work with PostSession.
class Images(models.Model):
name = models.ForeignKey(
PostSession,related_name='images', on_delete=models.CASCADE, null=True, blank=True)
Instead, use session.image.all in template(FYI: it returns a queryset, so you need to iterate through it to get the image object):
{% for session in object_list %}
<li>{{ session.session_name }}</li>
<ul>
{% for i in session.images.all %}
<li> i.picture.url </li>
{% endfor %}
</ul>
{% endfor %}
More information on reverse relation can be found in documentation.

Get Featured Image from different Model

I have 2 model objects, Business & BusinessImage as so, listed with views and index.html. I am trying to list the business's featured image, but it's not happening. I am getting the following error:
'QuerySet' object has no attribute 'businessimage_set'
How can I get the business featured image for a list?
Business
class Business(models.Model):
name = models.CharField("Name", max_length=70, default="Business Name")
slug = models.SlugField()
description = models.TextField("About", max_length=400)
category = models.ManyToManyField(Category, verbose_name="Categories", blank=True)
order = models.IntegerField("Order", default=0)
claimed = models.BooleanField("Claimed", default=False)
featured = models.BooleanField("Featured", default=False)
class Meta:
ordering = ['order']
verbose_name = "Business"
verbose_name_plural = "Businesses"
def __str__(self):
return self.name
BusinessImage
class BusinessImage(models.Model):
business = models.ForeignKey(Business)
image = models.ImageField(upload_to="images/business")
title = models.CharField(max_length=120)
featured = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.title
view.py
from .models import Business, BusinessImage
def index_view(request):
latest_business_list = Business.objects.all()
images = latest_business_list.businessimage_set.all()
template = loader.get_template('index.html')
context = RequestContext(request, {
'latest_business_list': latest_business_list,
'images': images,
})
return HttpResponse(template.render(context))
index.html
{% block content %}
<div class="text-center business_title">
<h2>Featured</h2>
</div>
{% if latest_business_list %}
{% for business in latest_business_list|slice:":4" %}
{% if business.active %}
<div class="col-sm-6 col-md-3">
<li>{{ business.name }}</li>
{% for image in latest_business_list.businessimage_set.all %}
{% if image.featured %}
<a href="{% url 'single_product' product.slug %}">
<img src="{{MEDIA_URL}}{{image.image}}" alt="{{image}}">
</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}
businessimage_set is an attribute of a Business instance, but you're trying to access it as an attribute of a queryset (i.e. list of businesses). If your goal is just to be able to access the images for each business in a template, you can leave out images entirely. Instead your template would have:
{% for image in business.businessimage_set.all %}
(Though look into prefetch_related for efficiency.)