Django: error trying to validate an email verification otp

I'm sending an email to the user on register and getting the otp together with the email, I have a separate verify model that looks like below the error I'm facing is {"message": "This OTP is invalid"} which has something to do with peace of code in the verifyEmail view(down below)

class Verify(models.Model):
email = models.EmailField(
    _("Email"), max_length=254, unique=True, default=None, blank=True, null=True)
otp = IntegerRangeField(
    min_value=111111, max_value=999999, blank=True, null=True)
created_at = models.DateTimeField(
    _("created at"), auto_now=False, auto_now_add=True, blank=False)
expires_at = models.DateTimeField(null=True)

def __str__(self) -> str:
    return self.email

how I'm sending the email

def send_opt_email(email, firstname, lastname):
    otp = random.randint(100000, 999999)
    subject = 'Email verification'
    message = 'Email verification'
    html_message = loader.render_to_string(
        'email_verify.html',
        {
            'firstname': firstname,
            'lastname': lastname,
            'otp': otp,
        }
    )
   email_from = settings.EMAIL_HOST_USER
   send_mail(subject, message, email_from, [
          email], fail_silently=True, html_message=html_message)
   otp = Verify.objects.create(
   email=email,
   otp=otp,
   created_at=datetime.now(),
   expires_at=datetime.now() + timedelta(minutes=30)
     )
    otp.save()

this is the view I use to validate the otp

class Verify_Email(APIView):
"""
Verify registered emails
"""
def post(self, request):
    try:
        data = request.data
        serializer = VerifySerializerBase(data=data)
        if serializer.is_valid():
            email = serializer.data['email']
            otp = serializer.data['otp']
            verify = Verify.objects.get(email=email)
            user = User.objects.get(email=email)

            try:
                if user.is_active:
                    return Response({
                        'message': "This email has already been verified"
                    }, status=status.HTTP_400_BAD_REQUEST)

                elif verify:
                    print(verify.email)
                    print(verify.otp)
                    print(verify.created_at)
                    print(verify.expires_at)
                    #prints everything as expected
                    now = timezone.now()
                    if verify.otp != otp:
                        return Response({
                            'message': "This OTP is invalid"
                        }, status=status.HTTP_400_BAD_REQUEST)
                    elif verify.expires_at < now:
                        return Response({
                            'message': "This OTP has expired, please request another one"
                        }, status=status.HTTP_400_BAD_REQUEST)
                    verify.delete()
                    user.is_active = True
                    user.save()
                    return Response({
                        'message': "Email has been verified"
                    }, status=status.HTTP_200_OK)
            except User.DoesNotExist:
                return Response({"message": "User was not found"}, status=status.HTTP_404_NOT_FOUND)

            return Response({
                'message': "Something is wrong"
            }, status=status.HTTP_400_BAD_REQUEST)

        return Response({
            'message': "Something is wrong"
        }, status=status.HTTP_400_BAD_REQUEST)

    except Exception as e:
        return Response(str(e), status=status.HTTP_404_NOT_FOUND, template_name=None, content_type=None)

I don't think the Verify_Email view is a nice implementation and any improvements would be appreciated

Did you try type casting the variable? (i. e. if int(verify.otp) != int(otp):)

Also, are you 100% sure verify.otp == otp? I see you're printing verify.otp but not otp. What's both results?

You got what you were looking for in Sören Rifé's answer and I just cleaned up your code. (however you might not like it)

from django.shortcuts import get_object_or_404
from rest_framework.exceptions import ValidationError

class Verify_Email(APIView):
    def post(self, request):
        serializer = VerifySerializerBase(data=request.data)
        if not serializer.is_valid():
            raise ValidationError(
                "Something is wrong"
            )
        
        email = serializer.data['email']
        otp = serializer.data['otp']

        user = get_object_or_404(User, email=email)
        if user.is_active:
            raise ValidationError(
                "This email has already been verified",
            )

        verify = get_object_or_404(Verify, email=email)
        if int(verify.otp) != int(otp):
            raise ValidationError(
                "This OTP is invalid"
            )
        if verify.expired:
            raise ValidationError(
                "This OTP has expired, please request another one"
            )

        verify.delete()
        user.is_active = True
        user.save()
        return Response(
            {'message': "Email has been verified"},
            status=status.HTTP_200_OK
        )
from datetime import datetime

class Verify(models.Model):
    email = models.EmailField(
        _("Email"), max_length=254, unique=True, default=None, blank=True, null=True)
    otp = IntegerRangeField(
        min_value=111111, max_value=999999, blank=True, null=True)
    created_at = models.DateTimeField(
        _("created at"), auto_now_add=True)
    expires_at = models.DateTimeField(null=True)

    def __str__(self) -> str:
        return self.email

    @property
    def expired(self):
        return self.expires_at < datetime.now()
Back to Top