Django Templates Display The Latest 3 Comments - python

I have this code in django that, for every django comment, does whatever is inside the for loop:
{% for comment in post.comments.all %}
{% endfor %}
This code, for every comment, does whatever is inside the for. To count the comments I use this:
{{ post.comments.count }}
This is my comment model in case you need it:
class Comment(models.Model):
post = models.ForeignKey('blog.Post', on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField(default='')
created_date = models.DateTimeField(default=timezone.now)
anonymous = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
Now, I want to sort of combine these together, so it shows only the latest 3 comments and only runs the for loop 3 times or less. (Please note that I want to do all of this work in the templates, and not in the views.py file)

You can order the comments by default by the creation date with:
from django.conf import settings
class Comment(models.Model):
post = models.ForeignKey(
'blog.Post',
on_delete=models.CASCADE,
related_name='comments'
)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
text = models.TextField(default='')
created_date = models.DateTimeField(auto_now_add=True)
anonymous = models.BooleanField(default=False)
class Meta:
ordering = ['-created_date']
# …
Then you can work with the |slice template filter [Django-doc] to slice the queryset:
{% for comment in post.comments.all|slice:':3' %}
{% endfor %}
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
It is however better to prefetch the elements in the view, with:
Post.objects.prefetch_related('comments')
this will fetch the comments in bulk, which is more efficient than running a query per Post object.

Related

Django template user in queryset condition

I have the following Post and Follow models:
class Post(models.Model):
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
date_modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
#property
def followers(self):
return self.follow_set.filter(post=self).values_list('user', flat=True)
class Follow(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
I tried the following code in shell to check for data in .followers property:
>>> from posts.models import Post
>>> x= Post.objects.get(id=22)
>>> x.followers
<QuerySet [1]>
What I ultimately want is to check if the authenticated user is among the followers. I have the following template code:
{% if user in object.followers %}
Unfollow
{% else %}
Follow
{% endif %}
The problem I have is that {% if user in object.followers %} always evaluates to False. P.S.: I always log-in the user id=1 (same as the queryset result from above).
Use
{% if user in object.followers.all %}
Nevermind. I just realized my mistake.
{% if user.id in object.followers %}

Django Forum App, comments don't update on user-side, but can be seen through admin

For reference, here are my models in my Forum app:
class Forum(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('forum-detail', kwargs={'pk': self.pk})
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
forum = models.ForeignKey(Forum, on_delete=models.CASCADE)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
To display the forum posts, I have a CBV 'ForumListView':
class ForumListView(ListView):
model = Forum
template_name = 'forum/forum.html'
context_object_name = 'forum_posts'
ordering = ['-created_at']
From this list, the user can click on any forum and it will lead them to 'forum-detail' with the CBV 'ForumDetailView':
class ForumDetailView(DetailView):
model = Forum
extra_context = {
'comments': Comment.objects.all().order_by('-created_at')}
Here is where I passed in the comments from my Comment model to be shown alongside the post. I think this is the reason why the comments don't update, but I'm not too sure how to fix this.
In the template for forum_detail.html, this is how I display all the comments made:
{% for comment in comments %}
{% if comment.forum == forum %}
<div class="content-section">
<p>{{ comment.description }}</p>
<small>{{ comment.user.username }}, on {{ comment.created_at|date:"F d, Y" }}</small>
</div>
{% endif %}
{% endfor %}
Note that the new comment made will be shown if I re-run
python manage.py runserver
and sometimes the new comment appears after a few minutes of waiting/refreshing the page.
Also, I think function-based views may fix this, however I have coded my entire app with CBVs and would love a fix that doesn't involve re-coding everything!
Any help is greatly appreciated, and can provide more info/code if needed!
Putting it in extra_context like this will cause the queryset to be evaluated when you define the view, and any values that it has at that time will be the only values that the view will get. That's why it is working when you restart the server. So it should be dynamic and fetched every time a new request comes. In that case you need to put it in get_context_data
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.all().order_by('-created_at')}
return context

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

Getting profile picture of blogposter in Django

When I display a blog post in HTML using a for loop
{% for post in posts %}
<div class="blogpost">
<h3><strong>{{post.title}}</strong></h3>
<img class="thumbnail" src="{{author.imageURL}}">
<h7>{{post.date}} - {{post.author}}</h7>
<br>
<hr>
<br>
{{post.context|linebreaks}}<br><br>
</div>
<br>
{% endfor %}
it works perfectly fine, except the authors profile picture does NOT get displayed.
I get the posts by getting all posts in my views.py from my models.py. The thing is that the profile picture of the user posting the blog isn't stored in the "post" model in the database. It is stored in the "Customers". Everybody should be able to read and post blogs. The admin (me) can later delete unwanted posts.
I have tried making a for loop using an array key:value in JavaScript. That is not very secure, because everybody just gets all users and all profilepictures through the whole database. That might not be a good idea.
This is my models.py
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank = True)
name = models.CharField(max_length=200, null=True)
email = models.EmailField(max_length=200, null=True)
about = models.CharField(max_length=100, null=True)
image = models.ImageField(null=True, blank=True)
#property
def imageURL(self):
try:
url = self.image.url
except:
url = 'placeholder.png'
return url
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200, null=True)
context = models.TextField(max_length=250, blank=True,
validators=[MaxLengthValidator(250)])
author = models.CharField(max_length=200, null=True)
from datetime import date
date = models.DateField(("Date"), default=date.today)
views.py
def homepage(request):
if request.user.is_authenticated: # Everybody has to be logged in
posts = Post.objects.all()
authors = Customer.objects.filter()
context = {'posts':posts, 'authors':authors}
return render(request, 'store/homepage.html', context)
That means that I want the user to see the persons profile picture in the corner of the blog post.
First I think you need to link the author in the Post model to a Customer with a foreignkey:
Then you can acces author fields like
{{ post.author.xyz }}
Currently your context contains authors as a queryset:
authors = Customer.objects.filter()
context = {'posts':posts, 'authors':authors}
But in your template you use it as if it were a single Customer:
<img class="thumbnail" src="{{author.imageURL}}">
And be aware of the typo "author" and "authors"
It is unclear what you meant by {{author.imageURL}} as author is QuerySet( that results in collection of authors).
You probably meant the related author to post which would be post.author
Other than that you should pass image URL to template and not ImageField string representation as documented
So it should be something in a line of
{{ post.author.imageURL.url }}

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

Categories