How to display comments and it's replies in django templates? - django

I want to display comment and it's replies in the template. But there is an issue, every reply may have some other replies. The below snippet is my Comment and CommentReply model:
class Comment(models.Model):
author = models.ForeignKey(Profile, related_name="c_sender", on_delete=models.CASCADE, unique=False)
comment = models.CharField(max_length=500, unique=False)
created_date = models.DateTimeField(auto_now_add=True)
edited_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.comment
#property
def replys(self):
return CommentReply.objects.filter(comment_id=self)
class CommentReply(models.Model):
comment_id = models.ForeignKey(Comment, related_name='sender', on_delete=models.CASCADE)
reply_id = models.ForeignKey(Comment, related_name='reply', on_delete=models.CASCADE)
Updated:
Also I have a model for WorksComments that every comments that related to Work model saved there.
class WorkComment(models.Model):
work_id = models.ForeignKey(Work, on_delete=models.CASCADE, related_name='e_exercise', unique=False)
comment_id = models.ForeignKey(Comment, related_name='e_comment', unique=False)
The below snippet is my view:
comments = WorkComment.objects.filter(work_id=work).all()
return render(request, 'work.html', {'comments': comments})
My question is how to display comments and it's replies under it, and every reply may have some other replyies that I want to display them too.

First things first... put this in your bookmarks; https://ccbv.co.uk/
You're going to want a Detail View here I suspect in order to display the details of an instance.
Setup URLs...
from django.conf.urls import url
from work.views import WorkDetailView
urlpatterns = [
url(r'^(?P<id>[-\d]+)/$', WorkDetailView.as_view(), name='work-detail'),
]
And a view;
from django.views.generic.detail import DetailView
from django.utils import timezone
from work.models import Work
class WorkDetailView(DetailView):
model = Work
def get_context_data(self, **kwargs):
context = super(WorkDetailView, self).get_context_data(**kwargs)
context['comments'] = WorkComment.objects.filter(work_id=self.object.id).all()
return context
Then a simple view might be work/work_detail.html:
<h1>{{ object.title }}</h1>
<p>{{ object.content }}</p>
<h2>Comments</h2>
{% for comment in comments %}
{{ comment }}
{% endfor %}

Related

Trying to delete a comment from a post in django

I am currently trying to delete a comment from my database via a button in django template.
Model looks like this
from django.db import models
from django.contrib.auth.models import User
from cloudinary.models import CloudinaryField
from profiles.models import UserProfile
class Post(models.Model):
user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, related_name='user_posts')
title = models.CharField(max_length=220, unique=True)
location = models.CharField(max_length=220)
rating = models.DecimalField(
max_digits=6, decimal_places=2)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="activity_post")
updated_on = models.DateTimeField(auto_now=True)
description = models.TextField()
featured_image = CloudinaryField('image', blank=False)
created_on = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField(User, related_name='activity_likes', blank=True)
like_count = models.BigIntegerField(default='0')
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
def number_of_likes(self):
return self.likes.count()
def liked_by_user(self):
return self.likes.values_list('id', flat=True)
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="user_comment")
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created_on']
def __str__(self):
return f"Comment {self.body} by {self.name}"
Delete function
def delete_comment(request, post_id):
users_comment = get_object_or_404(Comment, post=post_id)
users_comment.delete()
return redirect(reverse('activity'))
URLS
from . import views
from django.urls import path
urlpatterns = [
path('like/', views.like, name='like'),
path("add/", views.add_post, name="add_post"),
path('edit/<int:post_id>/', views.edit_post, name='edit_post'),
path('delete/<int:post_id>/', views.delete_post, name='delete_post'),
path('edit_comment/<int:id>/', views.edit_comment, name='edit_comment'),
path('delete_comment/<int:post_id>/', views.delete_comment, name='delete_comment'),
path("activity/", views.PostList.as_view(), name="activity"),
path('comment/<int:post_id>/', views.Comment.as_view(), name='comment'),
path('searched_posts/', views.search_posts, name='searched_posts'),
path('post/<int:post_id>/', views.post_detail, name='post_detail')
]
here is the comment part that is showing the button.
{%if comments %}
{% for comment in comments %}
{% if comment.user == request.user %}
{{comment.body}} :comment
{{comment.id}} id
<a class="btn tbn-success" href="{% url 'edit_comment' comment.id %}" aria-label="edit button">Edit</a>
<button class="btn btn-warning">Delete</button>
{% endif %}
{% endfor%}
{% endif%}
When I click delete i get an error
Error
Any help would be greatly appreciated, I have tried a ton of different ways from online but nothing seems to work. can anyone point me in the right direction
The first thing I can see is that your delete function uses post = post_id.
Every comment on a particular post will share that post foreign key, so if there is more than one comment on a post, you can't use get_or_404() - it's limited to returning 1 item.
The URL you create for your button is using comment.id so it makes sense to use that instead - this will make it easier to see what's happening.
urls.py
path('delete_comment/<int:comment_id>/', views.delete_comment, name='delete_comment'),
views.py
def delete_comment(request, comment_id):
users_comment = get_object_or_404(Comment, pk=comment_id)
users_comment.delete()
return redirect(reverse('activity'))

How to add condition to a manytomany relationship

I'm a little new to Django, so my question may be basic, but bare with me. Let's say I'm sharing my posts with some of my friends (who are manytomany relation with the post), but also giving an extra condition for whether they can comment on it, or not. This condition, together with the name of the user to be shared with are submitted through a forum. Now my problem is that I don't know how/where to save this condition. I will show you some of my code.
Models.py
class Task(models.Model):
name = models.CharField(max_length=50)
text = models.TextField()
deadline = models.DateTimeField()
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='taskset')
shared = models.ManyToManyField(User, blank=True, related_name = 'shared_list')
def __str__(self):
return f"{self.name}-{self.user.username}"
class Comment(models.Model):
text = models.TextField()
task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments')
created = models.DateField(auto_now_add=True)
def __str__(self):
return f"{self.user.username}-{self.task}"
Share form html
{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
<h3>Enter the Username of the Friend you want to share with</h3>
<form method="POST">
{% csrf_token %}
{{form|crispy}}
<input type="submit", value="Share">
</form>
{% endblock content %}
And the view processing it
def share(request, id):
task = Task.objects.get(id = id)
if request.method == 'POST':
share_with = User.objects.get(username = request.POST['username'])
if share_with is not None:
task.shared.add(share_with)
task.save()
return redirect('index')
else:
form = ShareForm()
return render(request, 'share.html', {'form':form})
Thanks a lot, I've been stuck for two hours, PLEASE HELP!

Django - Reverse a queryset

I have very basic view with queryset, but for some reason I cannot reverse it.
def home(request):
posts = Post.objects.all().reverse()
return render(request, "home.html", {'posts': posts})
For some reason no matter how I change code - instanses from queryset always rendering in same order.
Below there are lines of code that I already tried and which didn't worked for some reason:
Post.objects.all().reverse()
Post.objects.order_by('-date')
Post.objects.order_by('date').reverse()
home.html:
{% for post in posts %}
{{ post.title }}
{{ post.content }}
{% endfor %}
models.py:
class Post(models.Model):
title = models.CharField(max_length=100, unique=True, blank=True, null=True)
image = models.URLField(blank=True, null=True, unique=True)
content = models.TextField(blank=True, null=True)
date = models.DateField(auto_now=False, auto_now_add=True)
#To make in name, not objXXX
def __str__(self):
return self.title
I believe the queryset you're looking for is Post.objects.all().order_by("-date"):
def home(request):
posts = Post.objects.all().order_by("-date")
return render(request, "home.html", {"posts": posts})
It is considered a best practice to avoid putting ordering on a Django model, as it can force unnecessary sorting operations in the database when you don't need them. When necessary, I put default ordering on Django Admin definitions, instead of the model. Good luck!
If it is the ordering that you are looking for try (in models.py):
class Meta:
ordering = ('-date',)
If not, it does not mean much to reverse something that isn't ordered, try (in views.py):
This
post = Post.objects.filter("<filter name>").reverse()
instead of
post = Post.objects.all.order_by("<field name>").reverse()

How to pass multiple app's models in one views? [django]

Hei there,
I'm having difficulties passing two app models in one views. I have 2 apps :
Post
Author
What I expect is, I want to display Author's avatar in the Post views, so I can include it in posts loop.
Author models.py :
class Author(models.Model):
avatar = models.ImageField(upload_to='images/%Y/%m/%d', verbose_name=u'Author Avatar', validators=[validate_image], blank=True, null=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
location = models.CharField(max_length=30, blank=True)
...
Post views.py :
from app_author.models import Author
class PostList(ListView):
model = Post
template_name = 'app_blog/blog_homepage.html'
context_object_name = 'post_list'
paginate_by = 9
def get_context_data(self, **kwargs):
context = super(PostList, self).get_context_data(**kwargs)
context['post_hot'] = Post.objects.filter(misc_hot_post = True).order_by('-misc_created')[:1]
context['post_list'] = Post.objects.filter(misc_published = True).order_by('-misc_created')
context['author'] = Author.objects.all() # No need this line
return context
When I called something like this in templates, it doesn't work. The {{ author.avatar }} not showing :
{% for post in post_list %}
<ul>
<li>{{ post.title }}</li> # This is works just fine
<li>{{ author.avatar }}</li> # This does not work at all
<ul>
{% endfor %}
My Posts app urls.py :
from . import views
from django.conf import settings
from app_blog.views import PostList, PostDetailView
from django.conf.urls import include, url
from django.conf.urls.static import static
urlpatterns = [
url(r'^$', views.PostList.as_view(), name='post-list'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Any help would be greatly appreciated!
Thank you in advance!
Regards
--- UPDATE ---
Here is my Post models.py
import datetime
from django import forms
from django.db import models
from django.conf import settings
from autoslug import AutoSlugField
from django.forms import ModelForm
from app_author.models import Author
from taggit.managers import TaggableManager
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.template.defaultfilters import slugify
# CUSTOM FILE SIZE VALIDATOR
def validate_image(fieldfile_obj):
filesize = fieldfile_obj.file.size
megabyte_limit = 5
if filesize > megabyte_limit*1024*1024:
raise ValidationError("Max file size is %sMB" % str(megabyte_limit))
class Category(models.Model):
# PRIMARY DATA
id = models.AutoField(primary_key=True)
category_name = models.CharField(max_length=200, verbose_name=u'Category Name', blank=False, null=False)
def __str__(self):
return str(self.category_name)
class Post(models.Model):
# PRIMARY DATA
post_image = models.ImageField(upload_to='images/%Y/%m/%d', verbose_name=u'Post Featured Image', validators=[validate_image], blank=True, null=True)
post_author = models.ForeignKey(Author, related_name="user_posts", null=True, blank=True)
post_title = models.CharField(max_length=200, verbose_name=u'Post Title', blank=False, null=False)
post_sub_title = models.CharField(max_length=200, verbose_name=u'Post Sub-Title', blank=False, null=False)
post_category = models.ForeignKey('Category', verbose_name=u'Post Category', on_delete=models.CASCADE)
post_content = models.TextField()
post_tags = TaggableManager()
slug = AutoSlugField(populate_from='post_title')
# MISC
misc_created = models.DateTimeField(default=datetime.datetime.now, null=True, blank=True)
misc_modified = models.DateTimeField(default=datetime.datetime.now, null=True, blank=True)
misc_hot_post = models.BooleanField(default=False)
misc_published = models.BooleanField(default=False)
def __str__(self):
return str(self.post_title)
#models.permalink
def get_absolute_url(self):
return 'app_blog:post', (self.slug,)
Thank you
--- UPDATE 2 ---
SOLVED
#Gagik & #Daniel answer sloved my problem. I just have to use this tag instead :
{{ post.post_author.avatar }}
I did not change any code above. Except I dont need this line :
context['author'] = Author.objects.all()
Thank you
You need to related Author and Post models to each other by ForeignKey file, like:
class Post (models.Model):
author = models.ForeignKey(Author)
Then when you can access to your avatar through post.author.
Updated:
Updating answer after question updated.
I think you need to update your template as follows. use post.post_author.avatar to access the avatar you need:
{% for post in post_list %}
<ul>
<li>{{ post.title }}</li> # This is works just fine
<li>{{ post.post_author.avatar }}</li> # This does not work at all
<ul>
{% endfor %}
Considering this please note that you don't need to search Author's desperately in your view. So you don't need to have following line:
context['author'] = Author.objects.all() # This is where I expect the magic is
Because when you have ForeignKey then you already have access to Author through post object.
Gagik and JF's answers should have been enough for you to work out what to do. Your foreign key is called "post_author", so that's what you should use in the template:
{{ post.post_author.avatar }}
There's no need for you get all the authors and pass them to the template in your view as you are doing.

How to retrieve data from one to many relations in django?

I am making my personal website using django 1.10
Here is models of skill app:
from __future__ import unicode_literals
from django.db import models
# Create your models here.
class Skill(models.Model):
name = models.CharField(max_length=256)
created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.name
def __str__(self):
return self.name
class Subskill(models.Model):
skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
name = models.CharField(max_length=256)
link = models.CharField(max_length=256)
created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True, auto_now_add=False)
def __unicode__(self):
return self.name
def __str__(self):
return self.name
And view:
from django.shortcuts import render
from skill.models import Skill,Subskill
# Create your views here.
def home(request):
skill = Skill.objects.all()
subskill =Subskill.objects.all()
context = {'skills':skill,
'subskills':subskill}
return render(request, 'skill.html', context)
This is my template page:
skill.html
{% block skill %}
{% for subskill in subskills %}
{{subskill.skill.name}}
{{subskill.name}}
{% endfor %}
{% endblock skill %}
Let assume, there is a skill named web design which has two subskill named html and css.
I want to render in view page as like as skill name and it's two child name:
Web design
Html
CSS
But it renders as like Web design Html Web design CSS
Please help me about this issue.
You can do realted query on skill itself
https://docs.djangoproject.com/en/1.10/topics/db/queries/#backwards-related-objects
# example
skill_obj = Skill.objects.all()[0]
subskills = skill_obj.subskill_set.all()
Or in your case
def home(request):
skills = Skill.objects.all().prefetch_related('subskill_set') # optimizing
context = {'skills':skills}
return render(request, 'skill.html', context)
In template
{% for skill in skills %}
{{skill.name}}
{% for subskill in skill.subskill_set.all %}
{{subskill.name}}
{% endfor %}
{% endfor %}