Getting the number of comments from a specific post in django - python

I have a little problem with a query. I work on a blog website with django. For posts I have the first page where i display all the posts as a list, with their details (title, date posted etc.) and I want to display the number of comments for each post along with title, date posted and tags. I'm not sure how to make that, I need to implement something on the model classes or in view function that renders the page ?
Here are the model classes.
class Post(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=500)
content = models.TextField()
tags = models.CharField(max_length=100)
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
comment_text = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return f'{self.user.username} Comment ' + str(self.id)
and the view function
def blog(request):
context = {
'posts': Post.objects.all(),
'title': 'Blog',
'banner_page_title': 'Blog',
'page_location': 'Home / Blog'
}
return render(request, 'blog/blog.html', context)

Method 1:
You can use this in your template.
{% for post in posts %}
{{ post.comment_set.count }}
{% endfor %}
Method 2:
You can implement a model method like this:
class Post(models.Model):
....
def __str__(self):
return self.title
#property
def comment_count(self):
return self.comment_set.count()
And you can call the model method in your template like this:
{% for post in posts %}
{{ post.comment_count }}
{% endfor %}

Use a reverse lookup in the template and count to get the number of related objects:
{{ post.comment_set.count }}

Something like this?
template.html
...
{% for post in posts %}
<div> Title: {{post.title}} </div>
<div> Date Posted: {{post.date_posted}} </div>
<div> Number of Comments: {{post.comment_set.count}} </div>
{% endfor %}

Related

Queryset filter by variable that's in another queryset

I am trying to filter a queryset by a variable in another queryset that hasn't been set yet. I know it sounds confusing so let me show you.
Views.py
def ViewThreadView(request, thread):
posts = Post.objects.filter(post_thread=thread)
thread = Thread.objects.get(pk=thread)
form_class = QuickReplyForm
thread_name = thread.name
return render(request, 'thread/viewthread.html',
{'thread': thread, 'posts': posts, 'thread_name': thread_name})
Post Model
class Post(models.Model):
post_body = models.TextField(blank=True, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post_date = models.DateTimeField(auto_now_add=True)
post_thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
def __str__(self):
return str(self.id) + ' | ' + str(self.author)
The User model is the standard Django model
As of now, if I want to access the post author in the template, I'd do this
{% for post in posts %}
post.author
{% endfor %}
My question is, how do I access the tables of post.author. So if I want to filter how many posts that author has, I want to do something like user_posts = Post.objects.get(author=post.author). But that can't work in the views because "posts" is a queryset and not a value. How can I do this?
In your template you can access your related objects with post_set:
{% for post in posts %}
{{ post.author.post_set.count }}
{% endfor %}
If you need more then the total number of posts, do you want filter your related objects or something else. You can always write a custom method for your model. See Model methods
For example:
from django.utils.functional import cached_property
class Post(models.Model):
post_body = models.TextField(blank=True, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post_date = models.DateTimeField(auto_now_add=True)
post_thread = models.ForeignKey(Thread, on_delete=models.CASCADE)
def __str__(self):
return str(self.id) + ' | ' + str(self.author)
#cached_property
def count_by_author_and_thread(self):
return self.author.post_set.filter(post_thread=self.post_thread).count()
And then in your template simple use:
{% for post in posts %}
{{ post.count_by_author_and_thread }}
{% endfor %}

How to iterate through Django model items in a ManyToManyField

Brand new to Django/Python and thought I'd start by building a simple blog app. I would like the User to be able to post book references that include the book name and a link to the book.
My current References class in my models.py looks like this:
class References(models.Model):
link = models.URLField(max_length=150, default=False)
title = models.CharField(max_length=100, default=False)
def __str__(self):
return self.title
and my Post class looks like this:
class Post(models.Model):
title = models.CharField(max_length=31)
content = models.TextField()
thumbnail = models.ImageField()
displayed_author = models.CharField(max_length=25, default=True)
shortquote = models.TextField()
reference_title = models.ManyToManyField(References)
publish_date = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = models.SlugField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("detail", kwargs={
'slug': self.slug
})
def get_love_url(self):
return reverse("love", kwargs={
'slug': self.slug
})
#property
def comments(self):
return self.comment_set.all()
#property
def get_comment_count(self):
return self.comment_set.all().count()
#property
def get_view_count(self):
return self.postview_set.all().count()
#property
def get_love_count(self):
return self.love_set.all().count()
I understand that i'm only returning the title in my References class, I've tried returning self.title + self.linkbut this gives me both the title and link together when being called in the template.
My template calling the references class looks like this:
{% for title in post.reference_title.all %}
<a href="{{ link }}">
<li>{{ title }}</li>
</a>
{% endfor %}
I've tried a combination of different things in order to get the link AND title to render independently as shown in the template, but the issue comes with knowing how to display different items from a class through a ManyToManyField. Any help on this would be great as I do believe it's just something I haven't learnt yet. Thanks in advance!
title is not the title, it is a References object, it is only because you implemented __str__ to return the title, that {{ title }}, will indeed render the .title attribute of that References object.
You thus iterate over it, and access the attribute:
{% for reference in post.reference_title.all %}
<a href="{{ reference.link }}">
<li>{{ reference }}</li>
</a>
{% endfor %}
You thus can replace {{ reference }} with {{ reference.title }} here, although the two are equivalent because the __str__ returns the title.

Retrieving metadata from a post, then sorting posts by said metadata in django

I have two interconnected models in my blog app; Category and Post. The blog front page displays a list of posts and their corresponding metadata, like it should; fairly standard stuff.
Aside from displaying the posts on the front page, they're also displayed on the individual user's profile page in short form (just the category and the headline).
What I'm interested in doing is sorting all the posts that belong in a category, however the only way I've managed to make it work is something like this:
NEWS
some title
NEWS
another title
PYTHON
another arbitrary title
NEWS
yet another title
I'd like to sort it thusly instead:
NEWS
some title
another title
yet another title
PYTHON
another arbitrary title
Alas, my mind keeps turning into a bowl of spaghetti when I try to come up with a method, so without further ado; how should I go about this bit?
I reckon that there's something off with calling the category from the post's metadata only to try and categorize the posts via the retrieved data, but aside from that, I'm somewhat lost.
Here's the template snippet from user_profile.html:
{% if user.post_set.exists %}
<p>
{% for post in user.post_set.all|dictsortreversed:"date_posted" %}
<span style="margin-right: 5px; padding: 3px 6px; border-radius:12px; color:#FFF; background-color:#FFA826;">{{ post.category }}</span><br/>
<a style="margin-left:3px;" href="{% url 'blog:post-detail' post.slug %}">{{ post.title|truncatechars_html:30 }}</a><br/>
{% endfor %}
</p>
{% endif %}
The models:
class Category(models.Model):
class Meta:
verbose_name = 'category'
verbose_name_plural = 'categories'
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=60)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.CASCADE)
content = RichTextUploadingField(
external_plugin_resources=[(
'youtube',
'/static/ckeditor/ckeditor/plugins/youtube/',
'plugin.js'
)],
blank=True,
null=True,
)
date_posted = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(auto_now=True)
slug = models.SlugField(max_length=70, blank=True, null=True, help_text='<font color="red">don\'t. touch. the. slug. field. unless. you. mean. it.</font> (it will auto-generate, don\'t worry.)')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post-detail', kwargs={'slug': self.slug})
And finally the view which relate to the post_list.html:
class PostListView(ListView):
model = Post
template_name = 'blog/home.html'
context_object_name = 'posts'
ordering = '-date_posted'
paginate_by = 6
Should I be doing it in a different manner altogether, I wonder? And if so, what would be considered 'best practice'?
Thank you :)
You can add the ordering in your model:
class Post(models.Model):
...
class Meta:
ordering = ['category', '-date_posted']
See the documentation for more details:
update
Maybe its better to use custom manager for this:
from django.db import models
class CustomManager(models.Manager):
# subclass model manager
def custom_category_dict(self, **kwargs):
# add a new method for building a dictionary
nDict = dict()
for i in self.get_queryset().filter(**kwargs): # filter queryset based on keyword argument passed to this method
current_list = nDict.get(i.category.name, [])
current_list.append(i)
nDict.update({i.category.name: current_list})
return nDict
class Posts(models.Model):
# override posts model with manager
objects = CustomManager()
Usage:
# view
class PostListView(ListView):
...
def get_context_data(self, **kwargs):
context = super(PostListView, self).get_context_data(**kwargs)
context['category_wise_sorted_posts'] = Posts.objects.custom_category_dict() # you can pass filter logic as well, like Posts.objects.custom_category_dict(author_id=1)
return context
# template
{% for category, posts in category_wise_sorted_posts.items %}
<!-- Or {% for category, posts in user.posts_set.custom_category_dict.items %} -->
{{ category }}
{% for p in posts %}
{{ p.title }}
{% endfor %}
{% endfor %}

Django blog - calling latest 3 posts per category via many to many

So far my blog project has :
Models.py
class Category(models.Model):
title = models.CharField(max_length=200)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "categories"
class Post(models.Model):
title = models.CharField(max_length=200)
summary = models.CharField(max_length=500, default = True)
body = models.TextField()
pub_date = models.DateTimeField(default=timezone.now)
categories = models.ForeignKey('Category', default=True)
def __str__(self):
return self.title
Views
from django.shortcuts import render
from .models import Post, Category
def getAllCategories(request):
categories = Category.objects.all()
context = {
'categories':categories,
}
return render(request, 'categories/getAllCategories.html', context)
Template
{% extends "../posts/base.html" %}
{% block content %}
<div>
{% for category in categories %}
<div>
<h3>{{ category.title }}</h3>
<ul>
{% for post in category.post_set.all %}
<li><a href=#>{{post.title}}</a></li>
- {{post.summary}}
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endblock %}
This pulls through all of my posts per category fine - however I'm stuck as I would like to only pull through the latest three posts per category. I understand that in a simple view you can use order_by()[:3], but then you can't use post_set with that. So I need a way of filtering by top three posts within a category, so using the many-to-many relationship. Hope this is clear.
Many thanks

Django: Querying to ImageField all images are being rendered

So my problem is that when I try to query to the image url so it can be posted to its corresponding Post all the images that have been uploaded to the media folder is being rendered, even though in the admin panel it shows that each post has it's own image and they are assigned to different posts, instead all of them are being rendered together for each and every post.
The models that I have are SellPost which is for creating a post and SellPostImage is for assigning the image to the post.
models.py
class SellPost(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=128)
category = models.ForeignKey(Category)
type = models.ForeignKey(SellPostType, default=None)
body = models.CharField(max_length=400)
price = models.DecimalField(decimal_places=1, max_digits=5, default=0.0)
views = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
slug = models.SlugField(unique=True, default='automatic')
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(SellPost, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class SellPostImage(models.Model):
user = models.ForeignKey(User, null=True)
post = models.ForeignKey(SellPost)
pictures = models.ImageField(upload_to='post_images', blank=True)
def __str__(self):
return "{}".format(self.post)
class Meta:
verbose_name_plural = "Post Images"
In the view I tried to create a context dict (because I'm a newbie in Django and have learned that from Tango with Django so I went with it) for the post and then the images:
views.py
def post(request, post_name_slug):
context_dict = {}
try:
post = SellPost.objects.get(slug=post_name_slug)
context_dict['post'] = post
post_image = SellPostImage.objects.all()
context_dict['post_image'] = post_image
except SellPost.DoesNotExist:
pass
return render(request, 'p.html', context_dict)
and here is how I tried to render them in the HTML file.
p.html
<ul>
{% for post in posts %}
<li>{{ post.title }} </li>
{% for post_images in post_image %}
<img style="width:200px; height:200px;" src="{{ post_images.pictures.url }}" />
{% endfor %}
{% endfor %}
</ul>
You'll want to filter the SellPostImage for the retrieved post:
post = SellPost.objects.get(slug=post_name_slug)
context_dict['post'] = post
post_image = SellPostImage.objects.filter(post=post)
context_dict['post_image'] = post_image
But you can just as easily put that logic part directly into your template:
{% for post in posts %}
<li>{{ post.title }} </li>
{% for post_images in post.sellpostimage_set.all %}
<img style="width:200px; height:200px;" src="{{ post_images.pictures.url }}" />
{% endfor %}
{% endfor %}
and then you can remove the SellPostImage in your views:
try:
post = SellPost.objects.get(slug=post_name_slug)
context_dict['post'] = post
except SellPost.DoesNotExist:
pass
In your post method you query for all SellPostImages:
post_image = SellPostImage.objects.all()
That's why you get all images for each post.
You can filter only the images associated with a post by doing the following instead:
post_image = SellPostImage.objects.filter(post=post)
It will provide all images for that specific post.

Categories