Retrieve info from many-to-many relationship in django temaplate - django

I am creating a blog and have a many-to-many relationship between the posts and categories.
class Category(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField()
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=255)
subtitle = models.CharField(max_length=255,null=True,blank=True)
published_date = models.DateTimeField(auto_now_add=True)
draft = models.BooleanField(default=True)
body = RichTextField(config_name='blog')
slug = models.SlugField()
categories = models.ManyToManyField(Category)
featured = models.BooleanField(default=False)
I am trying to retrieve the the list of categories associated to an individual post within the template so I can display those category titles at the bottom of the post.
Here is the template code which displays the posts properly but not the categories.
{% for post in blog_posts %}
<div class="post">
<div class="date">
{{post.published_date|date:"M"}}
<span class="day">{{post.published_date|date:"d"}}</span>
<span>{{post.published_date|date:"Y"}}</span>
</div>
<div class="entry">
<div class="page-header">
<h2 class="post-title">{{post.title}}</h2>
</div>
<blockquote>
<p><strong>{{post.subtitle}}</strong></p>
</blockquote>
<p>{{post.body|safe}}</p>
<div class="well well-small">
<i class="icon-th-list "></i> Categories:LIST CATEGORIES HERE
</div>
</div> <!--entry div-->
</div><!--post div-->
{% endfor %}
Does anyone have thoughts on how I could retrieve the categories for a specific post? I greatly appreciate the time and expertise.

You can access the categories with this
{% for category in post.categories.all %}
{{ category.title }}
{% endfor %}
I also recommend adding .prefetch_related('categories') to the queryset in your view to reduce the number of sql queries.

Related

Django: How to Join One-to-many relationship? / Show joined query in template

I am new in Django. I am trying to make a clone of craiglist. Currently I have to models: Posts and Media (images). One post can have many Media. I want to show first image that was posted by user when they create a post as well as info about post on the index page.
Unfortunately I can't properly join Media when I refer to Posts and vice versa. How to properly show post and media instances in index template?
Models:
class Post(models.Model):
title = models.CharField(max_length=50)
description = models.CharField(max_length=1500)
user = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
subcategory = models.ForeignKey(Subcategory, on_delete=models.SET_NULL, null=True)
price = models.DecimalField(max_digits=12, decimal_places=2)
city = models.CharField(max_length=200)
class Media(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
name = models.CharField(max_length=500, null=True, blank=True)
photo = models.ImageField(upload_to='photo/%Y/%m/%d/', null=True, blank=True)
Views:
def index(request):
posts = models.Media.objects.all()
paginator = Paginator(posts, 2)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'aggregator/index.html', {'page': page_obj})
index.html template:
{% for media in page %}
<div class="col">
<div>
<p>Picture</p>
</div>
<div>
<h4>{{ media.post.title|upper }}</h4>
</div>
<div>
<h5>{{media.post.price}}$</h5>
</div>
<div>
<h6>{{media.post.city}}</h6>
</div>
</div>
{% endfor %}
So again. How to normally join first media related to the posts? Like in the attached screenshot.
I found a solution to get posts from Media:
posts = models.Media.objects.all()
instead of getting posts themselves (i know this is horribly wrong) because I do not understand how to join Media: select_related and prefetch_related don't work - there is an error like this:
posts = models.Post.objects.select_related('Media').all()
Invalid field name(s) given in select_related: 'Media'. Choices are: user, category, subcategory
You can prefetch with:
def index(request):
posts = models.Post.objects.prefetch_related('media_set')
paginator = Paginator(posts, 2)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'aggregator/index.html', {'page': page_obj})
Then in the template render with:
{% for post in page %}
<div class="col">
<div>
{% if post.media_set.all.0.photo %}
<img src="{{ post.media_set.all.0.photo.url }}">
{% endif %}
</div>
<div>
<h4>{{ post.title|upper }}</h4>
</div>
<div>
<h5>{{ post.price }}$</h5>
</div>
<div>
<h6>{{ post.city }}</h6>
</div>
</div>
{% endfor %}
The .prefetch_related is not even necessary, but it will slow the view down, because then it will make a query per Post, not one extra query to fetch all Media objects.

Tiltled previous and next blog using django class based view

If I open a blog detail, there should be the previous blog and next blog title as a pagination. From Django documentation, I implemented the pagination by page number. But how can I implement the pagination by previous blog and next blog title.
Here is my model.py:
class Post(models.Model):
title = models.CharField(max_length=250, unique=True)
slug = models.SlugField(max_length=250, unique=True, null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="blog_posts")
cover = models.ImageField(upload_to="Uploaded/")
content = RichTextUploadingField(blank=True, null=True)
sticker = models.CharField(max_length=50, unique=False, null=True, blank=True)
published_at = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
status = models.IntegerField(choices=STATUS, default=0)
tags = models.CharField(max_length=400, null=True, blank=True)
class Meta:
ordering = ['-published_at']
def __str__(self):
return self.title
views.py
class PostDetail(generic.DetailView):
model = Post
template_name = 'blogs/post_detail.html'
post_detail.html
{% extends "blogs/base.html" %}
{% load static %}
{% block post %}
<section class="services">
<dev class="box-container">
<div class="box-lg">
<h3>{{ post.title }}</h3>
<img src="/Images/{{ post.cover }}" >
<hr>
<small>{{ post.published_at }}</small>
<p>{{ post.content | safe }}</p>
</div>
</dev>
</section>
<!-- Pagination Start -->
<div class="center">
</div>
<!-- Pagination Ends -->
{% endblock post %}
Now please help me how to do the prev-next blog titled pagination like the below image!
titled pagination image
One way you can handle this is to use .get_previous_by_Foo and .get_next_by_Foo you can read more here : https://docs.djangoproject.com/en/2.1/ref/models/instances/#django.db.models.Model.get_next_by_FOO
This is just an idea how you can do it.
{% extends "blogs/base.html" %}
{% load static %}
{% block post %}
<section class="services">
<dev class="box-container">
<div class="box-lg">
<h3>{{ post.title }}</h3>
<img src="/Images/{{ post.cover }}" >
<hr>
<small>{{ post.published_at }}</small>
<p>{{ post.content | safe }}</p>
</div>
</dev>
</section>
<!-- Pagination Start -->
<div class="center">
{{ post.get_previous_by_published_at.title }}
</div>
<!-- Pagination Ends -->
<div class="center">
{{ post.get_next_by_published_at.title }}
</div>
{% endblock post %}
as i said this just an idea in real life you have to check that next or previous post exists or maybe that post is the last one a thing like that...

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 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.

Django template loop through items with parent ID or PK

I'm trying to set up magnific popup on django.
My goal is to have one main picture in the homepage overview gallery view, which when clicked, would open a popup with the related images from the same photoshoot i.e. images with the same ID or PK.
I tried to apply the following approach
but i just cannot get it to work, maybe someone could help me out in this
My models.py
class Item(models.Model):
name = models.CharField(blank=False, max_length=200)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
order = models.IntegerField(blank=True, null=True)
active = models.BooleanField(blank=True, default=False)
objects = models.Manager()
class Meta:
verbose_name_plural = 'items'
def __str__(self):
return self.name
class ItemImage(models.Model):
image = ProcessedImageField(
blank=True,
null=True,
processors=[ResizeToFit(width=1680, upscale=False)],
format='JPEG',
options={'quality':90})
order = models.IntegerField(blank=True, null=True)
main = models.BooleanField(blank=True, default=False)
cover = models.BooleanField(blank=True, default=False)
item = models.ForeignKey(Item, related_name='items', blank=True, null=True, on_delete=models.SET_NULL)
objects = models.Manager()
class Meta:
verbose_name_plural = 'item images'
Views.py
def portraits(request):
port = ItemImage.objects.filter(item__category__slug='portraits', item__active=True, main=True,).order_by('item__order')
portall = ItemImage.objects.filter(item__category__slug='portraits', item__active=True).order_by('item__order')
context = {
'main_portraits': port,
'all_portraits': portall
}
return render(request, 'gallery/portraits.html', context)
and Template:
{% block content %}
<div class="grid">
{% for pic in main_portraits %}
<div class="item">
<div class="item">
<div class="outer-text">
<div class="text">
{{ pic.item.name }}
<p>Click to view gallery</p>
</div>
</div>
<a><img class="lazy" alt=""
sizes="(min-width:1400px) 1220px
(min-width:1000px) 1000px,
(min-width:500px) 700px,
(min-width:320px) 420px,
280px"
srcset="{{ pic.image_xs.url }} 280w,
{{ pic.image_s.url }} 420w,
{{ pic.image_m.url }} 700w,
{{ pic.image_l.url }} 1000w,
{{ pic.image_xl.url }} 1220w" />
</a> {{ pic.item.pk }}
</div>
<div class="lazy">
{% for p in all_portraits %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% endblock %}
I have set
z.item.pk
just as a test, to see if any of my manipulations result in the pk's to bunch up. For example the first for-loop returns a picture with PK "24", I need for the second for-lop to return only images with the same PK; and so for every image. I think the answer might be connected with _set.all function, just like in the related question above, but I cant seem to get it to work in my case. Feels like I'm missing something here.
current output:
<div class="grid">
<div class="item">
<div class="item">
<div class="outer-text">
<div class="text">
Palagā tītā
<p>Click to view gallery</p>
</div>
</div>
<a><img class="lazy" alt=""
sizes="(min-width:1400px) 1220px
(min-width:1000px) 1000px,
(min-width:500px) 700px,
(min-width:320px) 420px,
280px"
srcset="/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/958ba5dbee5efe28fd2f5054b8f819e1.jpg 280w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/02d12ca7f0633fee2fc762cf96f7889e.jpg 420w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/ba5fa6633e92a288e3b2f47a713d64c2.jpg 700w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/fe0d559fef5b02434c43f841005d4961.jpg 1000w,
/media/CACHE/images/IMG_8329_3Vi8mYO_GD621ql/96d0e52dff14d1bc4b60bbec674565db.jpg 1220w" />
</a> 24
</div>
<div class="lazy">
</div>
</div>
You need prefiltered querysets containing the related images for every main image before handing over to the template.
def portraits(request):
ports = ItemImage.objects.filter(item__category__slug='portraits', item__active=True, main=True,).order_by('item__order')
for p in ports:
# You may not need the item__category__slug filter
# if there are only images of the same category
# associated with an item.
# Also, if you want to exclude the main image
# from the set of related images, you need to add the filter
# main=False
p.related_images = ItemImage.objects.filter(item__category__slug='portraits', item__id=p.item.id)
context = {
'main_portraits': ports,
}
return render(request, 'gallery/portraits.html', context)
Then you can loop over main_portraits in the template, and get the related images for each main image in a nested loop:
{% for mainp in main_portraits %}
{% for im in mainp.related_images %}
{# do something with the related images #}
{% endfor %}
{% endfor %}
You can break down the models like this it will make the querying easier.
# models.py
class Item(mdoels.Model):
name = models.CharField(blank=False, max_length=200)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
...
display_image = models.ProcessedImageField(...)
class ItemImage(models.Model):
item = models.ForeignKey(Item, related_name='images', blank=True, null=True, on_delete=models.SET_NULL)
image = models.ProcessedImageField(...)
...
#views.py
def portraits(request):
items = Item.objects.filter(category__slug='portraits', active=True)
return render(request, 'gallery/portraits.html', context={items: items})
#template
{% for item in items %}
<h1> {{item.name}} </h1>
<img src={{item.display_image}} />
{% for item_image in item.images.all %}
<img src={{item_image.image}} />
{% endfor %}
{% endfor %}