【问题标题】:Django Rest Framework - to_internal_value Error InconsistencyDjango Rest Framework - to_internal_value 错误不一致
【发布时间】:2020-07-22 14:53:48
【问题描述】:

我有一个模型,用户,其中主要的唯一字段是电子邮件。我有一个单独的组织模型,它允许用户通过多对多映射映射到组织。

如果用户有现有的电子邮件但没有组织关联,我希望序列化程序允许创建用户(组织来自发出请求的用户,因此不在有效负载中)。

标准的 ModelSerializer 包括对 to_internal_value() 中的字段的检查。因此,我试图像这样覆盖它:

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        """
        fields = self._writable_fields
        for field in fields:
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                if validate_method == self.validate_email:
                    if User.objects.filter(email=primitive_value).exists():
                        if not self.active_organization.members.filter(email=primitive_value).exists():
                            continue
                else:
                    validated_value = field.run_validation(primitive_value)
                    if validate_method is not None:
                        validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail
            except DjangoValidationError as exc:
                errors[field.field_name] = get_error_detail(exc)
            except SkipField:
                pass
            else:
                set_value(ret, field.source_attrs, validated_value)
        return super().to_internal_value(data)

这可行,但是如果对象在用户和组织中已有记录,则返回的错误不能正确映射为字典。例如,验证错误显示如下:

[ErrorDetail(string='User with this Email already exists.', code='unique')]

而不是它应该显示的内容:

{'email': [ErrorDetail(string='User with this Email already exists.', code='unique')]}

我通过覆盖该方法并尝试了我的自定义调用代码与原始代码进行了测试,它复制了上述发现:

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        """
        try:
            print('trying custom')
            for field in self._writable_fields:
                if field.field_name == 'email':
                    print(field.run_validation)
                    validated_value = field.run_validation(field.get_value(data))
        except Exception as e:
            print('error custom')
            print(str(e))

        try:
            print('Trying original')
            value = super().to_internal_value(data)
        except Exception as e:
            print('Exception - original')
            print(str(e))

输出:

trying custom
<bound method CharField.run_validation of EmailField(max_length=254, validators=[<UniqueValidator(queryset=User.objects.all())>])>
error custom
[ErrorDetail(string='User with this Email already exists.', code='unique')]

Trying original
Exception - original
{'email': [ErrorDetail(string='User with this Email already exists.', code='unique')]}

谁能帮我理解为什么会这样?我真的不知道这是怎么发生的。

【问题讨论】:

  • 你想如何使用已经存在的电子邮件创建用户?如果您的用户模型上有电子邮件 unique=True,那是不可能的。
  • 这就是我尝试这个的原因。基本上我检查这个 to_internal_value() 调用中是否已经存在用户。如果他们确实 && 他们存在于组织中,我想返回正常的失败消息。如果它们仅作为电子邮件而不是组织存在,则数据将被馈送到 create() 方法,在该方法中我不创建电子邮件而是将它们添加到组织中:Organization.members.add(User.objects.get(email=电子邮件))。奇怪的是,当我覆盖 to_internal_value 方法时,错误不会作为以“电子邮件”为键的字典返回。
  • 下半天我会帮你的。基本上尝试在序列化程序中使用 validate_email 方法(您可以在其中验证电子邮件,不要覆盖 to_internal_value 方法!),并尝试使用 self.context['request'].user (in serializer) .. 获得活跃的组织。

标签: python-3.x django django-rest-framework


【解决方案1】:

当我从底层 ModelSerializer 复制代码与调用 super() 时,我无法弄清楚为什么 to_internal_value(self, data) 生成的错误会发生变化。不过,我确实找到了一种骇人听闻的解决方法。

因为我事先删除了所有其他相关信息(嵌套序列化程序等),所以我真的只是担心电子邮件。因此,我遍历 _writable_fields 两次:第一次查找不在组织中的现有电子邮件。如果这些条件为真,我会返回一个字典并继续执行 create() 方法。如果他们失败了,我会打电话给超级。到目前为止,这似乎有效。这是我的简化课程。

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        """
        try:
            for field in self._writable_fields:
                if field.field_name == 'email':
                    primitive_value = field.get_value(data)
                    validator = EmailValidator()
                    validator(primitive_value)
                    if User.objects.filter(email=primitive_value).exists():
                        if not self.active_organization.members.filter(email=primitive_value).exists():
                            self.email_already_exists = True
                            return {'email': primitive_value}
        except:
            pass

        return super().to_internal_value(data)

作为参考,这是我正在使用的完整 ViewSet 和 Serializer:

class AdminUsersSerializer(serializers.ModelSerializer):
    """User serializer for the admin user view"""
    groups = GenericGroupSerializer(source='group_set', many=True, required=False)
    permission_sets = GenericPermissionSetSerializer(source='permissionset_set', many=True, required=False)
    active_organization = None
    email_already_exists = False

    class Meta:
        model = User
        fields = ['pk', 'email', 'groups', 'permission_sets']  # , 'permissions']

    def set_active_organization(self, organization):
        self.active_organization = organization

    def create(self, validated_data):
        if not self.email_already_exists:
            user = User.objects.create(
                email=validated_data['email']
            )
            user.set_password(None)
            user.save()
        else:
            user = User.objects.get(email=validated_data['email'])
        return user

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        """
        try:
            for field in self._writable_fields:
                if field.field_name == 'email':
                    primitive_value = field.get_value(data)
                    validator = EmailValidator()
                    validator(primitive_value)
                    if User.objects.filter(email=primitive_value).exists():
                        if not self.active_organization.members.filter(email=primitive_value).exists():
                            self.email_already_exists = True
                            return {'email': primitive_value}
        except:
            pass

        return super().to_internal_value(data)

class AdminUsersViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = AdminUsersSerializer
    permission_classes = [account_permissions.IsAdminRequired]
    http_method_names = ['get', 'post']
    active_organization = None

    def create(self, request, *args, **kwargs):
        self.active_organization = self.request.user.activeorganization.organization
        groups = None
        permission_sets = None
        serializer = self.get_serializer(data=request.data)
        serializer.set_active_organization(self.active_organization)
        if 'groups' in serializer.initial_data:
            if serializer.initial_data.get('groups'):
                groups = serializer.initial_data.pop('groups')
            else:
                serializer.initial_data.pop('groups')
        if 'permission_sets' in serializer.initial_data:
            if serializer.initial_data.get('permission_sets'):
                permission_sets = serializer.initial_data.pop('permission_sets')
            else:
                serializer.initial_data.pop('permission_sets')
        # Normal method functions
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        # Created user
        user = User.objects.get(email=serializer.data['email'])
        # Add user to organization
        self.active_organization.members.add(user)
        # Make sure to format the ajax groups data properly
        if groups:
            try:
                group = Group.objects.get(
                    id=groups,
                    organization=self.active_organization
                )
                group.members.add(user)
            except:
                pass
        if permission_sets:
            try:
                permission_set = PermissionSet.objects.get(
                    id=permission_sets,
                    organization=self.active_organization
                )
                permission_set.members.add(user)
            except:
                pass
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def get_queryset(self):
        return self.queryset.filter(
            userorganizationmembership__organization=self.request.user.activeorganization.organization
        ).all()

确实感觉很老套,但它似乎完成了工作。

【讨论】:

    猜你喜欢
    • 2016-03-15
    • 2016-09-09
    • 2021-10-03
    • 1970-01-01
    • 2019-11-22
    • 2015-02-13
    • 2016-02-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多