DRF 博客API实战 #
一、项目概述 #
1.1 功能需求 #
- 用户注册、登录、个人资料
- 文章CRUD、分类、标签
- 评论系统
- 点赞功能
- 搜索和过滤
1.2 项目结构 #
text
blog_api/
├── manage.py
├── blog_api/
│ ├── settings.py
│ └── urls.py
├── apps/
│ ├── users/
│ ├── articles/
│ └── comments/
└── core/
├── permissions.py
└── pagination.py
二、模型设计 #
2.1 用户模型 #
python
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
bio = models.TextField(max_length=500, blank=True)
website = models.URLField(blank=True)
class Meta:
verbose_name = '用户'
verbose_name_plural = '用户'
def __str__(self):
return self.username
2.2 文章模型 #
python
class Category(models.Model):
name = models.CharField('名称', max_length=50)
slug = models.SlugField(unique=True)
description = models.TextField('描述', blank=True)
class Meta:
verbose_name = '分类'
verbose_name_plural = '分类'
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField('名称', max_length=50)
slug = models.SlugField(unique=True)
class Meta:
verbose_name = '标签'
verbose_name_plural = '标签'
def __str__(self):
return self.name
class Article(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
]
title = models.CharField('标题', max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField('内容')
summary = models.CharField('摘要', max_length=300, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='articles')
tags = models.ManyToManyField(Tag, blank=True, related_name='articles')
status = models.CharField('状态', max_length=10, choices=STATUS_CHOICES, default='draft')
views = models.PositiveIntegerField('浏览量', default=0)
likes = models.ManyToManyField(User, blank=True, related_name='liked_articles')
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
published_at = models.DateTimeField('发布时间', null=True, blank=True)
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-created_at']
def __str__(self):
return self.title
2.3 评论模型 #
python
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments')
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='replies')
content = models.TextField('内容')
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '评论'
verbose_name_plural = '评论'
ordering = ['created_at']
def __str__(self):
return f'{self.author.username}: {self.content[:50]}'
三、序列化器 #
3.1 用户序列化器 #
python
class UserSerializer(serializers.ModelSerializer):
articles_count = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'username', 'email', 'avatar', 'bio', 'website', 'articles_count']
read_only_fields = ['id']
def get_articles_count(self, obj):
return obj.articles.count()
class UserRegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
password_confirm = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['username', 'email', 'password', 'password_confirm']
def validate(self, data):
if data['password'] != data['password_confirm']:
raise serializers.ValidationError('两次密码不一致')
return data
def create(self, validated_data):
validated_data.pop('password_confirm')
user = User.objects.create_user(**validated_data)
return user
3.2 文章序列化器 #
python
class ArticleListSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
category_name = serializers.CharField(source='category.name', read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments_count = serializers.SerializerMethodField()
class Meta:
model = Article
fields = ['id', 'title', 'slug', 'summary', 'author', 'category_name',
'tags', 'views', 'comments_count', 'created_at']
def get_comments_count(self, obj):
return obj.comments.count()
class ArticleDetailSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
is_liked = serializers.SerializerMethodField()
class Meta:
model = Article
fields = '__all__'
def get_is_liked(self, obj):
request = self.context.get('request')
if request and request.user.is_authenticated:
return obj.likes.filter(id=request.user.id).exists()
return False
四、视图 #
4.1 用户视图 #
python
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return UserRegisterSerializer
return UserSerializer
def get_permissions(self):
if self.action == 'create':
return [AllowAny()]
if self.action in ['update', 'partial_update', 'destroy']:
return [IsAuthenticated(), IsOwner()]
return [AllowAny()]
@action(detail=False, methods=['get', 'put', 'patch'])
def me(self, request):
if request.method == 'GET':
serializer = self.get_serializer(request.user)
return Response(serializer.data)
serializer = self.get_serializer(request.user, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
4.2 文章视图 #
python
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.select_related('author', 'category').prefetch_related('tags', 'comments')
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['category', 'status']
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'views']
ordering = ['-created_at']
lookup_field = 'slug'
def get_serializer_class(self):
if self.action == 'list':
return ArticleListSerializer
return ArticleDetailSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def like(self, request, slug=None):
article = self.get_object()
article.likes.add(request.user)
return Response({'status': 'liked'})
@action(detail=True, methods=['post'])
def unlike(self, request, slug=None):
article = self.get_object()
article.likes.remove(request.user)
return Response({'status': 'unliked'})
@action(detail=False)
def my(self, request):
articles = self.get_queryset().filter(author=request.user)
page = self.paginate_queryset(articles)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
五、URL配置 #
python
from rest_framework.routers import DefaultRouter
from .views import UserViewSet, ArticleViewSet, CategoryViewSet, TagViewSet, CommentViewSet
router = DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'articles', ArticleViewSet)
router.register(r'categories', CategoryViewSet)
router.register(r'tags', TagViewSet)
router.register(r'comments', CommentViewSet)
urlpatterns = router.urls
六、总结 #
本章通过博客API实战,综合运用了:
- 模型设计
- 序列化器
- 视图集
- 权限控制
- 过滤搜索
构建了一个完整的RESTful API!
最后更新:2026-03-28