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

DRF实现同用户同书籍高亮记录存在则更新不存在则创建

DRF实现书籍高亮记录「存在即更新、不存在则新建」方案

你现有代码存在两处基础问题会直接影响功能:

  • Serializer继承类错误:当前继承的serializers.Serializer不会识别Meta下的模型配置,需改为serializers.ModelSerializer
  • 模型层无联合唯一约束:仅靠业务逻辑判断无法在高并发场景下阻止重复数据插入,必须从数据库层面加约束兜底

第一步:修改模型层,添加联合唯一约束

给HighLight模型添加用户+书籍的联合唯一键,确保同一个用户对同一本书仅能存在一条高亮记录,修改后代码如下:

class HighLight(models.Model):
    id = models.AutoField(primary_key=True)
    book = models.ForeignKey(
        BookDetails,
        on_delete=models.CASCADE,
        related_name='book_highlight'
    )
    customer = models.ForeignKey('user_profile.CustomerProfile', on_delete=models.CASCADE,null=True,blank=True)
    
    start = models.IntegerField(blank=True, null=True)
    end = models.IntegerField(blank=True, null=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        # 联合唯一约束:同用户同书籍仅一条记录
        unique_together = ('book', 'customer')

修改完成后依次执行python manage.py makemigrations、python manage.py migrate让约束生效。

第二步:修正序列化器代码

修正继承类,同时将用户、id字段设为只读,避免前端恶意传值篡改:

from rest_framework import serializers
from .models import HighLight

class HighLightSerializer(serializers.ModelSerializer):
    class Meta:
        model = HighLight
        fields = ('id','book','customer','start','end',)
        read_only_fields = ('id', 'customer')

第三步:修改视图逻辑,实现upsert效果

放弃默认的全量新建逻辑,重写create方法,用Django ORM内置的update_or_create方法实现「存在则更新、不存在则新建」,该方法自带并发适配,比手写查询判断更稳定:

from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
from rest_framework.exceptions import ValidationError
from user_profile.models import CustomerProfile
from .models import HighLight
from .serializers import HighLightSerializer

class HighLightedBooks(generics.ListCreateAPIView):
    permission_classes = (IsAuthenticated,)
    serializer_class = HighLightSerializer
    authentication_classes = (TokenAuthentication,)

    def get_queryset(self):
        # 列表接口仅返回当前用户自己的高亮记录
        customer = CustomerProfile.objects.get(user=self.request.user)
        return HighLight.objects.filter(customer=customer)
    
    def create(self, request, *args, **kwargs):
        # 校验用户profile是否存在
        try:
            customer = CustomerProfile.objects.get(user=request.user)
        except CustomerProfile.DoesNotExist:
            raise ValidationError('请先完善个人资料后再提交高亮记录')
        
        book_id = request.data.get('book')
        if not book_id:
            raise ValidationError('必须传入关联书籍ID')

        # 核心逻辑:匹配到同用户同书籍记录就更新start/end,否则新建
        highlight_obj, is_created = HighLight.objects.update_or_create(
            book_id=book_id,
            customer=customer,
            defaults={
                'start': request.data.get('start'),
                'end': request.data.get('end')
            }
        )
        serializer = self.get_serializer(highlight_obj)
        # 新建返回201,更新返回200,符合REST规范
        return Response(
            serializer.data, 
            status=status.HTTP_201_CREATED if is_created else status.HTTP_200_OK
        )

补充:如果你的业务要求同一个用户对同一本书可以有多条不同位置的高亮,只需要把联合唯一约束的字段调整为('book', 'customer', 'start', 'end')即可,其余逻辑不需要改动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 08:18:28