fbpx Django REST Framework từ cơ bản đến Nâng Cao Skip to main content
Django

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

About

Công ty thiết kế web app chuyên thiết kế web và các dịch vụ maketing digital, seo, google adword...