Techno Blender
Digitally Yours.

Build a Blog Website using Django Rest Framework — Part 3

0 34


Photo by Sincerely Media on Unsplash

Hello folks, I hope you all are doing well and that you enjoyed the first two parts of this DRF article series. In the first part of the series, we dealt with the basics of setting up this project, and you all got an overview of the project we will build, while in the second part, we dealt with the users app of our application where we wrote the serializer and view for our users part

If you have not yet read those parts, I strongly suggest you go and first read the first two parts and then come back to this article.

In the third part of the series, we will deal with the complete posts app of our Django application, thus finishing up the complete backend for our application. We would also be testing the APIs using the Browsable API interface in the same way we did for the users app. So, let us dive right into the posts app and start building it.

So, when we move into the posts folder, we will see we have the same files as we did for the users app when we moved into that folder. First, we would be moving into the models.py file to build our models.

models.py

Let’s start with the models.py file first. We will define our database models here. So, we will have three different models for the posts part — Post, Upvote and Comment.

We will first start with importing the required dependencies into our file, as shown below:

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

As we can see, we have imported the default Usermodel and also imported modelsand reverse from Django. These would help us in building our models.

So, we would start with the Posts model. Let us first look into the code, and then we will understand the significance of those lines.

class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
title = models.CharField(max_length = 100)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
upvote_count = models.IntegerField(default = 0)

def __str__(self):
return self.title

def get_absolute_url(self):
return reverse('post-detail', kwargs = {'pk': self.pk})

The code above defines a Post class that inherits from Django’s models.Model class. This makes the Post class a Django model, which allows it to be saved in and retrieved from a database.

The Post class has several fields, including user, title, body, created, updated, and upvote_count. Each field is defined using a class from Django’s models module, which specifies the type of data that each field will store. For example, the title field is a CharField, which will store the title of the blog post, and the created field is a DateTimeField, which stores the time at which the post was created.

The user field is defined as a ForeignKey field, which means that it is a reference to the default Django User model. The on_delete argument specifies what should happen to a Post object if the User object that it references is deleted. Here, the on_delete argument is set to models.CASCADE, which signifies that if the User object is deleted, the Post object will also be deleted.

The ForeignKey field allows a Post to be linked to a specific User object, which can be accessed using the post.user attribute. This can be used to get the username of the user who created the Post, or to allow only the user who created the Post to edit or delete it.

The upvote_countfield will store the number of upvotes the blog post gets from the users. The default value has been set to zero, which signifies that when a new Post object is created, the upvote_count is set to zero. The upvote_count will be incremented when the user clicks on the upvote button on the blog post.

The __str__ method is a special method that defines how a Post object should be represented as a string. In this case, the method returns the title of the Post.

The get_absolute_url method returns the URL of the Post object’s detail page. Django uses this to determine the URL of the object when it is saved to the database.

Next, we would look into the Upvote model that will store all the upvotes in our application. Let us have a look at the model, and then I will explain the significance of the code.

class Upvote(models.Model):
user = models.ForeignKey(User, related_name = 'upvotes', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'upvotes', on_delete = models.CASCADE)

This code defines an Upvote class that inherits from Django’s models.Model class, just like the Post class that we discussed earlier.

The Upvote class has two fields: user and post. Both fields have been defined as ForeignKey fields, which signifies that they are references to User and Post objects, respectively. The related_name argument specifies the name that should be used to access the reverse relationship. For example, if a User object user has upvoted several Post objects, the user.upvotes attribute will return a queryset of the Post objects that user has upvoted.

The on_delete argument for both fields is set to models.CASCADE, which means that if either the User or the Post object that an Upvote object references is deleted, the Upvote object will also be deleted.

This class is used to track which users have upvoted which posts. We will use this to prevent a user from upvoting a Post more than once, or we can also use this to retrieve the list of users who have upvoted a given Post , though we will not be implementing the latter in our application. But, as we have seen, it is easy to do so, and you can surely try to implement it to improve the application.

Next, we move to the final model class Comment, which will store all the comments made on the blog posts in our application. The code for the same is below.

class Comment(models.Model):
user = models.ForeignKey(User, related_name = 'comments', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'comments', on_delete = models.CASCADE)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)

def __str__(self):
return self.body

The code defines a Comment class that inherits from Django’s models.Model class, just like the Post and Upvote classes.

The Comment class has four fields: user, post, body and created. The user and post fields are defined as ForeignKey fields, which means that they are references to User and Post objects, respectively. The related_name argument for both fields specifies the name that should be used to access the reverse relationship. For example, if a User object user has written several Comment objects, the user.comments attribute will return a queryset of the Comment objects that user has written.

The body field is a TextField, which stores the main content of the comment is stored.

The created field is a DateTimeField that stores the date and time when the Comment object was created. The auto_now_add argument is set to True, which means that the Comment object’s created field will automatically be set to the current date and time when the object is first created.

The __str__ method is a special method that defines how a Comment object should be represented as a string. In this case, the method returns the body of the Comment, which is the main content of the comment.

The Comment class is used to determine which users have commented on a particular Post object. We will use this to display the comments on a blog post along with the user’s name. Also, this can be used to allow only the user who created the comment to edit or delete it though we have not implemented this part in the current application.

The complete code for the models.py section can be found below:

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
title = models.CharField(max_length = 100)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
upvote_count = models.IntegerField(default = 0)

def __str__(self):
return self.title

def get_absolute_url(self):
return reverse('post-detail', kwargs = {'pk': self.pk})

class Upvote(models.Model):
user = models.ForeignKey(User, related_name = 'upvotes', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'upvotes', on_delete = models.CASCADE)

class Comment(models.Model):
user = models.ForeignKey(User, related_name = 'comments', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'comments', on_delete = models.CASCADE)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)

def __str__(self):
return self.body

Next, we would be moving to serializers.py file. Since this file would not be present by default, we need to create it as we did for the users app.

serializers.py

In this file, we will serialize our models to make them in a format which can be shared across the web and can be accessed by any frontend framework.

First, we will be importing the required dependencies in our file. We will import serializers from rest_framework package, and also we import the three models we created — Post, Upvote and Comment.

Next, we will write three serializers — one for each of the three models. The code for the serializers class is given below. Let us first look at the code, and then we can discuss it later.

from rest_framework import serializers
from .models import Post, Upvote, Comment

class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('title', 'body', 'created', 'updated', 'user', 'upvote_count')

class UpvoteSerializer(serializers.ModelSerializer):
class Meta:
model = Upvote
fields = ('user', 'post')

class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('user', 'post', 'body', 'created')

The structure is very similar to the serializer we wrote for the Users model in the previous article.

Each of the three classes written above defines a serializer for a specific model: Post, Upvote, and Comment. The Meta inner class of each serializer class specifies the model that the serializer should work with and which fields of the model should be included in the serialized data.

As we can see, PostSerializer will serialize instances of the Post model and will include the title, body, created, updated, user, and upvote_count fields in the serialized data.

Similarly, UpvoteSerializer and CommentSerializer will serialize their own fields.

Next, we move to the most important part — views, where we would be defining all the logic of the application, and we will deal with get, post, put and delete requests in that file.

views.py

In this section, we will define various views for handling different requests related to the three models, we have created.

We will be adding the following functionalities to our application:

  1. Seeing all the blog posts
  2. Creating a new blog post
  3. Editing a blog post written by that particular user
  4. Deleting a blog post written by that particular user
  5. Seeing the blog posts written by a specific user
  6. Upvoting the post
  7. Adding a comment on the post

So, let us start with the required imports.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions
from .models import Post, Upvote, Comment
from .serializers import PostSerializer, UpvoteSerializer, CommentSerializer
from django.contrib.auth.models import User

So, in the views.py file, we have imported various classes and functions — some from rest_framework package, while some are the serilializers and models.

APIView is a class that provides a way to define views that handle incoming HTTP requests in a RESTful manner. A view is a callable that takes an HTTP request as its argument and returns an HTTP response.

Response is a class that provides a way to create HTTP response objects. An HTTP response typically includes a status code, headers, and a body containing the response data.

status is a module that defines a number of HTTP status codes as constants. These codes indicate the success or failure of an HTTP request.

permissions is a module that provides classes for implementing permission checks in views. A permission is a rule that determines whether a user has the right to access a particular view.

The code also imports several model classes from the .models module, as well as the serializer classes from the .serializers module. Finally, it imports the User class from the django.contrib.auth.models module, which is a class that represents a user of the Django web framework, i.e. the default Django User model class.

Also, all the classes in the views.py file, we will require the user to be logged in, so we will use the permissions module to enforce this in all the classes.

Next, we will define PostListAPIView class which will provide the ability to fetch all the blog posts available and also will have the ability to create a new blog post. Here is the code for the same:

class PostListAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, *args, **kwargs):
posts = Post.objects.all()
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, *args, **kwargs):
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body')
}
serializer = PostSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

This is a subclass of APIView, which provides several methods for handling different HTTP request methods, such as GET, POST, PUT, and DELETE.

The permission_classes attribute of the PostListAPIView class specifies that only authenticated users are allowed to access this view. This is achieved by using the isAuthenticated method. This means that only users who have logged in will be able to see the list of posts or create new posts. We will be using the same in all other classes too.

The get method of the PostListAPIView class is called when a GET request is sent to the view. This method retrieves all the blog posts on the website and creates a serialized representation of the data using the PostSerializer class. It then returns this data in an HTTP response with a status code of 200 OK.

The post method of the PostListAPIView class is called when a POST request is sent to the view. This method creates a new Post object using the data from the request, serializes the data using the PostSerializer class, and returns the serialized data in an HTTP response with a status code of 201 CREATED. If the data from the request is not valid, it returns an error message with a status code of 400 BAD REQUEST.

Next, we define the PostDetailAPIView which will deal with the operations of an individual post in a blog. It will handle fetching that particular post, editing it or deleting it. It also has the same permissions which we had for the previous class. Here is the code for it:

class PostDetailAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

def put(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body'),
'upvote_count': post.upvote_count
}
serializer = PostSerializer(post, data = data, partial = True)
if serializer.is_valid():
if post.user.id == request.user.id:
serializer.save()
return Response(serializer.data, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to edit this post"}, status = status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
if post.user.id == request.user.id:
post.delete()
return Response({"res": "Object deleted!"}, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to delete this post"}, status = status.HTTP_401_UNAUTHORIZED)

The get_object method is a helper method that retrieves the post with the specified primary key (pk). If the post does not exist, it returns None.

The get method of the PostDetailAPIView class is called when a GET request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, serializes the post using the PostSerializer class, and returns the serialized data in an HTTP response with a status code of 200 OK. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

The put method of the PostDetailAPIView class is called when a PUT request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, updates the post with the data from the request and saves the updated post. If the current user is not the owner of the post, it returns an error message with a status code of 401 UNAUTHORIZED. If the data from the request is not valid, it returns an error message with a status code of 400 BAD REQUEST.

The delete method of the PostDetailAPIView class is called when a DELETE request is sent to the view. It uses the get_object method to retrieve the post with the specified pk and then deletes it if the current user is the owner of the post. If the current user is not the owner of the post, it returns an error message with a status code of 401 UNAUTHORIZED. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

Next, we will be having UserPostAPIView class which deals with viewing posts from a particular user.

class UserPostAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, username, *args, **kwargs):
user = User.objects.filter(username = username).first()
if user is None:
return Response({'error': 'User not found'}, status = status.HTTP_404_NOT_FOUND)
posts = Post.objects.filter(user = user)
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

The get method of the UserPostAPIView class retrieves the user with the specified username, retrieves all the posts created by that user, and then creates a serialized representation of the data using the PostSerializer class. It then returns this data in an HTTP response with a status code of 200 OK. If the user with the specified username does not exist, it returns an error message with a status code of 404 NOT FOUND.

Next, we will be having the UpvoteAPIView class which will deal with all the functionalities related to the upvotes part.

class UpvoteAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)

upvoters = post.upvotes.all().values_list('user', flat = True)
if request.user.id in upvoters:
post.upvote_count -= 1
post.upvotes.filter(user = request.user).delete()
else:
post.upvote_count += 1
upvote = Upvote(user = request.user, post = post)
upvote.save()
post.save()
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

The get_object method is a helper method that retrieves the post with the specified primary key (pk). If the post does not exist, it returns None. It is similar to the one we wrote for PostDetailAPIView class.

The post method of the UpvoteAPIView class uses the get_object method to retrieve the post with the specified pk and then either adds or removes an upvote for the post by the current user. It then updates the upvote_count of the post and saves the updated post. Finally, it creates a serialized representation of the post using the PostSerializer class and returns the serialized data in an HTTP response with a status code of 200 OK. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

Finally, we have the CommentAPIView which will deal with the comments made on a particular post. It will have the functionality to fetch all the comments for a particular post and to post a new comment.

class CommentAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
comments = Comment.objects.filter(post = post)
serializer = CommentSerializer(comments, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'post': post.id,
'body': request.data.get('body')
}
serializer = CommentSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

As we can see, it has the same get_object helper function, which takes in the primary key pk and returns the post corresponding to it.

The get method of the CommentAPIView class is called when a GET request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, retrieves all the comments on that post, and then creates a serialized representation of the data using the CommentSerializer class. It then returns this data in an HTTP response with a status code of 200 OK. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

The post method of the CommentAPIView class is called when a POST request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, creates a new Comment object using the data from the request, serializes the data using the CommentSerializer class, and returns the serialized data in an HTTP response with a status code of 201 CREATED. If the data from the request is not valid, it returns an error message with a status code of 400 BAD REQUEST. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

The complete code for the views.py file can be found below.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions
from .models import Post, Upvote, Comment
from .serializers import PostSerializer, UpvoteSerializer, CommentSerializer
from django.contrib.auth.models import User

class PostListAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, *args, **kwargs):
posts = Post.objects.all()
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, *args, **kwargs):
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body')
}
serializer = PostSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

class PostDetailAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

def put(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body'),
'upvote_count': post.upvote_count
}
serializer = PostSerializer(post, data = data, partial = True)
if serializer.is_valid():
if post.user.id == request.user.id:
serializer.save()
return Response(serializer.data, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to edit this post"}, status = status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
if post.user.id == request.user.id:
post.delete()
return Response({"res": "Object deleted!"}, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to delete this post"}, status = status.HTTP_401_UNAUTHORIZED)

class UserPostAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, username, *args, **kwargs):
user = User.objects.filter(username = username).first()
if user is None:
return Response({'error': 'User not found'}, status = status.HTTP_404_NOT_FOUND)
posts = Post.objects.filter(user = user)
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

class UpvoteAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)

upvoters = post.upvotes.all().values_list('user', flat = True)
if request.user.id in upvoters:
post.upvote_count -= 1
post.upvotes.filter(user = request.user).delete()
else:
post.upvote_count += 1
upvote = Upvote(user = request.user, post = post)
upvote.save()
post.save()
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

class CommentAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
comments = Comment.objects.filter(post = post)
serializer = CommentSerializer(comments, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'post': post.id,
'body': request.data.get('body')
}
serializer = CommentSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

Next, we move to admin.py file to register the models we created so that the admin panel has the access to those models.

admin.py

from django.contrib import admin
from .models import Post, Upvote, Comment

admin.site.register(Post)
admin.site.register(Upvote)
admin.site.register(Comment)

As shown above, we registered the three models — Post, Upvote and Comment.

Next, we will create a file named urls.py in our posts folder, and we will write the URLs for all the views we wrote for our posts app.

urls.py

In the urls.py file, we will be defining the URL patterns for our posts app, and then we will have to include them in the urls.py file present in the blog application.

from django.urls import path
from .views import PostListAPIView, PostDetailAPIView, UserPostAPIView, UpvoteAPIView, CommentAPIView

urlpatterns = [
path('', PostListAPIView.as_view()),
path('<int:pk>/', PostDetailAPIView.as_view()),
path('<int:pk>/upvote/', UpvoteAPIView.as_view()),
path('<int:pk>/comment/', CommentAPIView.as_view()),
path('<username>/', UserPostAPIView.as_view())
]

Here, urlpatterns is a list of URL patterns for the blog app. Each element in the list is a path object that maps a URL path to a view.

The first element in the list maps the root URL (/) to the PostListAPIView view, so when a user accesses the root URL, the PostListAPIView view will be called and handle the request. The other elements in the list map URLs of the form /<int:pk>/, /<int:pk>/upvote/, /<int:pk>/comment/, and /<username>/ to the corresponding views. These URLs are used to access individual posts, upvote/downvote posts, create comments on posts, and view the posts of a specific user.

<int:pk> is a URL path parameter that specifies that the URL path should contain an integer value (the primary key of the post) at that position. This integer value is passed as the pk argument to the view when the URL is accessed, and the view can use it to retrieve the post with the specified primary key. This allows the view to handle requests for specific posts rather than all posts.

Similarly, /<username>/ is used to access posts from a particular user. The username is passed to the view when the URL is accessed.

Now, in the blog application’s urls.py file, we will include the URLs of the posts app as we did for the users app.

from django.contrib import admin
from django.urls import path, include
from posts import urls as posts_urls
from users import urls as users_urls

urlpatterns = [
path("admin/", admin.site.urls),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("api/posts/", include(posts_urls)),
path("api/users/", include(users_urls)),
]

Before we can test the APIs, we need to perform migrations since we have created new models.

So, we will run the following command to do so:

python manage.py makemigrations
python manage.py migrate

We need to run a migration when we create a new model in Django because the database schema needs to match the structure of the data that will be stored in it. Without running a migration, the database will not have the correct tables and fields to store the data from the new model, and we will get errors when trying to save or retrieve data from the model.

The python manage.py makemigrations command analyzes the changes we have made to our models and creates a new migration file that contains the instructions for updating the database schema.

The python manage.py migrate command applies the changes in the migration file to the database. This updates the database schema and ensures it is in sync with our models.

We can test the APIs for the posts app in the same way as we did for the users app. Since this article has already been too big, so I will leave the testing part for the posts app for you to do yourself. It will be very much the same way you did for the users app by first running the server and then going to the respective URLs as defined in the urls.py file.

So, that brings us to the end of the backend part of this article series. I hope you all enjoyed the backend part and learnt how DRF works, and now you should try to build it on your own and add functionalities to it to enhance the application further.

I will soon start the work on the frontend of our website and will be adding the articles on the frontend part soon. I will link them up here when they are ready. Till then, keep learning!

Here are some more article series you would love to read:


Photo by Sincerely Media on Unsplash

Hello folks, I hope you all are doing well and that you enjoyed the first two parts of this DRF article series. In the first part of the series, we dealt with the basics of setting up this project, and you all got an overview of the project we will build, while in the second part, we dealt with the users app of our application where we wrote the serializer and view for our users part

If you have not yet read those parts, I strongly suggest you go and first read the first two parts and then come back to this article.

In the third part of the series, we will deal with the complete posts app of our Django application, thus finishing up the complete backend for our application. We would also be testing the APIs using the Browsable API interface in the same way we did for the users app. So, let us dive right into the posts app and start building it.

So, when we move into the posts folder, we will see we have the same files as we did for the users app when we moved into that folder. First, we would be moving into the models.py file to build our models.

models.py

Let’s start with the models.py file first. We will define our database models here. So, we will have three different models for the posts part — Post, Upvote and Comment.

We will first start with importing the required dependencies into our file, as shown below:

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

As we can see, we have imported the default Usermodel and also imported modelsand reverse from Django. These would help us in building our models.

So, we would start with the Posts model. Let us first look into the code, and then we will understand the significance of those lines.

class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
title = models.CharField(max_length = 100)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
upvote_count = models.IntegerField(default = 0)

def __str__(self):
return self.title

def get_absolute_url(self):
return reverse('post-detail', kwargs = {'pk': self.pk})

The code above defines a Post class that inherits from Django’s models.Model class. This makes the Post class a Django model, which allows it to be saved in and retrieved from a database.

The Post class has several fields, including user, title, body, created, updated, and upvote_count. Each field is defined using a class from Django’s models module, which specifies the type of data that each field will store. For example, the title field is a CharField, which will store the title of the blog post, and the created field is a DateTimeField, which stores the time at which the post was created.

The user field is defined as a ForeignKey field, which means that it is a reference to the default Django User model. The on_delete argument specifies what should happen to a Post object if the User object that it references is deleted. Here, the on_delete argument is set to models.CASCADE, which signifies that if the User object is deleted, the Post object will also be deleted.

The ForeignKey field allows a Post to be linked to a specific User object, which can be accessed using the post.user attribute. This can be used to get the username of the user who created the Post, or to allow only the user who created the Post to edit or delete it.

The upvote_countfield will store the number of upvotes the blog post gets from the users. The default value has been set to zero, which signifies that when a new Post object is created, the upvote_count is set to zero. The upvote_count will be incremented when the user clicks on the upvote button on the blog post.

The __str__ method is a special method that defines how a Post object should be represented as a string. In this case, the method returns the title of the Post.

The get_absolute_url method returns the URL of the Post object’s detail page. Django uses this to determine the URL of the object when it is saved to the database.

Next, we would look into the Upvote model that will store all the upvotes in our application. Let us have a look at the model, and then I will explain the significance of the code.

class Upvote(models.Model):
user = models.ForeignKey(User, related_name = 'upvotes', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'upvotes', on_delete = models.CASCADE)

This code defines an Upvote class that inherits from Django’s models.Model class, just like the Post class that we discussed earlier.

The Upvote class has two fields: user and post. Both fields have been defined as ForeignKey fields, which signifies that they are references to User and Post objects, respectively. The related_name argument specifies the name that should be used to access the reverse relationship. For example, if a User object user has upvoted several Post objects, the user.upvotes attribute will return a queryset of the Post objects that user has upvoted.

The on_delete argument for both fields is set to models.CASCADE, which means that if either the User or the Post object that an Upvote object references is deleted, the Upvote object will also be deleted.

This class is used to track which users have upvoted which posts. We will use this to prevent a user from upvoting a Post more than once, or we can also use this to retrieve the list of users who have upvoted a given Post , though we will not be implementing the latter in our application. But, as we have seen, it is easy to do so, and you can surely try to implement it to improve the application.

Next, we move to the final model class Comment, which will store all the comments made on the blog posts in our application. The code for the same is below.

class Comment(models.Model):
user = models.ForeignKey(User, related_name = 'comments', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'comments', on_delete = models.CASCADE)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)

def __str__(self):
return self.body

The code defines a Comment class that inherits from Django’s models.Model class, just like the Post and Upvote classes.

The Comment class has four fields: user, post, body and created. The user and post fields are defined as ForeignKey fields, which means that they are references to User and Post objects, respectively. The related_name argument for both fields specifies the name that should be used to access the reverse relationship. For example, if a User object user has written several Comment objects, the user.comments attribute will return a queryset of the Comment objects that user has written.

The body field is a TextField, which stores the main content of the comment is stored.

The created field is a DateTimeField that stores the date and time when the Comment object was created. The auto_now_add argument is set to True, which means that the Comment object’s created field will automatically be set to the current date and time when the object is first created.

The __str__ method is a special method that defines how a Comment object should be represented as a string. In this case, the method returns the body of the Comment, which is the main content of the comment.

The Comment class is used to determine which users have commented on a particular Post object. We will use this to display the comments on a blog post along with the user’s name. Also, this can be used to allow only the user who created the comment to edit or delete it though we have not implemented this part in the current application.

The complete code for the models.py section can be found below:

from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

class Post(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
title = models.CharField(max_length = 100)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
upvote_count = models.IntegerField(default = 0)

def __str__(self):
return self.title

def get_absolute_url(self):
return reverse('post-detail', kwargs = {'pk': self.pk})

class Upvote(models.Model):
user = models.ForeignKey(User, related_name = 'upvotes', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'upvotes', on_delete = models.CASCADE)

class Comment(models.Model):
user = models.ForeignKey(User, related_name = 'comments', on_delete = models.CASCADE)
post = models.ForeignKey(Post, related_name = 'comments', on_delete = models.CASCADE)
body = models.TextField()
created = models.DateTimeField(auto_now_add = True)

def __str__(self):
return self.body

Next, we would be moving to serializers.py file. Since this file would not be present by default, we need to create it as we did for the users app.

serializers.py

In this file, we will serialize our models to make them in a format which can be shared across the web and can be accessed by any frontend framework.

First, we will be importing the required dependencies in our file. We will import serializers from rest_framework package, and also we import the three models we created — Post, Upvote and Comment.

Next, we will write three serializers — one for each of the three models. The code for the serializers class is given below. Let us first look at the code, and then we can discuss it later.

from rest_framework import serializers
from .models import Post, Upvote, Comment

class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('title', 'body', 'created', 'updated', 'user', 'upvote_count')

class UpvoteSerializer(serializers.ModelSerializer):
class Meta:
model = Upvote
fields = ('user', 'post')

class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('user', 'post', 'body', 'created')

The structure is very similar to the serializer we wrote for the Users model in the previous article.

Each of the three classes written above defines a serializer for a specific model: Post, Upvote, and Comment. The Meta inner class of each serializer class specifies the model that the serializer should work with and which fields of the model should be included in the serialized data.

As we can see, PostSerializer will serialize instances of the Post model and will include the title, body, created, updated, user, and upvote_count fields in the serialized data.

Similarly, UpvoteSerializer and CommentSerializer will serialize their own fields.

Next, we move to the most important part — views, where we would be defining all the logic of the application, and we will deal with get, post, put and delete requests in that file.

views.py

In this section, we will define various views for handling different requests related to the three models, we have created.

We will be adding the following functionalities to our application:

  1. Seeing all the blog posts
  2. Creating a new blog post
  3. Editing a blog post written by that particular user
  4. Deleting a blog post written by that particular user
  5. Seeing the blog posts written by a specific user
  6. Upvoting the post
  7. Adding a comment on the post

So, let us start with the required imports.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions
from .models import Post, Upvote, Comment
from .serializers import PostSerializer, UpvoteSerializer, CommentSerializer
from django.contrib.auth.models import User

So, in the views.py file, we have imported various classes and functions — some from rest_framework package, while some are the serilializers and models.

APIView is a class that provides a way to define views that handle incoming HTTP requests in a RESTful manner. A view is a callable that takes an HTTP request as its argument and returns an HTTP response.

Response is a class that provides a way to create HTTP response objects. An HTTP response typically includes a status code, headers, and a body containing the response data.

status is a module that defines a number of HTTP status codes as constants. These codes indicate the success or failure of an HTTP request.

permissions is a module that provides classes for implementing permission checks in views. A permission is a rule that determines whether a user has the right to access a particular view.

The code also imports several model classes from the .models module, as well as the serializer classes from the .serializers module. Finally, it imports the User class from the django.contrib.auth.models module, which is a class that represents a user of the Django web framework, i.e. the default Django User model class.

Also, all the classes in the views.py file, we will require the user to be logged in, so we will use the permissions module to enforce this in all the classes.

Next, we will define PostListAPIView class which will provide the ability to fetch all the blog posts available and also will have the ability to create a new blog post. Here is the code for the same:

class PostListAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, *args, **kwargs):
posts = Post.objects.all()
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, *args, **kwargs):
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body')
}
serializer = PostSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

This is a subclass of APIView, which provides several methods for handling different HTTP request methods, such as GET, POST, PUT, and DELETE.

The permission_classes attribute of the PostListAPIView class specifies that only authenticated users are allowed to access this view. This is achieved by using the isAuthenticated method. This means that only users who have logged in will be able to see the list of posts or create new posts. We will be using the same in all other classes too.

The get method of the PostListAPIView class is called when a GET request is sent to the view. This method retrieves all the blog posts on the website and creates a serialized representation of the data using the PostSerializer class. It then returns this data in an HTTP response with a status code of 200 OK.

The post method of the PostListAPIView class is called when a POST request is sent to the view. This method creates a new Post object using the data from the request, serializes the data using the PostSerializer class, and returns the serialized data in an HTTP response with a status code of 201 CREATED. If the data from the request is not valid, it returns an error message with a status code of 400 BAD REQUEST.

Next, we define the PostDetailAPIView which will deal with the operations of an individual post in a blog. It will handle fetching that particular post, editing it or deleting it. It also has the same permissions which we had for the previous class. Here is the code for it:

class PostDetailAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

def put(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body'),
'upvote_count': post.upvote_count
}
serializer = PostSerializer(post, data = data, partial = True)
if serializer.is_valid():
if post.user.id == request.user.id:
serializer.save()
return Response(serializer.data, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to edit this post"}, status = status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
if post.user.id == request.user.id:
post.delete()
return Response({"res": "Object deleted!"}, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to delete this post"}, status = status.HTTP_401_UNAUTHORIZED)

The get_object method is a helper method that retrieves the post with the specified primary key (pk). If the post does not exist, it returns None.

The get method of the PostDetailAPIView class is called when a GET request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, serializes the post using the PostSerializer class, and returns the serialized data in an HTTP response with a status code of 200 OK. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

The put method of the PostDetailAPIView class is called when a PUT request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, updates the post with the data from the request and saves the updated post. If the current user is not the owner of the post, it returns an error message with a status code of 401 UNAUTHORIZED. If the data from the request is not valid, it returns an error message with a status code of 400 BAD REQUEST.

The delete method of the PostDetailAPIView class is called when a DELETE request is sent to the view. It uses the get_object method to retrieve the post with the specified pk and then deletes it if the current user is the owner of the post. If the current user is not the owner of the post, it returns an error message with a status code of 401 UNAUTHORIZED. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

Next, we will be having UserPostAPIView class which deals with viewing posts from a particular user.

class UserPostAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, username, *args, **kwargs):
user = User.objects.filter(username = username).first()
if user is None:
return Response({'error': 'User not found'}, status = status.HTTP_404_NOT_FOUND)
posts = Post.objects.filter(user = user)
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

The get method of the UserPostAPIView class retrieves the user with the specified username, retrieves all the posts created by that user, and then creates a serialized representation of the data using the PostSerializer class. It then returns this data in an HTTP response with a status code of 200 OK. If the user with the specified username does not exist, it returns an error message with a status code of 404 NOT FOUND.

Next, we will be having the UpvoteAPIView class which will deal with all the functionalities related to the upvotes part.

class UpvoteAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)

upvoters = post.upvotes.all().values_list('user', flat = True)
if request.user.id in upvoters:
post.upvote_count -= 1
post.upvotes.filter(user = request.user).delete()
else:
post.upvote_count += 1
upvote = Upvote(user = request.user, post = post)
upvote.save()
post.save()
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

The get_object method is a helper method that retrieves the post with the specified primary key (pk). If the post does not exist, it returns None. It is similar to the one we wrote for PostDetailAPIView class.

The post method of the UpvoteAPIView class uses the get_object method to retrieve the post with the specified pk and then either adds or removes an upvote for the post by the current user. It then updates the upvote_count of the post and saves the updated post. Finally, it creates a serialized representation of the post using the PostSerializer class and returns the serialized data in an HTTP response with a status code of 200 OK. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

Finally, we have the CommentAPIView which will deal with the comments made on a particular post. It will have the functionality to fetch all the comments for a particular post and to post a new comment.

class CommentAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
comments = Comment.objects.filter(post = post)
serializer = CommentSerializer(comments, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'post': post.id,
'body': request.data.get('body')
}
serializer = CommentSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

As we can see, it has the same get_object helper function, which takes in the primary key pk and returns the post corresponding to it.

The get method of the CommentAPIView class is called when a GET request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, retrieves all the comments on that post, and then creates a serialized representation of the data using the CommentSerializer class. It then returns this data in an HTTP response with a status code of 200 OK. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

The post method of the CommentAPIView class is called when a POST request is sent to the view. It uses the get_object method to retrieve the post with the specified pk, creates a new Comment object using the data from the request, serializes the data using the CommentSerializer class, and returns the serialized data in an HTTP response with a status code of 201 CREATED. If the data from the request is not valid, it returns an error message with a status code of 400 BAD REQUEST. If the post with the specified pk does not exist, it returns an error message with a status code of 404 NOT FOUND.

The complete code for the views.py file can be found below.

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions
from .models import Post, Upvote, Comment
from .serializers import PostSerializer, UpvoteSerializer, CommentSerializer
from django.contrib.auth.models import User

class PostListAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, *args, **kwargs):
posts = Post.objects.all()
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, *args, **kwargs):
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body')
}
serializer = PostSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

class PostDetailAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

def put(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'title': request.data.get('title'),
'body': request.data.get('body'),
'upvote_count': post.upvote_count
}
serializer = PostSerializer(post, data = data, partial = True)
if serializer.is_valid():
if post.user.id == request.user.id:
serializer.save()
return Response(serializer.data, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to edit this post"}, status = status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
if post.user.id == request.user.id:
post.delete()
return Response({"res": "Object deleted!"}, status = status.HTTP_200_OK)
return Response({"error": "You are not authorized to delete this post"}, status = status.HTTP_401_UNAUTHORIZED)

class UserPostAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, username, *args, **kwargs):
user = User.objects.filter(username = username).first()
if user is None:
return Response({'error': 'User not found'}, status = status.HTTP_404_NOT_FOUND)
posts = Post.objects.filter(user = user)
serializer = PostSerializer(posts, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

class UpvoteAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)

upvoters = post.upvotes.all().values_list('user', flat = True)
if request.user.id in upvoters:
post.upvote_count -= 1
post.upvotes.filter(user = request.user).delete()
else:
post.upvote_count += 1
upvote = Upvote(user = request.user, post = post)
upvote.save()
post.save()
serializer = PostSerializer(post)
return Response(serializer.data, status = status.HTTP_200_OK)

class CommentAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]

def get_object(self, pk):
try:
return Post.objects.get(pk = pk)
except Post.DoesNotExist:
return None

def get(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
comments = Comment.objects.filter(post = post)
serializer = CommentSerializer(comments, many = True)
return Response(serializer.data, status = status.HTTP_200_OK)

def post(self, request, pk, *args, **kwargs):
post = self.get_object(pk)
if post is None:
return Response({'error': 'Post not found'}, status = status.HTTP_404_NOT_FOUND)
data = {
'user': request.user.id,
'post': post.id,
'body': request.data.get('body')
}
serializer = CommentSerializer(data = data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

Next, we move to admin.py file to register the models we created so that the admin panel has the access to those models.

admin.py

from django.contrib import admin
from .models import Post, Upvote, Comment

admin.site.register(Post)
admin.site.register(Upvote)
admin.site.register(Comment)

As shown above, we registered the three models — Post, Upvote and Comment.

Next, we will create a file named urls.py in our posts folder, and we will write the URLs for all the views we wrote for our posts app.

urls.py

In the urls.py file, we will be defining the URL patterns for our posts app, and then we will have to include them in the urls.py file present in the blog application.

from django.urls import path
from .views import PostListAPIView, PostDetailAPIView, UserPostAPIView, UpvoteAPIView, CommentAPIView

urlpatterns = [
path('', PostListAPIView.as_view()),
path('<int:pk>/', PostDetailAPIView.as_view()),
path('<int:pk>/upvote/', UpvoteAPIView.as_view()),
path('<int:pk>/comment/', CommentAPIView.as_view()),
path('<username>/', UserPostAPIView.as_view())
]

Here, urlpatterns is a list of URL patterns for the blog app. Each element in the list is a path object that maps a URL path to a view.

The first element in the list maps the root URL (/) to the PostListAPIView view, so when a user accesses the root URL, the PostListAPIView view will be called and handle the request. The other elements in the list map URLs of the form /<int:pk>/, /<int:pk>/upvote/, /<int:pk>/comment/, and /<username>/ to the corresponding views. These URLs are used to access individual posts, upvote/downvote posts, create comments on posts, and view the posts of a specific user.

<int:pk> is a URL path parameter that specifies that the URL path should contain an integer value (the primary key of the post) at that position. This integer value is passed as the pk argument to the view when the URL is accessed, and the view can use it to retrieve the post with the specified primary key. This allows the view to handle requests for specific posts rather than all posts.

Similarly, /<username>/ is used to access posts from a particular user. The username is passed to the view when the URL is accessed.

Now, in the blog application’s urls.py file, we will include the URLs of the posts app as we did for the users app.

from django.contrib import admin
from django.urls import path, include
from posts import urls as posts_urls
from users import urls as users_urls

urlpatterns = [
path("admin/", admin.site.urls),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("api/posts/", include(posts_urls)),
path("api/users/", include(users_urls)),
]

Before we can test the APIs, we need to perform migrations since we have created new models.

So, we will run the following command to do so:

python manage.py makemigrations
python manage.py migrate

We need to run a migration when we create a new model in Django because the database schema needs to match the structure of the data that will be stored in it. Without running a migration, the database will not have the correct tables and fields to store the data from the new model, and we will get errors when trying to save or retrieve data from the model.

The python manage.py makemigrations command analyzes the changes we have made to our models and creates a new migration file that contains the instructions for updating the database schema.

The python manage.py migrate command applies the changes in the migration file to the database. This updates the database schema and ensures it is in sync with our models.

We can test the APIs for the posts app in the same way as we did for the users app. Since this article has already been too big, so I will leave the testing part for the posts app for you to do yourself. It will be very much the same way you did for the users app by first running the server and then going to the respective URLs as defined in the urls.py file.

So, that brings us to the end of the backend part of this article series. I hope you all enjoyed the backend part and learnt how DRF works, and now you should try to build it on your own and add functionalities to it to enhance the application further.

I will soon start the work on the frontend of our website and will be adding the articles on the frontend part soon. I will link them up here when they are ready. Till then, keep learning!

Here are some more article series you would love to read:

FOLLOW US ON GOOGLE NEWS

Read original article here

Denial of responsibility! Techno Blender is an automatic aggregator of the all world’s media. In each content, the hyperlink to the primary source is specified. All trademarks belong to their rightful owners, all materials to their authors. If you are the owner of the content and do not want us to publish your materials, please contact us by email – [email protected]. The content will be deleted within 24 hours.

Leave a comment