This post is the official finishing up of the Writing Instagram in Python series. It’s not extremely dependent on previous posts but knowledge of the system we built will be beneficial. Let’s begin.
I initially planned on developing a Like
model myself but I realized that that might be a little too complicated. It would be a good challenge however and I encourage you to try to build one yourself but we don’t want to reinvent the wheel. Ironic, since we’re trying to reinvent an already popular website but I’m digressing so let’s tackle the issue at hand — voting.
We shall be using a popular third party Django app — django-vote. It’s important to understand how that works however and so we will be discussing the design. We won’t go exactly how the app works but a high level overview that’s just enough.
How can we track likes for posts? Let’s break down a “like”. We know that a user has to make one, a “like”. We know that this user has to make this like to a post. I can already see two fields inside this abstract like model — a foreign key to a user and one to a post. Let’s add in a timestamp for each like in case we ever want to implement a statistics feature in the future. To translate this mental model in Django, this could be an implementation —
class Like(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey('post.Post', on_delete=models.CASCADE)
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
I can already see some interesting queries we can make with such a model. For example, we could aggregate a list of posts that the followers of a user like, select the most frequently occuring set of posts, get the user of said posts with the highest number of likes, and make a recommendation system for user followers. Or an explore page based off of the public users that the followers of a user follow with all the most liked pages. Suddenly, the possibilitie seem endless.
Alright, we’ve discussed enough theory, let’s get to the implementation. Install the package using pip
and then make another app, like
.
$ pip install django-vote
$ ./manage.py startapp like
Why are we starting another app when we just installed a third party package? The most compelling reason is just to separate our views. But first let’s configure our Post
model to be votable. Thankfully, it’s as simple as inhering from the VoteModel
class provided by django-vote —
from vote.models import VoteModel
class Post(VoteModel, models.Model):
...
And that’s it! You have to run your migrations again, so make sure to do that. Let’s finally write a view to like a Post. In your like/views.py
, enter the following —
Alright, let’s look at what’s happening here. We import our required modules and define a POST method for our PostLikeAPIView
class. The post_id is the only field that is required in the body of the request and we make sure we receive it. Next, we first query the existence of the post itself and then we check whether the signed in user has the “permission” to like said post.
This “permission” has to be determind by several factors. First, check if the user who posted the post has a public account. If they do, the currently signed in user has the permission.
If they have it on private, check if the signed in user is the same as the poster. If they are, they obviously have the permission to like their own post.
Finally, check if the poster’s id
is in the QuerySet
of the users the current user follows.
If either of these clauses evaluate to be true, the post can be liked. If none of them are true, simply return a response with a 404
status code.
Now we have the problem of removing a like from a Post. This is even easier since we don’t even have to check for permissions here and the API for django-vote doesn’t raise any exceptions for a user deleting a vote from a model. So you can copy the code for PostLikeAPIView
up until where we perform a get_object_or_404
call and write the following —
post.votes.delete(user.id)
return Response(status=status.HTTP_204_NO_CONTENT)
That’s it!
We’re almost done but there are still two pressing concerns — we wish to count the likes for each post and we also want to have a field in our serializer that specifies whether a post has been liked by the user who made the request. Sort of like this —
[
{
"id": 1,
"user": {
"picture": "/media/default.png",
"username": "admin"
},
"images": [
{
"file": "/media/post_images/Ganesh.jpeg"
}
],
"is_liked": false,
"num_vote_up": 0,
"caption": "Ganesh."
}
]
The num_vote_up
is automatically added by django-vote
for our model and serializers, and thus it solves the problem of counting the likes. The is_liked
field requires a bit of hacking around. Go in your post/serializers.py
file and exclude a few other fields that django-vote
added —
class Meta:
exclude = ['timestamp', 'vote_score', 'num_vote_down']
We don’t care about the downvotes a post recieved since we don’t provide the ability to dislike a post. Neither do we care about the vote_score
field since that is just the total upvotes subtracted by the total downvotes — or in our case, simply all the upvotes.
Now, let’s figure out how to fetch the like status of a post. We know from the django-vote
API that we only need to call votes.exist(user.id)
on a post instance to check whether the user has like said post. So we require two things —
- The user requesting the post(s).
- The post instance itself.
Thankfully, rest framework provides a nice serializer field called SerializerMethodField
that lets us dynamically inject data by having access to the request instance passed by Django. We simply define a new field as a SerializerMethodField
and then define a method inside the serializer with the name as get_<field_name>
. It looks easier than I just described —
That is our final PostSerializer
. And believe it or not, we’re done! We now have the ability to like / unlike posts and get them in JSON with a field specifying whether the caller of the endpoint has liked them or not!
If you’ve made it this far, give yourself a pat on the back. We’ve developed quite a clean albiet simple in just a few hours. And that calls for a celebration!
At this point, our backend can handle —
- User sign ups
- User log ins
- Post creation with multiple images
- Post updating with image handling
- Following other users
- Unfollowing other users
- Feed generation based on follow relationships
- Post liking by users
- Post unliking by users
- Fetch feeds with information about like count and sign in user like status
- And can do much more with all the power you now hold!
A follow up on this post with the comments feature will be available in the near future. For now, I bid adieu. Happy hacking!