DEV Community

Cover image for How to add Like/Unlike button to your Django Blog
Radu-Alexandru B
Radu-Alexandru B

Posted on • Updated on

How to add Like/Unlike button to your Django Blog

In this mini-tutorial, we will add a Like/Unlike functionality to our Django Blog. As a note, we will implement this feature while using a class-based view for our BlogPost DetailView.

2nd NOTE: Unfortunately, we cannot avoid the page refresh after every click of Like/Unlike button. In order to skip the refresh, it is needed to implement the whole functionality of Like/Unlike button inside our blogpost-detail HTML, using Ajax.js. This mini-tutorial will not focus on this type of implementation.

Let's get started, first, in our models.py we need to add to our BlogPost model the following fields:

# models.py
from django.db import models
from django.contrib.auth.models import User

class BlogPost(models.Model):
    ...
    likes = models.ManyToManyField(User, related_name='blogpost_like')

    def number_of_likes(self):
        return self.likes.count()
Enter fullscreen mode Exit fullscreen mode

"likes" is a Many-to-many relationship with our User model, meaning that users (objects) can have multiple likes, and blog posts can have multiple likes. The function number_of_likes will return the number of likes of the current blog post object.

After every change in the models.py file, we need to open our terminal and make the migrations to our database:

# CLI/Terminal
>> cd C:\Projects\...\YourDjangoAppMainFolder
>> python manage.py makemigrations
>> python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Let's now make a function-based view for our Like button functionality. In your view.py file, right before (or after) your BlogPost DetailView class, define a BlogPostLike function:

# views.py
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.urls import reverse

def BlogPostLike(request, pk):
    post = get_object_or_404(BlogPost, id=request.POST.get('blogpost_id'))
    if post.likes.filter(id=request.user.id).exists():
        post.likes.remove(request.user)
    else:
        post.likes.add(request.user)

    return HttpResponseRedirect(reverse('blogpost-detail', args=[str(pk)]))
Enter fullscreen mode Exit fullscreen mode

'blogpost_id' will be our button identification in our blogpost_detail.html. Every time a logged-in user clicks the Like button, we will retrieve his id and then will check if that user already liked or not the current blogpost (more specifically: if like from user x exists, then remove like from current blogpost, else, add like from user x to current blogpost). Then we will redirect the user to the same blogpost page (like a refresh of that page).

Now, it the same view.py file, where we've implemented our BlogPost DetailView, we need to add to our get_context_data the following:

# views.py
class BlogPostDetailView(DetailView):
    model = BlogPost
    # template_name = MainApp/BlogPost_detail.html
    # context_object_name = 'object'

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs)

        likes_connected = get_object_or_404(BlogPost, id=self.kwargs['pk'])
        liked = False
        if likes_connected.likes.filter(id=self.request.user.id).exists():
            liked = True
        data['number_of_likes'] = likes_connected.number_of_likes()
        data['post_is_liked'] = liked
        return data
Enter fullscreen mode Exit fullscreen mode

Within our get_context_data function, we will retrieve the current blogpost primary key, and we will check if the currently logged-in user has liked or not this blog post. We will store in a local variable this statement, to send it further as a context to our HTML-based blogpost_detail. We will also retrieve the number of likes (calling the earlier written function) in order to display the number of likes directly in our HTML template.

Let's also add this new function-based view to our urls.py:

# urls.py
from django.urls import path
from .views import (
    ...
    BlogPostLike,
    ...
)

urlpatterns = [
    ...
    path('blogpost-like/<int:pk>', views.BlogPostLike, name="blogpost_like"),
    ...
]
Enter fullscreen mode Exit fullscreen mode

Finally, to our blogpost_detail.html let's write in the DjangoTemplateLanguage the following:

<!-- LIKES -->
{% if user.is_authenticated %}
  <form action="{% url 'blogpost_like' object.id %}" method="POST">
    {% csrf_token %}

    {% if post_is_liked %}
    <button type="submit" name="blogpost_id" value="{{object.id}}" class="btn btn-info">Unlike</button>
    {% else %}
    <button type="submit" name="blogpost_id" value="{{object.id}}" class="btn btn-info">Like</button>
    {% endif %}
  </form>
{% else %}
  <a class="btn btn-outline-info" href="{% url 'login' %}?next={{request.path}}">Log in to like this article!</a><br>
{% endif %}
<strong class="text-secondary">{{ number_of_likes }} Like{{ number_of_likes|pluralize }}</strong>
Enter fullscreen mode Exit fullscreen mode

And we are done!

Example of Like Button

Top comments (17)

Collapse
 
kaybrian profile image
kaybrian

nice this was amazing and nice. i really liked the way you brought the all concept into something else and nice to follow

Collapse
 
radualexandrub profile image
Radu-Alexandru B

Thanks!

Wish I had the time to improve on this article with newer technologies / better stack, dynamic JS (via a front-end framework) or create new content...

Collapse
 
agamerdan profile image
Merdan

Hi teacher it's awsome post thank you very much. But i give mistake when i was click like button. Field 'id' expected a number but got ''. plese help me

Collapse
 
mahfuzkhandaker profile image
MahfuzKhandaker

Awesome post. But I have a question, how it will be made ajaxify?

Collapse
 
radualexandrub profile image
Radu-Alexandru B

Yes... indeed a very good question that I currently don't have an answer to.

I had an intention to make a follow-up of this tutorial using Ajax, but I didn't make time for it... However, there are a very few tutorials on youtube on this matter, so I wish you the best if you will try to implement this feature. Unfortunately for me, I had to change my tech stack to match the jobs in my area.

Have a nice day!

Collapse
 
mahfuzkhandaker profile image
MahfuzKhandaker

Welcome. Please open the link
stackoverflow.com/questions/660922...

Collapse
 
leandropaolo1 profile image
leandropaolo1

how would you add it to BlogPostListView(ListView)?

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
labpeng profile image
lab-peng

A great tutorial!

Collapse
 
leandropaolo1 profile image
leandropaolo1

works great, good job Radu. You saved me a lot of time! Keep up the good work and good content creation

Collapse
 
enesislam profile image
enesstr

Excellent! How can I make it using AJAX? Thanks

Collapse
 
oladejo_hezekiah_1bc716c8 profile image
Oladejo Hezekiah

How about using djangorestframework to create endpoints for like functionality

Collapse
 
bhavdeepsaragadam profile image
Bhavdeep

Zero-argument form of "super" call is valid only within a class Pylance