1

I'm writing a Django Rest API using Django Rest Framework and in it I have a base custom user User and two kinds of user that inherit from User, Doctor and Pacient.

Each one has a different endpoint, so to create a Doctor we make a POST request to /api/v1/doctor and to create a Paciente, a POST to api/v1/pacient/.

If I try creating a user (using those endpoints) but with an e-mail that is already registered (I have changed Django to use e-mail instead), I get the following.

{
    "email": [
        "user with this email already exists."
    ]
}

What I'm trying to achieve is to change this error message, but not only change, I need to verify if the e-mail if from a Pacient or Doctor regardless of which endpoint I'm using, so I can display different messages for each case.

PacientViewSet (DoctorViewSet is basically the same) :

class PacientViewSet(viewsets.ModelViewSet):
    serializer_class = PacientSerializer
    queryset = Pacient.objects.all()
    permission_classes = (AllowAny, )

Serializer:

class PacientSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        user = Paciente.objects.create_paciente_user(
            nome_completo=self.validated_data['full_name'],
            password=self.validated_data['password'],
            email=self.validated_data['email'],
        )
        return user

    class Meta:
        model = Pacient
        fields = ('id', 'email', 'password', 'full_name', ...)
        extra_kwargs = {
            'password': {
                'write_only': True,
            }
        }

Please let me know if there's something else I need to share.

EDIT:

class CustomUser(AbstractBaseUser, PermissionsMixin):
    full_name = models.CharField(max_length=100, null=True, blank=True)
    email = models.EmailField(_('email address'), unique=True)

    SEX_CHOICES = [
        (ConstantesSexo.MALE, 'Male'),
        (ConstantesSexo.FEMALE, 'Female'),
    ]
    sex = models.CharField(max_length=1, choices=SEX_CHOICES, null=True, blank=True)

    is_staff = models.BooleanField(_('staff status'), default=False)
    is_active = models.BooleanField(_('active'), default=True)

    created = models.DateTimeField(_('date joined'), default=timezone.now)
    modified = models.DateTimeField(auto_now=True)

    username_validator = UnicodeUsernameValidator()
    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        blank=True,
        null=True,
        validators=[username_validator],
        error_messages={
            'unique': _("Username already exists."),
        },
    )
    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    USER_CHOICES = [
        (UserType.DOCTOR, 'Doctor'),
        (UserType.PACIENT, 'Pacient'),
    ]
    user_type = models.CharField(max_length=3, choices=USER_CHOICES)


class Pacient(CustomUser):
    nome_social = models.CharField(blank=True, null=True, max_length=50)


class Doctor(CustomUser):
    doctor_id = models.CharField(max_length=50)
    bio = models.TextField(blank=True, null=True)
    verified = models.DateTimeField(null=True, blank=True)

Luc
  • 393
  • 7
  • 19
  • I think what you are looking for is a custom validation logic. You can implement a custom validation by writing a custom clean method: https://docs.djangoproject.com/en/3.0/ref/models/instances/#validating-objects – hendrikschneider Jun 26 '20 at 19:33
  • Yes, I'm aware, the problem is I'm not sure how to achieve this when I'm using DRF. I read that I was suppose to implement a method validate_email on my serializer, but it doesn't work – Luc Jun 26 '20 at 20:56
  • Did you set the email address to unique=True? If yes your custom validation won't be executed because you are violating a model rule. Would you mind to share your model – hendrikschneider Jun 26 '20 at 21:56
  • Yes, I did set email to unique=True.. I edited the question and added my models. – Luc Jun 27 '20 at 01:10

3 Answers3

1

as I see there are two ways to achieve this

First one would be raising an error in PacientSerializer create method

class PacientSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        if Paciente.objects.filter(email=validated_data["email"]).exists():
            raise serializers.ValidationError("Your Custom Error Message")

        user = Paciente.objects.create_paciente_user(
            nome_completo=validated_data["full_name"],
            password=validated_data["password"],
            email=validated_data["email"],
        )
        return user

the other way is to override

def finalize_response(self, request, response, *args, **kwargs):
    # change response.data dictionary accordingly
    ...
    return response 
Vishal Singh
  • 6,014
  • 2
  • 17
  • 33
1

In the end I solved it by overriding the create method in the ModelViewSet (not in the serializer):

class DoctorViewSet(viewsets.ModelViewSet):

    def create(self, request, *args, **kwargs):
        email = request.data.get('email')
        user = CustomUser.objects.filter(email=email).first()
        if email and user:
            if user.is_doctor():
                error_msg = 'doctor error msg'
            elif user.is_pacient():
                error_msg = 'pacient error msg'
            return Response({'error': error_msg}, status=status.HTTP_400_BAD_REQUEST)

        return super().create(request, *args, **kwargs)

Luc
  • 393
  • 7
  • 19
1

i know this is an old question and, i have been trying to find an answer to change unique error message for email address but the answers were too much work for such a small change so i debugged the flow of validation in the framework and came up with this solution. and i am leaving it here for someone looking for solution

i am using dj_rest_auth so i have a custom registration serialiser and it extends from dj_rest_auth.registration.serializers.RegisterSerializer so i only had to override the following method in my custom serialiser and it works as intended which is overriding error message for unique email

def validate_email(self, email):
    try:
        email = super(CustomRegisterSerializer, self).validate_email(email);
    except Exception as e:
            raise serializers.ValidationError(
                _("Please try a different email."))
    return email

dj_rest_auth.registration.serializers.RegisterSerializer has this method that we are overriding to only change the error message we can add our own code in there too but i only needed to change error.

another way i found is to add a class level field to this custom serialiser that i have which is

    email = serializers.EmailField(validators=[UniqueValidator(queryset=User.objects.all(),message="Please try a different email.")])

but this run an extra query User.objects.all() so imagine having a lot of records then this might slow the response down

sheikh hamza
  • 101
  • 3