Django REST Framework từ cơ bản đến Nâng Cao
1. Giới thiệu
Django REST Framework (DRF) là một thư viện mạnh mẽ và được sử dụng rộng rãi trong cộng đồng Python để xây dựng các Web API tuân thủ kiến trúc REST (Representational State Transfer). DRF được xây dựng trên nền tảng Django Framework, kế thừa các ưu điểm về ORM (Object-Relational Mapping), bảo mật và khả năng mở rộng của Django, đồng thời bổ sung các công cụ chuyên biệt cho việc phát triển API.
Tài liệu này được thiết kế với phương pháp tiếp cận hàn lâm, cung cấp cái nhìn toàn diện về kiến trúc, các thành phần cốt lõi, và quy trình phát triển API một cách có hệ thống. Mục tiêu là trang bị cho người đọc không chỉ kiến thức thực hành mà còn nền tảng lý thuyết vững chắc để thiết kế các giải pháp API chất lượng cao, có khả năng bảo trì và mở rộng.
1.1 Kiến trúc REST và nguyên lý thiết kế
REST (Representational State Transfer) là một phong cách kiến trúc phần mềm được Roy Fielding đề xuất năm 2000 trong luận án tiến sĩ của ông. REST định nghĩa một tập hợp các ràng buộc kiến trúc nhằm tạo ra các hệ thống phân tán có khả năng mở rộng cao, bao gồm:
- Client-Server: Tách biệt giao diện người dùng khỏi lưu trữ dữ liệu
- Stateless: Mỗi request từ client phải chứa đầy đủ thông tin cần thiết
- Cacheable: Response phải được định nghĩa rõ ràng có thể cache hay không
- Uniform Interface: Giao diện thống nhất giữa các thành phần
- Layered System: Kiến trúc phân lớp cho phép thêm các tầng trung gian
- Code on Demand (tùy chọn): Server có thể mở rộng chức năng client
1.2 Lợi ích của Django REST Framework
DRF cung cấp một bộ công cụ hoàn chỉnh để triển khai các nguyên lý REST một cách hiệu quả:
- Serialization mạnh mẽ: Chuyển đổi linh hoạt giữa các kiểu dữ liệu phức tạp và JSON/XML
- Authentication đa dạng: Hỗ trợ nhiều phương thức xác thực (Session, Token, JWT, OAuth2)
- Permission system linh hoạt: Kiểm soát truy cập chi tiết ở nhiều cấp độ
- Browsable API: Giao diện web tương tác để test và khám phá API
- Extensive documentation: Tài liệu chi tiết và cộng đồng lớn
2. Thiết lập môi trường phát triển
2.1 Yêu cầu hệ thống
Trước khi bắt đầu, cần đảm bảo hệ thống đáp ứng các yêu cầu tối thiểu:
- Python: Phiên bản 3.10 trở lên (khuyến nghị Python 3.11+ cho hiệu năng tối ưu)
- pip: Package manager của Python (thường đi kèm với Python)
- virtualenv: Công cụ quản lý môi trường ảo (tùy chọn nhưng được khuyến nghị)
Kiểm tra phiên bản Python hiện tại:
python --version
# hoặc
python3 --version
2.2 Thiết lập môi trường ảo (Virtual Environment)
Môi trường ảo là một thực hành tốt nhất trong phát triển Python, giúp cách ly các dependencies của dự án và tránh xung đột giữa các phiên bản thư viện khác nhau.
2.2.1 Tạo môi trường ảo
# Sử dụng module venv tích hợp
python -m venv venv
# Hoặc sử dụng virtualenv
virtualenv venv
2.2.2 Kích hoạt môi trường ảo
# Trên Linux/macOS
source venv/bin/activate
# Trên Windows (Command Prompt)
venv\Scripts\activate.bat
# Trên Windows (PowerShell)
venv\Scripts\Activate.ps1
Sau khi kích hoạt, dấu nhắc lệnh sẽ hiển thị (venv) ở đầu, cho biết bạn đang làm việc trong môi trường ảo.
2.3 Cài đặt các thư viện cần thiết
2.3.1 Cài đặt cơ bản
# Cài đặt Django và Django REST Framework
pip install django djangorestframework
# Cài đặt các thư viện bổ sung thường dùng
pip install django-filter # Hỗ trợ filtering nâng cao
pip install markdown # Hỗ trợ markdown trong browsable API
pip install pygments # Syntax highlighting trong browsable API
2.3.2 Quản lý dependencies
Tạo file requirements.txt để quản lý các thư viện:
pip freeze > requirements.txt
Cài đặt từ requirements.txt:
pip install -r requirements.txt
2.4 Khởi tạo dự án Django
2.4.1 Tạo project mới
# Tạo Django project
django-admin startproject drf_project
# Di chuyển vào thư mục project
cd drf_project
# Tạo application
python manage.py startapp api
2.4.2 Cấu trúc thư mục dự án
drf_project/
├── drf_project/
│ ├── __init__.py
│ ├── settings.py # Cấu hình chính của project
│ ├── urls.py # URL routing chính
│ ├── asgi.py # ASGI configuration
│ └── wsgi.py # WSGI configuration
├── api/
│ ├── __init__.py
│ ├── admin.py # Cấu hình Django admin
│ ├── apps.py # Cấu hình application
│ ├── models.py # Định nghĩa data models
│ ├── serializers.py # Serializers (cần tạo)
│ ├── views.py # API views
│ ├── urls.py # URL routing cho app (cần tạo)
│ └── tests.py # Unit tests
└── manage.py # Django management script
2.4.3 Đăng ký application trong settings.py
Mở file drf_project/settings.py và thêm các ứng dụng vào INSTALLED_APPS:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party apps
'rest_framework',
'django_filters',
# Local apps
'api.apps.ApiConfig',
]
2.4.4 Cấu hình cơ bản cho DRF
Thêm cấu hình DRF vào settings.py:
REST_FRAMEWORK = {
# Cấu hình renderer mặc định
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
# Cấu hình parser mặc định
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
],
}
2.5 Migration và chạy server
# Tạo migration cho các models mặc định
python manage.py migrate
# Tạo superuser để truy cập admin
python manage.py createsuperuser
# Chạy development server
python manage.py runserver
Truy cập http://127.0.0.1:8000/ để kiểm tra server đã hoạt động.
3. Các thành phần cốt lõi trong Django REST Framework
3.1 Models - Tầng dữ liệu
Models trong Django là đại diện trừu tượng của cơ sở dữ liệu, tuân theo nguyên tắc ORM (Object-Relational Mapping). Mỗi model tương ứng với một bảng trong database.
3.1.1 Thiết kế Model cơ bản
File api/models.py:
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinLengthValidator
class Article(models.Model):
"""
Model đại diện cho một bài viết trong hệ thống.
Attributes:
title: Tiêu đề bài viết, tối đa 255 ký tự
content: Nội dung chính của bài viết
author: Tác giả bài viết (foreign key đến User model)
status: Trạng thái xuất bản của bài viết
created_at: Thời điểm tạo bài viết (tự động)
updated_at: Thời điểm cập nhật gần nhất (tự động)
"""
# Choices cho status field
STATUS_CHOICES = [
('draft', 'Bản nháp'),
('published', 'Đã xuất bản'),
('archived', 'Đã lưu trữ'),
]
title = models.CharField(
max_length=255,
validators=[MinLengthValidator(5)],
help_text="Tiêu đề bài viết (5-255 ký tự)"
)
content = models.TextField(
validators=[MinLengthValidator(10)],
help_text="Nội dung bài viết (tối thiểu 10 ký tự)"
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='articles',
help_text="Tác giả bài viết"
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft',
help_text="Trạng thái xuất bản"
)
created_at = models.DateTimeField(
auto_now_add=True,
help_text="Thời điểm tạo"
)
updated_at = models.DateTimeField(
auto_now=True,
help_text="Thời điểm cập nhật gần nhất"
)
class Meta:
ordering = ['-created_at']
verbose_name = 'Bài viết'
verbose_name_plural = 'Các bài viết'
indexes = [
models.Index(fields=['-created_at']),
models.Index(fields=['status']),
]
def __str__(self):
return f"{self.title} - {self.author.username}"
def is_published(self):
"""Kiểm tra xem bài viết đã được xuất bản chưa."""
return self.status == 'published'
3.1.2 Model relationships nâng cao
Ví dụ về các mối quan hệ phức tạp hơn:
class Category(models.Model):
"""Model đại diện cho danh mục bài viết."""
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
class Tag(models.Model):
"""Model đại diện cho thẻ tag."""
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class ArticleExtended(models.Model):
"""Mở rộng Article model với relationships."""
article = models.OneToOneField(Article, on_delete=models.CASCADE)
# Many-to-One relationship
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
related_name='articles'
)
# Many-to-Many relationship
tags = models.ManyToManyField(
Tag,
related_name='articles',
blank=True
)
view_count = models.PositiveIntegerField(default=0)
featured = models.BooleanField(default=False)
3.1.3 Tạo và chạy migrations
# Tạo migration files
python manage.py makemigrations
# Xem SQL sẽ được thực thi
python manage.py sqlmigrate api 0001
# Áp dụng migrations
python manage.py migrate
# Kiểm tra trạng thái migrations
python manage.py showmigrations
3.2 Serializers - Tầng chuyển đổi dữ liệu
Serializers trong DRF đóng vai trò quan trọng trong việc chuyển đổi dữ liệu giữa các kiểu dữ liệu phức tạp (như Django model instances) và các kiểu dữ liệu đơn giản có thể được render thành JSON, XML hoặc các định dạng khác.
3.2.1 ModelSerializer cơ bản
File api/serializers.py:
from rest_framework import serializers
from .models import Article, Category, Tag, ArticleExtended
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
"""Serializer cho User model - chỉ hiển thị thông tin công khai."""
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name']
read_only_fields = ['id']
class ArticleSerializer(serializers.ModelSerializer):
"""
Serializer chính cho Article model.
Bao gồm:
- Nested serialization cho author
- Computed fields (is_published)
- Custom validation
"""
# Nested serializer để hiển thị thông tin đầy đủ của author
author = UserSerializer(read_only=True)
# Computed field - không tồn tại trong database
is_published = serializers.SerializerMethodField()
# Field chỉ để write, không hiển thị trong response
author_id = serializers.IntegerField(write_only=True, required=False)
class Meta:
model = Article
fields = [
'id',
'title',
'content',
'author',
'author_id',
'status',
'is_published',
'created_at',
'updated_at',
]
read_only_fields = ['id', 'created_at', 'updated_at']
def get_is_published(self, obj):
"""Method cho SerializerMethodField."""
return obj.is_published()
def validate_title(self, value):
"""Validation cho trường title."""
if len(value) < 5:
raise serializers.ValidationError(
"Tiêu đề phải có ít nhất 5 ký tự."
)
return value
def validate(self, attrs):
"""
Object-level validation.
Được gọi sau khi tất cả field-level validations hoàn thành.
"""
if attrs.get('status') == 'published' and len(attrs.get('content', '')) < 100:
raise serializers.ValidationError({
'content': 'Bài viết xuất bản phải có ít nhất 100 ký tự.'
})
return attrs
def create(self, validated_data):
"""Custom create logic."""
# Tự động gán author từ request user nếu không được cung cấp
if 'author_id' not in validated_data:
validated_data['author'] = self.context['request'].user
else:
author_id = validated_data.pop('author_id')
validated_data['author_id'] = author_id
return super().create(validated_data)
3.2.2 Nested Serializers và Relationships
class TagSerializer(serializers.ModelSerializer):
"""Serializer đơn giản cho Tag."""
class Meta:
model = Tag
fields = ['id', 'name']
class CategorySerializer(serializers.ModelSerializer):
"""Serializer cho Category với thống kê."""
# Đếm số lượng articles trong category
article_count = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ['id', 'name', 'description', 'article_count']
def get_article_count(self, obj):
return obj.articles.count()
class ArticleDetailSerializer(serializers.ModelSerializer):
"""
Serializer chi tiết cho Article với đầy đủ thông tin relationships.
Sử dụng cho detail view để tránh N+1 query problem.
"""
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True, source='articleextended.category')
tags = TagSerializer(many=True, read_only=True, source='articleextended.tags')
view_count = serializers.IntegerField(
read_only=True,
source='articleextended.view_count'
)
class Meta:
model = Article
fields = [
'id',
'title',
'content',
'author',
'category',
'tags',
'status',
'view_count',
'created_at',
'updated_at',
]
3.2.3 Serializer với custom fields
class ArticleCreateUpdateSerializer(serializers.ModelSerializer):
"""
Serializer riêng cho create và update operations.
Cho phép client gửi category_id và tag_ids thay vì nested objects.
"""
category_id = serializers.IntegerField(required=False, allow_null=True)
tag_ids = serializers.ListField(
child=serializers.IntegerField(),
required=False,
allow_empty=True
)
class Meta:
model = Article
fields = [
'id',
'title',
'content',
'status',
'category_id',
'tag_ids',
]
def create(self, validated_data):
# Extract relationship data
category_id = validated_data.pop('category_id', None)
tag_ids = validated_data.pop('tag_ids', [])
# Tạo article
validated_data['author'] = self.context['request'].user
article = Article.objects.create(**validated_data)
# Tạo ArticleExtended và set relationships
article_extended = ArticleExtended.objects.create(article=article)
if category_id:
article_extended.category_id = category_id
article_extended.save()
if tag_ids:
article_extended.tags.set(tag_ids)
return article
def update(self, instance, validated_data):
# Extract relationship data
category_id = validated_data.pop('category_id', None)
tag_ids = validated_data.pop('tag_ids', None)
# Update article fields
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Update relationships
article_extended, created = ArticleExtended.objects.get_or_create(
article=instance
)
if category_id is not None:
article_extended.category_id = category_id
article_extended.save()
if tag_ids is not None:
article_extended.tags.set(tag_ids)
return instance
3.3 Views - Tầng xử lý logic
Views trong DRF xử lý các HTTP requests và trả về responses. DRF cung cấp nhiều class-based views với các mức độ trừu tượng khác nhau.
3.3.1 ViewSets - Cách tiếp cận cao nhất
File api/views.py:
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django.db.models import Q
from .models import Article, Category, Tag
from .serializers import (
ArticleSerializer,
ArticleDetailSerializer,
ArticleCreateUpdateSerializer,
CategorySerializer,
TagSerializer,
)
class ArticleViewSet(viewsets.ModelViewSet):
"""
ViewSet đầy đủ cho Article model.
Cung cấp các actions:
- list: GET /articles/
- create: POST /articles/
- retrieve: GET /articles/{id}/
- update: PUT /articles/{id}/
- partial_update: PATCH /articles/{id}/
- destroy: DELETE /articles/{id}/
Custom actions:
- published: GET /articles/published/
- publish: POST /articles/{id}/publish/
"""
permission_classes = [IsAuthenticatedOrReadOnly]
def get_queryset(self):
"""
Tùy chỉnh queryset dựa trên action và user.
Áp dụng select_related/prefetch_related để tối ưu queries.
"""
queryset = Article.objects.all()
# Optimize queries cho detail view
if self.action == 'retrieve':
queryset = queryset.select_related(
'author',
'articleextended__category'
).prefetch_related(
'articleextended__tags'
)
else:
queryset = queryset.select_related('author')
# Chỉ hiển thị published articles cho anonymous users
if not self.request.user.is_authenticated:
queryset = queryset.filter(status='published')
# Users chỉ thấy articles của họ và published articles
elif not self.request.user.is_staff:
queryset = queryset.filter(
Q(author=self.request.user) | Q(status='published')
)
return queryset
def get_serializer_class(self):
"""Chọn serializer phù hợp dựa trên action."""
if self.action == 'retrieve':
return ArticleDetailSerializer
elif self.action in ['create', 'update', 'partial_update']:
return ArticleCreateUpdateSerializer
return ArticleSerializer
def perform_create(self, serializer):
"""Hook được gọi khi tạo object mới."""
serializer.save(author=self.request.user)
def perform_update(self, serializer):
"""Hook được gọi khi update object."""
serializer.save()
@action(detail=False, methods=['get'])
def published(self, request):
"""
Custom action: Lấy danh sách các bài viết đã xuất bản.
URL: GET /articles/published/
"""
published_articles = self.get_queryset().filter(status='published')
page = self.paginate_queryset(published_articles)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(published_articles, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""
Custom action: Xuất bản một bài viết.
URL: POST /articles/{id}/publish/
"""
article = self.get_object()
# Kiểm tra quyền
if article.author != request.user and not request.user.is_staff:
return Response(
{'detail': 'Bạn không có quyền xuất bản bài viết này.'},
status=status.HTTP_403_FORBIDDEN
)
# Validation trước khi publish
if len(article.content) < 100:
return Response(
{'detail': 'Bài viết phải có ít nhất 100 ký tự để xuất bản.'},
status=status.HTTP_400_BAD_REQUEST
)
article.status = 'published'
article.save()
serializer = self.get_serializer(article)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def increment_view(self, request, pk=None):
"""
Custom action: Tăng view count.
URL: POST /articles/{id}/increment_view/
"""
article = self.get_object()
article_extended, created = ArticleExtended.objects.get_or_create(
article=article
)
article_extended.view_count += 1
article_extended.save()
return Response({'view_count': article_extended.view_count})
class CategoryViewSet(viewsets.ModelViewSet):
"""ViewSet cho Category model."""
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [IsAuthenticatedOrReadOnly]
class TagViewSet(viewsets.ModelViewSet):
"""ViewSet cho Tag model."""
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
3.3.2 Generic Views - Kiểm soát chi tiết hơn
Khi cần kiểm soát chi tiết hơn, có thể sử dụng Generic Views:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class ArticleListCreateView(generics.ListCreateAPIView):
"""
View kết hợp list và create.
GET: Lấy danh sách articles
POST: Tạo article mới
"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
"""
View kết hợp retrieve, update và delete.
GET: Lấy chi tiết article
PUT/PATCH: Cập nhật article
DELETE: Xóa article
"""
queryset = Article.objects.all()
serializer_class = ArticleDetailSerializer
permission_classes = [IsAuthenticated]
3.4 URL Routing - Kết nối Views với Endpoints
3.4.1 Router cho ViewSets
File api/urls.py:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet, CategoryViewSet, TagViewSet
# Tạo router
router = DefaultRouter()
# Đăng ký viewsets
router.register(r'articles', ArticleViewSet, basename='article')
router.register(r'categories', CategoryViewSet, basename='category')
router.register(r'tags', TagViewSet, basename='tag')
# URL patterns
urlpatterns = [
path('', include(router.urls)),
]
3.4.2 Kết nối vào project URLs
File drf_project/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
path('api-auth/', include('rest_framework.urls')), # Browsable API login
]
3.4.3 URL patterns được tạo tự động
Router tự động tạo các URL patterns:
GET /api/articles/ # List articles
POST /api/articles/ # Create article
GET /api/articles/{id}/ # Retrieve article
PUT /api/articles/{id}/ # Update article
PATCH /api/articles/{id}/ # Partial update
DELETE /api/articles/{id}/ # Delete article
GET /api/articles/published/ # Custom action
POST /api/articles/{id}/publish/ # Custom action
POST /api/articles/{id}/increment_view/ # Custom action
4. Authentication & Authorization - Xác thực và Phân quyền
4.1 Các phương thức Authentication trong DRF
4.1.1 Session Authentication
Session Authentication là phương thức mặc định của Django, sử dụng cookies để duy trì session. Phù hợp cho các ứng dụng web truyền thống.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
}
4.1.2 Token Authentication
Token Authentication là ph