【问题标题】:Best way to handle unique together error - Django 2.2?处理唯一一起错误的最佳方法 - Django 2.2?
【发布时间】:2019-08-26 22:59:40
【问题描述】:

我知道这个问题已经被问过很多次了,但我仍然无法找到正确的解决方案。假设我有类似跟随的模型

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",)

在这个模型中departmentnumberunique together,现在部门是从pk 获取的,传入url。我处理独特错误的方式如下。

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,但这可能会导致 @ 987654334@,那么best practice是什么来处理这种情况的呢?

【问题讨论】:

  • 你能显示 url 配置吗?
  • 结束网址应该是这样的[POST] http://localhost/departments/{pk}/students
  • 在下面查看我的答案:)

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


【解决方案1】:

您可以在 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')
            )
        ]

更新-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')
            )
        ]

【讨论】:

  • 我更新了问题department is not a serializer field,因为我在perform_create(department=department)中添加了它,UniqueTogetherValidator不会导致race conditions
  • 检查我的 Update-1 部分
  • run_validatorscreate 之间有race condition 正确的机会,这再次导致Integrity Error 在视图集中的create 方法上正确。这就是在create 中使用try/catch方法。
  • 这将如何发生?创建过程中会出现完整性错误,验证过程将不允许您进入创建过程。
  • 如果两个用户 simultaneously 试图创建 record。用户一 run_validators 可能会通过,但在到达 create 之前,用户二创建了记录权。
【解决方案2】:

更好的方法是将异常的 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-21
    • 2017-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-31
    相关资源
    最近更新 更多