简体   繁体   中英

drf how to avoid objects.all() in UniqueValidator

I have a serializer class that represents a user.

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

users should be unique on uid, so when getting a post request what I really want is to change the uid field to:

    uid = serializers.IntegerField(required=True, validators=[validators.UniqueValidator(queryset=User.objects.all())])

and this will probably work, the problem is, this will trigger an sql query that will select all the users. This could have a very high impact on the system since there could be tens of thousands of them. What I would really want is to change the query to User.objects.get(uid=uid) , which will not select every user from the DB. However, since I'm in the serializer definition of uid, I can't use uid=uid because uid is just being defined.

(…) and this will probably work, the problem is, this will trigger an sql query the will select all the users.

This is incorrect . Django will filter the queryset, but the filtering itself happens at the database side.

This will not query for all the items in the User table. The queryset is not evaluated. It acts as a "root queryset" against which queries will be constructed.

We can look up the source code on GitHub :

 class UniqueValidator(object): # ... def __call__(self, value): queryset = self.queryset  queryset = self.exclude_current_instance(queryset) if qs_exists(queryset): raise ValidationError(self.message, code='unique') 

Here the queryset is thus filtered. This filtering is not done at the Python/Django level, but it constructs a filtered variant. Indeed, if we look at the filter_queryset function, we see:

  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) 

with as qs_filter :

 def qs_filter(queryset, **kwargs): try: return queryset.  except (TypeError, ValueError, DataError): return queryset.none() 

As you can see, it will thus generate a query User.objects.filter(uid= the_uid ).exclude(pk= item_that_is_updated )

It will thus check if there is a User object in that database with the same uid as the one you set, and exclude the one your are updating (given that is applicable). The query this will look like:

SELECT user.*
FROM user
WHERE uid = the_uid
  AND id <> item_that_is_updated

It will thus filter at the database level, and therefore boost efficiency.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM