Django Rest Framework basic usage

Django Rest Framework basic usage

Django Rest Framework is a powerful and flexible toolkit for building web APIs. This post covers some basic usage of Django Rest Framework, including deploying an API for CRUD with Django models and serializers.

Installation

To install Django Rest Framework, we can use pip:

1
pip install djangorestframework

Then add rest_framework to INSTALLED_APPS in settings.py:

1
2
3
4
INSTALLED_APPS = [
...
'rest_framework',
]

Models

Assume we have models for Note, Tag and Category. A note can have multiple tags and one category. The models are defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Tag(models.Model):
tag_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)

def __str__(self):
return self.name


class Category(models.Model):
category_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)

def __str__(self):
return self.name


class Note(models.Model):
note_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=50)
summary = models.CharField(max_length=255, null=True, blank=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
tags = models.ManyToManyField(Tag)
category = models.ForeignKey(Category, on_delete=models.DO_NOTHING, null=True, blank=True)

def __str__(self):
return self.title

API

If we want to have CRUD operations on single note, we can define the url as follows:

1
2
3
urlpatterns = [
path('<int:note_id>', views.note, name='notes'),
]

And the view function can be defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@api_view(['GET', 'DELETE', 'PUT'])
def note(request, note_id):

note = get_note_by_id(note_id)

if note is None:
return Response({'message': 'Note not found'}, status=404)

if request.method == 'GET':
return Response(NoteSerializer(note).data)

elif request.method == 'DELETE':
note.delete()
return Response({'message': 'Note deleted'}, status=204)

elif request.method == 'PUT':
serializer = NoteSerializer(note, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=400)

By using api_view, we don’t need to define the content type of the request. The request.data will be parsed according to the content type of the request.

Serializers

To return the data in JSON format, we need to define serializers for models. We can use ModelSerializer to simplify the process. According to the DRF documentation, ModelSerializer is a shortcut to create a serializer class with fields that correspond to the Model fields.

The ModelSerializer class is the same as a regular Serializer class, except that:

  • It will automatically generate a set of fields for you, based on the model.
  • It will automatically generate validators for the serializer, such as unique_together validators.
  • It includes simple default implementations of .create() and .update().

Here are examples of serializers for Tag and Category:

1
2
3
4
5
6
7
8
9
10
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__' # return all fields in the model


class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'

These serializers are simple because we are using ModelSerializer. For the Note model, we need to customize the serializer to include the related fields. If we define the serializer as follows, the related fields will be returned as primary keys:

1
2
3
4
5
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
fields = ['note_id', 'title', 'content', 'created_at',
'updated_at', 'tags', 'category']

Result:

1
2
3
4
5
6
7
8
9
{
"note_id": 1,
"title": "Django Rest Framework basic usage",
"content": "Django Rest Framework is a powerful and flexible toolkit for building web APIs. This post covers some basic usage of Django Rest Framework, including deploying an API for CRUD with Django models and serializers.",
"created_at": "2024-03-16T23:13:08.000000Z",
"updated_at": "2024-03-16T23:13:08.000000Z",
"tags": [1, 2, 3],
"category": 1
}

To get the names of tags and category, we need to customize tags and category fields in the serializer:

1
2
3
4
5
6
7
8
9
class NoteSerializer(serializers.ModelSerializer):

tags = TagSerializer(many=True)
category = CategorySerializer()

class Meta:
model = Note
fields = ['note_id', 'title', 'content', 'created_at',
'updated_at', 'tags', 'category']

Result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"note_id": 1,
"title": "Django Rest Framework basic usage",
"content": "Django Rest Framework is a powerful and flexible toolkit for building web APIs. This post covers some basic usage of Django Rest Framework, including deploying an API for CRUD with Django models and serializers.",
"created_at": "2024-03-16T23:13:08.000000Z",
"updated_at": "2024-03-16T23:13:08.000000Z",
"tags": [
{
"tag_id": 1,
"name": "Python"
},
{
"tag_id": 2,
"name": "Django"
},
{
"tag_id": 3,
"name": "Django Rest Framework"
}
],
"category": {
"category_id": 1,
"name": "Python"
}
}

PUT method

The PUT request for DRF is a little bit tricky. We need to override the update method in the serializer to handle the related fields. Here is an example of the update method in the NoteSerializer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def update(self, instance, validated_data):
# Get tags data and category data from validated_data
# Return [] if tags is not in validated_data
tags_data = validated_data.pop('tags', [])
# Return None if category is not in validated_data
category_data = validated_data.pop('category', None)

instance = super(NoteSerializer, self).update(instance, validated_data)

instance.tags.clear()
for tag_data in tags_data:
tag, created = Tag.objects.get_or_create(name=tag_data['name'])
instance.tags.add(tag)

if category_data is not None:
category, created = Category.objects.get_or_create(name=category_data['name'])
instance.category = category
instance.save()

return instance

The body of the PUT request should be in JSON format. Here is an example of the body:

1
2
3
4
5
6
7
8
9
10
{
"title": "Django Rest Framework basic usage",
"content": "Django Rest Framework is a powerful and flexible toolkit for building web APIs. This post covers some basic usage of Django Rest Framework, including deploying an API for CRUD with Django models and serializers.",
"tags": [
{"name": "Python"},
{"name": "Django"},
{"name": "Django Rest Framework"}
],
"category": {"name": "Python"}
}

POST method

The POST request is similar to the PUT request. We need to override the create method in the serializer to handle the related fields. Here is an example of the create method in the NoteSerializer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def create(self, validated_data):
# Get tags data and category data from validated_data
tags_data = validated_data.pop('tags')
category_data = validated_data.pop('category')

note = Note.objects.create(**validated_data)

for tag_data in tags_data:
tag, created = Tag.objects.get_or_create(name=tag_data['name'])
note.tags.add(tag)

category, created = Category.objects.get_or_create(name=category_data['name'])
note.category = category
note.save()

return note

The body of the POST request should be in JSON format. Here is an example of the body:

1
2
3
4
5
6
7
8
9
10
{
"title": "Django Rest Framework basic usage",
"content": "Django Rest Framework is a powerful and flexible toolkit for building web APIs. This post covers some basic usage of Django Rest Framework, including deploying an API for CRUD with Django models and serializers.",
"tags": [
{"name": "Python"},
{"name": "Django"},
{"name": "Django Rest Framework"}
],
"category": {"name": "Python"}
}

Comments