1
votes

The code:

class OTP(AppModel):
    phone_regex = RegexValidator(regex=r'^[6789]\d{9}$', message="phone no. is invalid.")
    phone_number = models.CharField(validators=[phone_regex], max_length=10, unique=True)
    code = models.CharField(max_length=255)

    def __str__(self):
        return str(self.phone_number) + ": "+str(self.code)

class OTPSerializer(serializers.ModelSerializer):
    code = serializers.CharField(max_length=None, required=False)
    class Meta:
        model = OTP
        fields = ('id', 'code', 'phone_number')
        read_only_fields=('id', 'code')

    @transaction.atomic
    def create(self, validated_data):
        phone_number = validated_data.pop("phone_number")
        otp, created = OTP.objects.update_or_create(
            phone_number=phone_number, defaults={"code": generate_otp()})
        return otp

I am trying to do update_or_create inside the create method of the django-rest-framework's ModelSerializer.

But, the field phone_number inside the model OTP must be unique. Hence the unique=True.

I was able to post a phone_number and create the object. But, posting the same phone_number again throws error otp with this phone number already exists, instead of updating it if it already exists as I have overridden the create method. Please help!

3

3 Answers

0
votes

You could make phone_number NOT required and then manually do the check. You get the error, because DRF validated phone_number before you do. So, basically, the solution could be the following (serialiser code only):

class OTPSerializer(serializers.ModelSerializer):
    code = serializers.CharField(max_length=None, required=False)
    class Meta:
        model = OTP
        fields = ('id', 'code', 'phone_number')
        read_only_fields=('id', 'code')
        extra_kwargs = {'phone_number': {'required': False}}

    @transaction.atomic
    def create(self, validated_data):
        phone_number = validated_data.pop("phone_number")
        otp, created = OTP.objects.update_or_create(
        phone_number=phone_number, defaults={"code": generate_otp()})
        return otp
0
votes

Try setting the validator inside your serializer class instead of inside the model class. So have your serializer class look something like this:

class OTPSerializer(serializers.ModelSerializer):
    code = serializers.CharField(max_length=None, required=False)
    phone_regex = RegexValidator(regex=r'^[6789]\d{9}$', message="phone no. is invalid.")  # add this line
    phone_number = serializers.CharField(validators=[phone_regex])  # and this line
    
    class Meta:
        model = OTP
        fields = ('id', 'code', 'phone_number')
        read_only_fields=('id', 'code')

    @transaction.atomic
    def create(self, validated_data):
        phone_number = validated_data.pop("phone_number")
        otp, created = OTP.objects.update_or_create(
            phone_number=phone_number, defaults={"code": generate_otp()})
        return otp
0
votes

You can use Signals to do this in a clean way. Simply, you can send a created variable to a receiver which you define, and deal with it based on either your object was created or not. in the case of the REST response, just override the create method in the Serializer to return data based on the state you're in, or the get/post/patch method you're using in the APIView to not return serializer.data, and instead return whatever you want it to. Here is an example for a signal receiver:

@receiver(post_save, sender=settings.OTP_MODEL)
def update(sender, instance=None, created=False, **kwargs):
    if created:
        # Do Something
    else:
        # Do Some Other thing