Validate Unique email django rest framework serializer

Viewed 2250

I have a model where Email field is unique. I thought it would make every email lowercase as it has normalize_email() method on User instance. However, it normalizes domain part only so Company@gmail.com is considered to be unique if company@gmail.com exists. So I decided to create validate_email() in my serializer to always return a lowercase email. It is a field validation and it is described in docs.

    def validate_email(self, value):
        return value.lower()

However, looks like this method returns the value after the serializer has checked if the value exists in the database. Here is an example:

if I try to create a user with user@gmail.com and it already exists, it will return "user already exists" which is expected. However, if I run with User@gmail.com it will run a SQL request first and check User@gmail.com != user@gmail.com and after that it'll try to create a new instance with user@gmail.com because it is returned from validate_email() and IntegrityError will be raised because it becomes user@gmail.com which is already in DB!

I could do something like

    def validate_email(self, value):
        norm_email = value.lower()
        if User.objects.filter(email=norm_email).exists():
            raise serializers.ValidationError("Not unique email")
        return norm_email

but this is another request to DB and I don't want it.

So my question is what method runs a SQL request to check uniqueness in DB? So that I can override it and pass value already lowercased?

1 Answers

Use iexact--(django doc) lookup

User.objects.filter(email__iexact=norm_email).exists()

Update

DRF querying under the hood to check the unique constraint because in model field we have set unique=True. In order to avoid that, we need to define the email field explicitly in the serializer, which bypasses the unique check validation

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField()

    def validate_email(self, value):
        lower_email = value.lower()
        if User.objects.filter(email__iexact=lower_email).exists():
            raise serializers.ValidationError("Duplicate")
        return lower_email

    class Meta:
        model = User
        fields = ('email',)

Django shell output

In [17]:  print(len(connection.queries))
7

In [18]: class UserSerializer(serializers.ModelSerializer):
    ...:     email = serializers.EmailField()
    ...: 
    ...:     def validate_email(self, value):
    ...:         lower_email = value.lower()
    ...:         if User.objects.filter(email__iexact=lower_email).exists():
    ...:             raise serializers.ValidationError("Duplicate")
    ...:         return lower_email
    ...: 
    ...:     class Meta:
    ...:         model = User
    ...:         fields = ('email',)
    ...: 

In [19]: print(len(connection.queries))
7

In [20]: s = UserSerializer(data={'email': 'Foo@gmail.com'})

In [21]: print(len(connection.queries))
7

In [22]: try:
    ...:     s.is_valid(True)
    ...: except serializers.ValidationError:
    ...:     print("raised validation error")
    ...: 
raised validation error

In [23]: print(len(connection.queries))
8
Related