How do I get reverse reference in Django template? - python

Apologies if the title doesn't make much sense. I don't quite understand what I lack in knowledge.
I have a Post and Comment models in my Django project. What I'm trying to do is list out all the Blog posts, and show NUMBER OF COMMENTS OF EACH POST. Please see my codes below.
models.py
class Blog(models.Model):
objects = models.Manager()
title = models.CharField(max_length=100, blank=True)
body = models.CharField(max_length=10000, blank=True)
created_at = models.DateField(auto_now_add=False)
class Comment(models.Model):
objects = models.Manager()
post = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='comment')
views.py
def main_page(request):
all_blogs = Blog.objects.all()
context = {
'blog' : blog,
}
return render(request, 'main/home.html', context)
template
{% for b in blog %}
<div>
<p>{{b.title}}</p>
<div>
{{WHERE THE NUMBER OF THIS POST'S COMMENTS IS DISPLAYED}}
</div>
</div>
{% endfor %}
All I need is the number of the comments, but have no idea how to do it. Is there a way to make this possible in the template? Or do I have to add some codes in views.py?

You can annotate the Blog objects with the number of related Comments with:
from django.db.models import Count
def main_page(request):
all_blogs = Blog.objects.annotate(
num_comments=Count('comment')
)
context = {
'blogs' : blogs
}
return render(request, 'main/home.html', context)
The Blog objects that arise from that queryset will have an extra attribute .num_comments with the number of related comments:
{% for blog in blogs %}
<div>
<p>{{ blog.title }}</p>
<div>
{{ blog.num_comments }}
</div>
</div>
{% endfor %}

Related

Django custom template tags

Why my custom template tag doesn't work?
templatetags.py:
from django import template
from ..models import User
register = template.Library()
#register.inclusion_tag('main/post_detail.html', takes_context=True)
def get_user_liked_posts():
request = context['request']
user = User.objects.get(username=request.user.username)
liked_posts = []
for post in user.liked_posts.all():
liked_posts.append(post.name)
return {'liked_posts': liked_posts}
post_detail.html:
{% load static %}
{% load templatetags %}
<nav class="blog-pagination" aria-label="Pagination">
<span id="likes_count">{{ post.likes_count }}</span>
{% if post.name in liked_posts %}
<button id="like_button" class="btn btn-outline-primary btn-primary text-
white">Like</button>
{% else %}
<button id="like_button" class="btn btn-outline-primary">Like</button>
{% endif %}
</nav>
views.py:
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
class LikePostView(View):
def post(self, request, slug):
post = Post.objects.get(id=request.POST['id'])
user = User.objects.get(username=request.user.username)
if request.POST['like'] == 'true':
post.likes_count += 1
user.liked_posts.add(post)
else:
post.likes_count -= 1
user.liked_posts.remove(post)
user.save()
post.save()
return redirect('post_detail', slug)
models.py:
class Post(models.Model):
"""
This is post model
"""
name = models.CharField(max_length=150, blank=False)
article = models.TextField(blank=False)
image = models.ImageField(upload_to='uploads/', blank=True)
likes_count = models.IntegerField(default=0)
url = models.CharField(max_length=150, blank=False)
def get_absolute_url(self):
return reverse('post_detail', kwargs={'slug': self.url})
I want to check if the post is in the liked post of the current user, but it doesn't work.
It doesn't show any errors, it just does nothing.
User in my app must like or unlike posts. In models, I have many to many relationship user with the post. I want to check if the user likes this post
The problem is that you don't even use the template tag, furthermore this is not even needed as you can simply write something like so in the template:
{% if post in request.user.liked_posts.all %}
A Liked post
{% else %}
Not a liked post
{% endif %}
But this is a bit inefficient as we are getting all the posts liked by the user just to check if they like some post. Also if this were in a loop with multiple posts we would be making a query for each post.
Instead we can simply annotate whether the user likes a post in the view itself using an Exists subquery [Django docs] on the through model of the many to many:
from django.db.models import Exists, OuterRef
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
liked_by_user=Exists(
User.liked_posts.through.objects.filter(
post_id=OuterRef("pk"),
user_id=self.request.user.id
)
)
)
return queryset
Now in the template we can simply write:
{% if post.liked_by_user %}
A Liked post
{% else %}
Not a liked post
{% endif %}
Note: Your way of saving the count similarly can simply be turned into an annotation using the Count aggregation function [Django
docs].
Generally one should not store calculated attributes in a column since
that might lead to inconsistent data when updating and forgetting to update the related count, etc.

What is wrong with my nested for Loop in a django Template?

I am trying to create a template with Django that loops through Posts and for each Post loops through all Pictures.
I already looked at some answers to other Questions but I can not find my error.
Models:
class Post(models.Model):
Post_Title = models.CharField(max_length=200)
Post_pub_date = models.DateField('date published')
Post_Author = models.CharField(max_length=100)
Post_Text = models.TextField()
def __str__(self):
return self.Post_Title
class Picture(models.Model):
Post = models.ForeignKey(Post, on_delete=models.CASCADE)
Picture = models.ImageField()
Picture_Name = models.CharField(max_length=100, null=True, blank=True)
def __str__(self):
return self.Picture_Name
Views:
class PostView(generic.ListView):
template_name = 'myblog/index.html'
context_object_name = 'Post_List'
def get_queryset(self):
"""
Returns Posts
"""
return Post.objects.order_by('-Post_pub_date')
Template:
{% for Post in Post_List %}
<h1 class="mb-4">{{Post.Post_Title}}</h1>
<span class="category">{{Post.Post_Author}}</span>
<span class="mr-2">{{Post.Post_pub_date}}</span>
<div class="post-content-body"><p>{{Post.Post_Text}}</p>
{% for Picture in Post.Picture_set.all %}
<div class="col-md-12 mb-4 element-animate">
<h2>{{Picture.Picture_Name}}</h2>
<img class="col-md-12 mb-4 element-animate" src="{{ MEDIA_URL }}{Picture.Picture}}">
</div>
{% endfor %}
</div>
{% endfor %}
The Post_Title, Post_Author, Post_pub_date and Post_Text are displayed fine. Just the nested For loop is not producing any Picture_Name or Picture as if the Picture_set.all is empty.
As mentioned above I tried to find my error in different Posts like this but could not find it.
Thanks for your help.
Following relationship backward, you need to write related model name from a small letter, even if model name starts from large letter:
{% for Picture in Post.picture_set.all %}
This is how it works in Django shell and i suppose in templates it is the same.
The issue isn't the nested for loop it's the view.It only returns a query for your Post, you don't pass any Photos to your template.

List of posts not displaying - django

I have created a post model and would like to view the posts in post_list. While creating the new post, it is redirecting to post_list but not displaying any post.
Also, in my post_form I have rendered the fields manually by using django templates. I couldnt figure out where I have made the mistake. Can someone please help me out. Thanks
models.py
class Post(models.Model):
author = models.ForeignKey(User, on_delete = models.CASCADE)
slug = models.SlugField(unique=True, blank=True, default=uuid.uuid1)
By default the context_object_name is object_list
Either you access your Posts list in template with object_list
{% for post in object_list %}
{{ post }} <!-- with lowercase -->
{% endfor %}
Or you change the context_object_name to post_list, so that way you will be able to access the post list with post_list in template
class PostListView(ListView):
model = Post
context_object_name = 'post_list'
You probably need to use lowercase variable name in your post_list.html.
For instance, {{ Post.title }} should probably be lowercase {{ post.title }}.
There are a few places to change that.

Access Many-to-Many field within Django template

I have the following blog project :
models.py:
from django.db import models
from django.utils import timezone
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
pub_date = models.DateTimeField(default=timezone.now)
categories = models.ManyToManyField('Category')
def __str__(self):
return self.title
class Category(models.Model):
title = models.CharField(max_length=200)
def __str__(self):
return self.title
# Change the name in Admin from categorys to categories
class Meta:
verbose_name_plural = "categories"
views.py:
from django.shortcuts import render
from .models import Post, Category, Comment
def getPosts(request):
posting = Post.objects.all().order_by('-pub_date')
categories = Category.objects.all()
context = {
'posting':posting,
'categories':categories,
}
return render(request, 'posts/getPosts.html', context)
getPosts.html template :
{% if posting %}
{% for article in posting %}
<h3>{{article.title}}</h3>
<ul>{{article.body}}</ul>
<ul>Posted : {{article.pub_date}}</ul>
<ul>
<em>Found in category : </em>
{{ article.categories }}
{{ article.categories.all }}
{% for category in categories %}
{{category.title}}
{% endfor %}
</ul>
{% endfor %}
{% endif %}
I have three posts, which all display properly, but
{{article.categories}} is giving me:
posts.Category.None
{{article.categories.all}} gives me
QuerySet [<Category: Diving>]
And the second loop outputs the list of all categories, which I expected as just a test run:
Kit & Packing Diving Places Tips Private
I am trying to simply pull through the category name for each post, which has been selected in the admin panel and saved through the admin panel.
I have tried what feels like a thousand different suggestions, such as changing the view to category = post.category_set.all(), and have been researching this for days now, but am getting no-where.
You already have the right answer; article.categories.all, which you should loop over.
{% for category in article.categories.all %}
{{category.title}}
{% endfor %}
You don't need the categories value in the view at all.

Showing one-to-many relationship in Django views

I am making a django blog and want to show a list of comments for each blog post, but I have trouble figuring out how to reference the comments in the views and the templates.
My models are defined like this:
class Issue(models.Model):
title = models.CharField(max_length=255)
text = models.TextField()
author = models.ForeignKey(User)
def __unicode__(self):
return self.title
class Comment(models.Model):
commenter = models.ForeignKey(User)
issue = models.ForeignKey(Issue)
text = models.TextField()
and my views like this
class IssueDetail(DetailView):
model = Issue
context_object_name = "issue"
template_name = "issue_detail.html"
def get_context_data(self, **kwargs):
context = super(IssueDetail, self).get_context_data(**kwargs)
context['comments'] = Comment.objects.all()
return context
class CommentDetail(DetailView):
model = Comment
context_object_name = "comment"
template_name = "comment_detail.html"
and finally the issue_detail.html template
{% block content %}
<h2>{{ issue.title }}</h2>
<br/>
<i>As written by {{ issue.author.first_name }}</i>
<br/><br/>
<blockquote> {{ issue.text }}</blockquote>
<h3>Comments</h3>
{% for comment in comments %}
<li>{{comment}}</li>
{% endfor %}
{% endblock %}
This allows me to reference the fields of the comment inside the Issue template, but basically then I want the comments to have a template of their own that will be rendered inside the for loop. What is the correct way to do this in Django?
The comments are already available in your template because of the model relationship you defined. You can delete the get_context_data in IssueDetail.
Your issue_detail.html template could look like this:
{% for comment in issue.comment_set.all %}
{% include 'comment_detail.html' %}
{% endfor %}
Your comment_detail.html template could look like this:
<ul>
<li>{{ comment.issue }}</li>
<li>{{ comment.text }}</li>
</ul>
what if this we were using a different model
product = models.ForeignKey(Customer)
how would we do the CRUD opertions from the templates an the views.py

Categories

Resources