drf 如何避免 UniqueValidator 中的 objects.all()

Max*_*her 1 django django-models python-3.x django-rest-framework

我有一个代表用户的序列化程序类。

class UserSerializer(BaseSerializer):
    uid = serializers.IntegerField(required=True)

    class Meta:
        model = User
        fields = "all"

    def validate(self, data):
        super().validate(data)
        validate_user_data(data=self.initial_data, user=self.instance)
        return data
Run Code Online (Sandbox Code Playgroud)

用户在 uid 上应该是唯一的,所以当收到 post 请求时,我真正想要的是将 uid 字段更改为:

    uid = serializers.IntegerField(required=True, validators=[validators.UniqueValidator(queryset=User.objects.all())])
Run Code Online (Sandbox Code Playgroud)

这可能会起作用,问题是,这将触发一个 sql 查询,该查询将选择所有用户。这可能会对系统产生非常大的影响,因为它们可能有数以万计。我真正想要的是将查询更改为User.objects.get(uid=uid),这不会从数据库中选择每个用户。但是,因为我在 uid 的序列化程序定义中,所以我不能使用 uid=uid 因为 uid 刚刚被定义。

Wil*_*sem 5

(...) 这可能会起作用,问题是,这将触发一个 sql 查询,将选择所有用户。

这是不正确的。Django 将过滤查询集,但过滤本身发生在数据库端。

不会查询User表中的所有项目。该查询集是进行评估。它充当“根查询集”,将针对该查询构建查询。

我们可以在 GitHub 上查找源代码

class UniqueValidator(object):

    # ...

    def __call__(self, value):
        queryset = self.queryset
        queryset = self.filter_queryset(value, queryset)
        queryset = self.exclude_current_instance(queryset)
        if qs_exists(queryset):
            raise ValidationError(self.message, code='unique')
Run Code Online (Sandbox Code Playgroud)

这里查询集因此被过滤。这种过滤不是在 Python/Django 级别完成的,但它构造了一个过滤变体。事实上,如果我们查看filter_queryset函数,我们会看到:

    def filter_queryset(self, value, queryset):
        """
        Filter the queryset to all instances matching the given attribute.
        """
        filter_kwargs = {'%s__%s' % (self.field_name, self.lookup): value}
        return qs_filter(queryset, **filter_kwargs)
Run Code Online (Sandbox Code Playgroud)

qs_filter

def qs_filter(queryset, **kwargs):
    try:
        return queryset.filter(**kwargs)
    except (TypeError, ValueError, DataError):
        return queryset.none()
Run Code Online (Sandbox Code Playgroud)

如您所见,它将因此生成一个查询 User.objects.filter(uid=the_uid).exclude(pk=item_that_is_updated)

因此,它将检查User该数据库中是否存在与uid您设置的对象相同的对象,并排除您正在更新的对象(如果适用)。查询将如下所示:

SELECT user.*
FROM user
WHERE uid = the_uid
  AND id <> item_that_is_updated
Run Code Online (Sandbox Code Playgroud)

因此,它将在数据库级别进行过滤,从而提高效率。