Building a Social Media Site With Python and Django: Part 7 Likes and Dislikes

LegionScript
5 min readFeb 15, 2021

Video Tutorial

Code on Github

In this tutorial, we are going to add the ability to like and dislike posts. To do this, we will store a list of users that clicked the like or the dislike button. We will add some checks to make sure that the user can only like or dislike a post once and they can only either like or dislike a post and not both at the same time. First we need a place to store this data, we will add that to our Post model.

Add to The Models.py

This will look very similar to how we added followers in the previous tutorial. We will add a field for the likes and one for the dislikes and we will store User objects in these fields.

social/models.py:

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

Add Like and Dislike Views

Now that we have a place to store the data, we need to add the views and url paths to be able to add or remove a user from the likes or dislikes ManyToManyField. This will also be pretty similar to the followers views that we did before. If a user has already liked/disliked a post, we want the next click to remove the like or dislike. This way, they can only like or dislike something once but they can undo it if they want to. We will also make sure if the user likes and then tries to dislike, that the like is removed before adding the dislike and the same thing goes for the opposite scenario. We will need to also redirect at the end but we will add that later, it won’t make any sense on how we will do it until after we add the form to our template. So that error will be fixed after everything else is set up.

social/views.py:

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

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

if is_dislike:
post.dislikes.remove(request.user)

Now let’s add two urls for each, we will add these next to our other post urls.

social/urls.py:

from django.urls import path
from .views import PostListView, PostDetailView, PostEditView, PostDeleteView, CommentDeleteView, ProfileView, ProfileEditView, AddFollower, RemoveFollower, AddLike, AddDislike
urlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/edit/<int:pk>/', PostEditView.as_view(), name='post-edit'),
path('post/delete/<int:pk>/', PostDeleteView.as_view(), name='post-delete'),
path('post/<int:post_pk>/comment/delete/<int:pk>/', CommentDeleteView.as_view(), name='comment-delete'),
path('post/<int:pk>/like', AddLike.as_view(), name='like'),
path('post/<int:pk>/dislike', AddDislike.as_view(), name='dislike'),
path('profile/<int:pk>/', ProfileView.as_view(), name='profile'),
path('profile/edit/<int:pk>/', ProfileEditView.as_view(), name='profile-edit'),
path('profile/<int:pk>/followers/add', AddFollower.as_view(), name='add-follower'),
path('profile/<int:pk>/followers/remove', RemoveFollower.as_view(), name='remove-follower'),
]

Update HTML Templates

Now that we have our views and urls set up, let’s add two forms with two buttons that will send a post request to either like or dislike a post. These need to be in separate forms so that we can send the post request to different urls. We will also add a hidden field, this will have a name of next with a value that equals the current path. This is the value we will use to redirect back to the previous page that we were on. This way we can send the post request from the social feed, the post detail or from the profile and we can redirect to whatever page the user was previously on.

social/post_list.html:

{% for post in post_list %}
<div class="row justify-content-center mt-3">
<div class="col-md-5 col-sm-12 border-bottom position-relative">
<p><a style="text-decoration: none;" class="text-primary" href="{% url 'profile' post.author.profile.pk %}">@{{ post.author }}</a> {{ post.created_on }}</p>
<div class="position-relative">
<p>{{ post.body }}</p>
<a class="stretched-link" href="{% url 'post-detail' post.pk %}"></a>
</div>
<div class="d-flex flex-row">
<form method="POST" action="{% url 'like' post.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit"><i class="far fa-thumbs-up"></i> <span>{{ post.likes.all.count }}</span></button>
</form>

<form method="POST" action="{% url 'dislike' post.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit"><i class="far fa-thumbs-down"></i> <span>{{ post.dislikes.all.count }}</span></button>
</form>
</div>
</div>
</div>
{% endfor %}

social/profile.html:

{% for post in posts %}
<div class="row justify-content-center mt-5">
<div class="col-md-5 col-sm-12 border-bottom position-relative">
<p><a style="text-decoration: none;" class="text-primary" href="{% url 'profile' post.author.profile.pk %}">@{{ post.author }}</a> {{ post.created_on }}</p>
<div class="position-relative">
<p>{{ post.body }}</p>
<a class="stretched-link" href="{% url 'post-detail' post.pk %}"></a>
</div>
<div class="d-flex flex-row">
<form method="POST" action="{% url 'like' post.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit"><i class="far fa-thumbs-up"></i> <span>{{ post.likes.all.count }}</span></button>
</form>
<form method="POST" action="{% url 'dislike' post.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit"><i class="far fa-thumbs-down"></i> <span>{{ post.dislikes.all.count }}</span></button>
</form>
</div>
</div>
</div>
{% endfor %}

social/post_detail.html:

<div class="row justify-content-center mt-3">
<div class="col-md-5 col-sm-12 border-bottom">
<p>
<strong>{{ post.author }}</strong> {{ post.created_on }}
{% if request.user == post.author %}
<a href="{% url 'post-edit' post.pk %}" style="color: #333;"><i class="far fa-edit"></i></a>
<a href="{% url 'post-delete' post.pk %}" style="color: #333;"><i class="fas fa-trash"></i></a>
{% endif %}
</p>
<p>{{ post.body }}</p>
<div class="d-flex flex-row">
<form method="POST" action="{% url 'like' post.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit"><i class="far fa-thumbs-up"></i> <span>{{ post.likes.all.count }}</span></button>
</form>
<form method="POST" action="{% url 'dislike' post.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit"><i class="far fa-thumbs-down"></i> <span>{{ post.dislikes.all.count }}</span></button>
</form>
</div>
</div>
</div>

Add Redirect to Views

Now that everything else is finished, let’s add the redirect to the two views to make all of this functional. We need to get the value of ‘next’ and use that to return an HTTP response. Here are the finished like and dislike views.

social/views.py:

from django.http import HttpResponseRedirectclass AddLike(LoginRequiredMixin, View):
def post(self, request, pk, *args, **kwargs):
post = Post.objects.get(pk=pk)
is_dislike = False for dislike in post.dislikes.all():
if dislike == request.user:
is_dislike = True
break
if is_dislike:
post.dislikes.remove(request.user)
is_like = False for like in post.likes.all():
if like == request.user:
is_like = True
break
if not is_like:
post.likes.add(request.user)

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

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

--

--