Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-viewsdjango-rest-framework-simplejwt

Update field value through reverse relationship. Django REST


I am using djangorestframework-simplejwt for authentication. My use case requires me to use OTP instead of passwords.

To store OTPs, I have created the following model:

class OneTimePassword(models.Model):
    otp = models.IntegerField()
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

User model:

class User(AbstractUser):

    username = None
    email = models.EmailField(_('email address'), unique=True)
    country_code = models.IntegerField(default=91, max_length=3)
    mobile = models.IntegerField(max_length=11, unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['mobile']

    objects = CustomUserManager()

    def __str__(self):
        return f"{self.email},{self.mobile}"

My plan is:

  1. POST mobile to /api/generate_otp/ endpoint
  2. Inside the view for this endpoint, a new OTP is generated and stored at OneTimePassword model. (the user is determined from mobile number)
  3. At the api/token/obtain/ endpoint, OTP and mobile number is posted.
  4. If OTP matches the value stored in the OTP model, then JWT token is returned.

I am facing difficulty in step 2, i.e. I am not able to update the value of OTP in OneTimePassword Model through the user.

I have tried the following approach:

class GenerateOTPMobileView(APIView):
    permission_classes = ()

    def post(self, request,):
        mobile = request.data.get("mobile")
        user = User.objects.get(mobile=mobile)
        random_otp = randint(10000, 99999)
        if user:
            user.onetimepassword_set.otp = random_otp   # This is not working
            ...
            # send OTP through third party API
            ...
            return Response({"success": "OTP sent to mobile number"},)
        else:
            return Response({"error": "Wrong Credentials"}, status=status.HTTP_400_BAD_REQUEST)


Solution

  • You're trying to set a property on the many side of a relationship and it doesn't work that way. Basically, you're just setting a property on the manager and the manager just like any other well behaved python object, will just set this property on itself - but it doesn't do anything useful.

    Instead, you should either create() a new OTP or update() a specific OTP.

    Remember your data model looks like this:

    user:
       - otp1
       - otp2
       - otp3  
    

    etc.

    So there is not "one OTP for one user". For that you need a OneToOneField.