Building a Social Media Site With Python and Django: Part 11 Adding Comment Replies

Adding Likes and Dislikes to Comments

social/models.py

class Comment(models.Model):
comment = models.TextField()
created_on = models.DateTimeField(default=timezone.now)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
likes = models.ManyToManyField(User, blank=True, related_name='comment_likes')
dislikes = models.ManyToManyField(User, blank=True, related_name='comment_dislikes')

social/views.py

class AddCommentLike(LoginRequiredMixin, View):
def post(self, request, post_pk, pk, *args, **kwargs):
comment = Comment.objects.get(pk=pk)
is_dislike = False for dislike in comment.dislikes.all():
if dislike == request.user:
is_dislike = True
break
if is_dislike:
comment.dislikes.remove(request.user)
is_like = False for like in comment.likes.all():
if like == request.user:
is_like = True
break
if not is_like:
comment.likes.add(request.user)

if is_like:
comment.likes.remove(request.user)
next = request.POST.get('next', '/')
return HttpResponseRedirect(next)
class AddCommentDislike(LoginRequiredMixin, View):
def post(self, request, post_pk, pk, *args, **kwargs):
comment = Comment.objects.get(pk=pk)
is_like = False for like in comment.likes.all():
if like == request.user:
is_like = True
break
if is_like:
comment.likes.remove(request.user)
is_dislike = False for dislike in comment.dislikes.all():
if dislike == request.user:
is_dislike = True
break
if not is_dislike:
comment.dislikes.add(request.user)

if is_dislike:
comment.dislikes.remove(request.user)
next = request.POST.get('next', '/')
return HttpResponseRedirect(next)

social/urls.py

path('post/<int:post_pk>/comment/delete/<int:pk>/', CommentDeleteView.as_view(), name='comment-delete'),
path('post/<int:post_pk>/comment/<int:pk>/like', AddCommentLike.as_view(), name='comment-like'),

social/templates/social/post_detail.html

{% for comment in comments %}
{% if comment.is_parent %}
<div class="row justify-content-center mt-3 mb-5">
<div class="col-md-5 col-sm-12 border-bottom">
<div>
<a href="{% url 'profile' comment.author.profile.pk %}"><img class="rounded-circle post-img" height="30" width="30" src="{{ comment.author.profile.picture.url }}" /></a>
<p class="post-text"><a class="post-link text-primary" href="{% url 'profile' comment.author.profile.pk %}">@{{ comment.author }}</a> {{ comment.created_on }}</p>
<div class="mb-3">
{% if request.user == comment.author %}
<a href="{% url 'comment-delete' post.pk comment.pk %}" class="edit-color"><i class="fas fa-trash"></i></a>
{% endif %}
</div>
</div>
<p>{{ comment.comment }}</p>
<div class="d-flex flex-row">
<form method="POST" action="{% url 'comment-like' post.pk comment.pk%}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit"><i class="far fa-thumbs-up"></i> <span>{{ comment.likes.all.count }}</span></button>
</form>
<form method="POST" action="{% url 'comment-dislike' post.pk comment.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit"><i class="far fa-thumbs-down"></i> <span>{{ comment.dislikes.all.count }}</span></button>
</form>
</div>
<div class="row justify-content-center mt-3 mb-5 d-none" id="{{ comment.pk }}">
<div class="col">
<form method="POST" action="{% url 'comment-reply' post.pk comment.pk %}">
{% csrf_token %}
{{ form | crispy }}
<div class="d-grid gap-2">
<button class="btn btn-success mt-3">Submit!</button>
</div>
</form>
</div>
</div>
</div>
</div>

Adding Replies to Comments

social/models.py

class Comment(models.Model):
comment = models.TextField()
created_on = models.DateTimeField(default=timezone.now)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
likes = models.ManyToManyField(User, blank=True, related_name='comment_likes')
dislikes = models.ManyToManyField(User, blank=True, related_name='comment_dislikes')
parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, related_name='+')
@property
def children(self):
return Comment.objects.filter(parent=self).order_by('-created_on').all()
@property
def is_parent(self):
if self.parent is None:
return True
return False

social/templates/social/post_detail.html

<div>
<button class="remove-default-btn"><i class="far fa-comment-dots" onclick="commentReplyToggle('{{ comment.pk }}')"></i></button>
</div>
<div class="row justify-content-center mt-3 mb-5 d-none" id="{{ comment.pk }}">
<div class="col">
<form method="POST" action="{% url 'comment-reply' post.pk comment.pk %}">
{% csrf_token %}
{{ form | crispy }}
<div class="d-grid gap-2">
<button class="btn btn-success mt-3">Submit!</button>
</div>
</form>
</div>
</div>

static/js/social.js

function commentReplyToggle(parent_id) {
const row = document.getElementById(parent_id);
if (row.classList.contains('d-none')) {
row.classList.remove('d-none');
} else {
row.classList.add('d-none');
}
}

social/views.py

class CommentReplyView(LoginRequiredMixin, View):
def post(self, request, post_pk, pk, *args, **kwargs):
post = Post.objects.get(pk=post_pk)
parent_comment = Comment.objects.get(pk=pk)
form = CommentForm(request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.author = request.user
new_comment.post = post
new_comment.parent = parent_comment
new_comment.save()

comments = Comment.objects.filter(post=post).order_by('-created_on')
context = {
'post': post,
'form': form,
'comments': comments,
}
return redirect('post-detail', pk=post_pk)

social/urls.py

path('post/<int:post_pk>/comment/reply/<int:pk>', CommentReplyView.as_view(), name='comment-reply'),

social/templates/social/post_detail.html

{% for comment in comments %}
{% if comment.is_parent %}
<div class="row justify-content-center mt-3 mb-5">
<div class="col-md-5 col-sm-12 border-bottom">
<div>
<a href="{% url 'profile' comment.author.profile.pk %}"><img class="rounded-circle post-img" height="30" width="30" src="{{ comment.author.profile.picture.url }}" /></a>
<p class="post-text"><a class="post-link text-primary" href="{% url 'profile' comment.author.profile.pk %}">@{{ comment.author }}</a> {{ comment.created_on }}</p>
<div class="mb-3">
{% if request.user == comment.author %}
<a href="{% url 'comment-delete' post.pk comment.pk %}" class="edit-color"><i class="fas fa-trash"></i></a>
{% endif %}
</div>
</div>
<p>{{ comment.comment }}</p>
<div class="d-flex flex-row">
<form method="POST" action="{% url 'comment-like' post.pk comment.pk%}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit"><i class="far fa-thumbs-up"></i> <span>{{ comment.likes.all.count }}</span></button>
</form>
<form method="POST" action="{% url 'comment-dislike' post.pk comment.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit"><i class="far fa-thumbs-down"></i> <span>{{ comment.dislikes.all.count }}</span></button>
</form>
<div>
<button class="remove-default-btn"><i class="far fa-comment-dots" onclick="commentReplyToggle('{{ comment.pk }}')"></i></button>
</div>
</div>
<div class="row justify-content-center mt-3 mb-5 d-none" id="{{ comment.pk }}">
<div class="col">
<form method="POST" action="{% url 'comment-reply' post.pk comment.pk %}">
{% csrf_token %}
{{ form | crispy }}
<div class="d-grid gap-2">
<button class="btn btn-success mt-3">Submit!</button>
</div>
</form>
</div>
</div>
</div>
</div>

{% for child_comment in comment.children %}
<div class="row justify-content-center mt-3 mb-5 child-comment">
<div class="col-md-5 col-sm-12 border-bottom">
<div>
<a href="{% url 'profile' child_comment.author.profile.pk %}"><img class="rounded-circle post-img" height="30" width="30" src="{{ child_comment.author.profile.picture.url }}" /></a>
<p class="post-text"><a class="post-link text-primary" href="{% url 'profile' child_comment.author.profile.pk %}">@{{ child_comment.author }}</a> {{ child_comment.created_on }}</p>
<div class="mb-3">
{% if request.user == child_comment.author %}
<a href="{% url 'comment-delete' post.pk child_comment.pk %}" class="edit-color"><i class="fas fa-trash"></i></a>
{% endif %}
</div>
</div>
<p>{{ child_comment.comment }}</p>
<div class="d-flex flex-row">
<form method="POST" action="{% url 'comment-like' post.pk child_comment.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit"><i class="far fa-thumbs-up"></i> <span>{{ child_comment.likes.all.count }}</span></button>
</form>
<form method="POST" action="{% url 'comment-dislike' post.pk child_comment.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit"><i class="far fa-thumbs-down"></i> <span>{{ child_comment.dislikes.all.count }}</span></button>
</form>
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% endfor %}

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store