处理唯一在一起错误的最佳方法-Django 2.2?

时间:2019-04-05 05:14:58

标签: django django-models django-rest-framework python-3.6 django-2.2

我知道这个问题已经被问过很多次了,但是我仍然找不到正确的解决方案。,比如说我有像Follow这样的模型

class Student(models.Model):
    number = models.IntegerField()
    department = models.ForeignKey(Department, on_delete=models.CASCADE)
    class Meta:
       constraints = [
                     models.UniqueConstraint(fields=['department', 'number'])
                     ]

和我的序列化器如下所示。

class StudentModelSerializer(serializers.ModelSerializer):
     class Meta:
          model = Student
          fields = ("number",)

在此模型department中,numberunique together,现在从URL中传递的pk中提取部门。我处理唯一错误的方式如下。

class StudentViewSet(ModelViewSet):
     queryset = Student.objects.all()
     serializer_class = StudentModelSerializer

     def perform_create(self, serializer):
          department = Department.objects.get(pk=self.kwargs['pk'])
          serializer.save(department=department)

     def create(self, request, *args, **kwargs):
         try:
             return super().create(request, *args, **kwargs)
         except IntegrityError as err:
             if 'UNIQUE constraint' in err.message:
                raise ValidationError({
                    'number': 'Number field should be unique.'
                })
             else:
                raise IntegrityError(err)

如上所述,我调用super().create()捕获异常,然后检查UNIQUE消息是否存在,如果存在,则我再次引发验证错误,因此rest framework's exception handler处理该错误。如果没有,我会再次引发错误。

此方法的问题是我正在检查消息UNIQUE的唯一错误,该错误将来可能会更改。当然,我可以在保存之前将部门添加到serializer contextvalidate,但这可能会导致race condition,那么best practice可以用来处理这种情况吗?

2 个答案:

答案 0 :(得分:0)

您可以在 StudentModelSerializer 类中使用UniqueTogetherValidator :)

示例

from rest_framework.validators import UniqueTogetherValidator


class StudentModelSerializer(serializers.ModelSerializer):
    # ...
    class Meta:
        # ToDo items belong to a parent list, and have an ordering defined
        # by the 'position' field. No two items in a given list may share
        # the same position.
        validators = [
            UniqueTogetherValidator(
                queryset=Student.objects.all(),
                fields=('department', 'number')
            )
        ]

UPDATE-1

覆盖 create() 的查看方法,

# views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework import status


class StudentViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

    def create(self, request, *args, **kwargs):
        request_data = request.data
        request_data.update({"department": kwargs['pk']})
        serializer = self.get_serializer(data=request_data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)


# serializers.py
from rest_framework.validators import UniqueTogetherValidator


class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = ("number", "department")
        validators = [
            UniqueTogetherValidator(
                queryset=Student.objects.all(),
                fields=('department', 'number')
            )
        ]

答案 1 :(得分:0)

更好的方法是将异常的pgcode与psycopg2错误代码进行比较:

from psycopg2 import errorcodes

class StudentViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

    def perform_create(self, serializer):
        department = Department.objects.get(pk=self.kwargs['pk'])
        serializer.save(department=department)

    def create(self, request, *args, **kwargs):
        try:
            return super().create(request, *args, **kwargs)
        except IntegrityError as err:
            if err.__cause__.pgcode == errorcodes.UNIQUE_VIOLATION and \
               "number" in err.args[0]
                raise ValidationError({
                    'number': 'Number field should be unique.'
                })
            raise