You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Django REST Framework创建Bookmark模型实例时遇到问题

使用Django REST Framework实现通用书签系统

我最近做了一个支持给任意内容类型(比如Book、Article这类模型)添加书签的功能,用Django的通用关系配合DRF实现,分享下我的代码和思路:

模型定义(models.py)

首先是核心的Bookmark模型,通过GenericForeignKey实现对任意模型的关联:

from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey

class Bookmark(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:
        # 限制同一个用户不能重复书签同一个内容
        unique_together = ('user', 'content_type', 'object_id')

API层实现(api.py)

接下来是DRF的序列化器和视图集,处理书签的增删改查逻辑:

from django.contrib.contenttypes.models import ContentType
from rest_framework import viewsets, mixins, serializers, permissions
from .models import Bookmark

class BookmarkSerializer(serializers.ModelSerializer):
    class Meta:
        model = Bookmark
        fields = ('id', 'user', 'content_type', 'object_id')
        # 用户字段设为只读,避免前端手动指定
        read_only_fields = ('user',)

class BookmarkViewSet(mixins.CreateModelMixin, 
                      mixins.ListModelMixin, 
                      mixins.RetrieveModelMixin, 
                      mixins.DestroyModelMixin, 
                      viewsets.GenericViewSet):
    queryset = Bookmark.objects.all()
    serializer_class = BookmarkSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        # 从查询参数获取内容类型(格式如`books.book`)和对象ID
        content_type_param = self.request.query_params.get('content_type')
        object_id = self.request.query_params.get('object_id')
        
        if not content_type_param or not object_id:
            raise serializers.ValidationError("必须提供content_type和object_id参数")
        
        try:
            app, model = content_type_param.split(".")
            ct = ContentType.objects.get_by_natural_key(app, model)
            # 可选:验证目标内容确实存在
            ct.get_object_for_this_type(id=object_id)
        except (ValueError, ContentType.DoesNotExist, ct.model_class().DoesNotExist):
            raise serializers.ValidationError("无效的content_type或object_id")
        
        serializer.save(user=self.request.user, content_type=ct, object_id=object_id)

    def get_queryset(self):
        # 默认只返回当前用户的书签
        queryset = Bookmark.objects.filter(user=self.request.user)
        content_type = self.request.query_params.get('content_type', None)
        
        # 支持按内容类型筛选,比如只查看书签过的书籍
        if content_type:
            try:
                app, model = content_type.split(".")
                ct = ContentType.objects.get_by_natural_key(app, model)
                queryset = queryset.filter(content_type=ct)
            except (ValueError, ContentType.DoesNotExist):
                # 参数无效时忽略筛选条件,返回用户所有书签
                pass
        return queryset

实用优化建议

  • 展示关联内容详情:如果需要在书签列表里显示对应的内容(比如Book的标题、作者),可以给序列化器添加SerializerMethodField,根据不同模型类型调用对应的序列化器来序列化content_object
  • 增强权限控制:在perform_create中可以额外添加逻辑,验证用户有权限访问要书签的内容
  • 路由配置:记得在项目的urls.py中注册视图集,比如用DefaultRouter:
    from rest_framework.routers import DefaultRouter
    from .api import BookmarkViewSet
    
    router = DefaultRouter()
    router.register(r'bookmarks', BookmarkViewSet)
    urlpatterns = router.urls
    

内容的提问来源于stack exchange,提问作者apiljic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:57:16