Trong chương trước, chúng ta đã xây dựng hệ thống đăng ký người học và cơ chế học khóa học, đồng thời tìm hiểu cách sử dụng bộ nhớ đệm trong Django. Trong chương này, chúng ta sẽ phát triển một API theo chuẩn REST để cho phép các ứng dụng khác tương tác với nền tảng của mình. Nội dung chính bao gồm:
- Xây dựng API tuân thủ nguyên tắc REST
- Thiết lập xác thực và phân quyền truy cập API
- Sử dụng ViewSet và Router để định tuyến hiệu quả
1. Thiết lập Django REST Framework
Để cung cấp dữ liệu cho các ứng dụng bên ngoài — ví dụ như frontend hiện đại (React, Vue.js) hoặc ứng dụng di động — việc xây dựng một API là cần thiết. Một lựa chọn phổ biến và mạnh mẽ là sử dụng Django REST Framework (DRF).
Cài đặt DRF qua pip:
pip install djangorestframework==3.8.2
Sau đó, thêm ứng dụng vào danh sách INSTALLED_APPS trong settings.py:
INSTALLED_APPS = [
# ...
'rest_framework',
]
Thêm cấu hình phân quyền mặc định:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
Lớp phân quyền này cho phép người dùng đã xác thực thực hiện đầy đủ thao tác CRUD, trong khi người dùng ẩn danh chỉ được phép đọc.
2. Tạo Serializer
Serializer giúp chuyển đổi dữ liệu từ model sang định dạng JSON và ngược lại. Trong thư mục courses/api/, tạo file serializers.py:
from rest_framework import serializers
from courses.models import Subject
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['id', 'title', 'slug']
Thử nghiệm trong shell:
python manage.py shell
>>> from courses.models import Subject
>>> from courses.api.serializers import SubjectSerializer
>>> subject = Subject.objects.last()
>>> serializer = SubjectSerializer(subject)
>>> serializer.data
{'id': 4, 'title': 'Lập trình', 'slug': 'lap-trinh'}
3. Xử lý phân tích và hiển thị dữ liệu
DRF sử dụng Parser để phân tích dữ liệu đầu vào (ví dụ: JSON thành Python dict), và Renderer để chuyển dữ liệu đầu ra thành định dạng phù hợp (như JSON).
Ví dụ minh họa:
from rest_framework.parsers import JSONParser
from rest_framework.renderers import JSONRenderer
from io import BytesIO
data = b'{"id":4,"title":"Lập trình","slug":"lap-trinh"}'
stream = BytesIO(data)
parsed_data = JSONParser().parse(stream) # Chuyển thành dict
json_bytes = JSONRenderer().render(serializer.data) # Chuyển thành JSON
Mặc định, DRF hỗ trợ JSONRenderer và BrowsableAPIRenderer — giao diện web giúp kiểm thử API dễ dàng hơn.
4. Tạo API View cơ bản
Sử dụng các generic view để nhanh chóng xây dựng chức năng liệt kê và chi tiết:
from rest_framework import generics
from courses.models import Subject
from .serializers import SubjectSerializer
class SubjectListView(generics.ListAPIView):
queryset = Subject.objects.all()
serializer_class = SubjectSerializer
class SubjectDetailView(generics.RetrieveAPIView):
queryset = Subject.objects.all()
serializer_class = SubjectSerializer
Định tuyến URL trong courses/api/urls.py:
from django.urls import path
from . import views
app_name = 'api'
urlpatterns = [
path('subjects/', views.SubjectListView.as_view(), name='subject_list'),
path('subjects/<pk>/', views.SubjectDetailView.as_view(), name='subject_detail'),
]
Kết nối vào URL gốc:
path('api/', include('courses.api.urls', namespace='api'))
Truy cập /api/subjects/ sẽ trả về danh sách chủ đề dưới dạng JSON.
5. Serializer lồng ghép
Chúng ta muốn hiển thị toàn bộ cấu trúc khóa học, bao gồm module và nội dung. Bắt đầu bằng việc tạo serializer cho Module:
class ModuleSerializer(serializers.ModelSerializer):
class Meta:
model = Module
fields = ['order', 'title', 'description']
class CourseSerializer(serializers.ModelSerializer):
modules = ModuleSerializer(many=True, read_only=True)
class Meta:
model = Course
fields = ['id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules']
Khi truy vấn khóa học, tất cả module sẽ được nhúng trực tiếp trong kết quả.
6. Tạo View tùy chỉnh
Để người học đăng ký khóa học qua API, sử dụng APIView:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
class CourseEnrollView(APIView):
def post(self, request, pk):
course = get_object_or_404(Course, pk=pk)
course.students.add(request.user)
return Response({'enrolled': True})
Thêm URL:
path('courses/<pk>/enroll/', views.CourseEnrollView.as_view(), name='course_enroll')
7. Xác thực và phân quyền
Chỉ người dùng hợp lệ mới được đăng ký. Cập nhật view:
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
class CourseEnrollView(APIView):
authentication_classes = [BasicAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, pk):
course = get_object_or_404(Course, pk=pk)
course.students.add(request.user)
return Response({'enrolled': True})
Kiểm thử bằng curl:
curl -X POST http://127.0.0.1:8000/api/courses/1/enroll/ -u username:password
8. Sử dụng ViewSet và Router
ViewSet giúp giảm lặp code bằng cách nhóm các hành động liên quan. Thay vì nhiều view riêng lẻ, ta dùng một ViewSet:
from rest_framework import viewsets
class CourseViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Course.objects.all()
serializer_class = CourseSerializer
Sử dụng Router để tự động sinh URL:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'courses', CourseViewSet)
urlpatterns = [
path('', include(router.urls)),
]
API sẽ có sẵn tại /api/courses/.
9. Thêm hành động tùy chỉnh vào ViewSet
Chuyển chức năng đăng ký vào ViewSet:
from rest_framework.decorators import action
class CourseViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Course.objects.all()
serializer_class = CourseSerializer
@action(detail=True, methods=['post'],
authentication_classes=[BasicAuthentication],
permission_classes=[IsAuthenticated])
def enroll(self, request, pk=None):
course = self.get_object()
course.students.add(request.user)
return Response({'enrolled': True})
Hành động này sẽ khả dụng tại /api/courses/1/enroll/.
10. Tạo lớp phân quyền tùy chỉnh
Chỉ những người đã đăng ký mới được xem nội dung khóa học. Tạo file permissions.py:
from rest_framework.permissions import BasePermission
class IsStudentEnrolled(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.students.filter(pk=request.user.pk).exists()
11. Serialize nội dung động
Do mô hình Content sử dụng GenericForeignKey, ta cần một trường serializer tùy chỉnh:
class ContentItemField(serializers.RelatedField):
def to_representation(self, value):
return value.render()
class ContentSerializer(serializers.ModelSerializer):
item = ContentItemField(read_only=True)
class Meta:
model = Content
fields = ['order', 'item']
Xây dựng serializer lồng ghép đầy đủ:
class ModuleContentSerializer(serializers.ModelSerializer):
contents = ContentSerializer(many=True)
class Meta:
model = Module
fields = ['order', 'title', 'description', 'contents']
class CourseContentSerializer(serializers.ModelSerializer):
modules = ModuleContentSerializer(many=True)
class Meta:
model = Course
fields = ['id', 'title', 'modules']
Thêm hành động contents vào ViewSet:
@action(detail=True, methods=['get'],
serializer_class=CourseContentSerializer,
authentication_classes=[BasicAuthentication],
permission_classes=[IsAuthenticated, IsStudentEnrolled])
def contents(self, request, pk=None):
return self.retrieve(request, pk=pk)
Giờ đây, khi truy cập /api/courses/1/contents/, người dùng đã đăng ký sẽ nhận được toàn bộ nội dung khóa học dưới dạng JSON, bao gồm văn bản, video và các tài nguyên khác.