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
相关产品推荐
相关产品推荐

