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?
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