2

I am creating a REST api for user registration, and I have a nested serializer where I store additional information about a user.

The User serializer asks for first_name, last_name, email, and password.

The nested serializer asks for agreed_terms_of_service

email, password, and agreed_terms_of_service are required.

But if a user keys in their email and password and DOES NOT check the agreed_terms_of_service box, it returns and error, but still creates a user with the email and password.

Then when the user goes to 'remedy the situation', the email address is already in use.

If I update instead of create, I feel like I would run into a situation where people are overwriting other users... I am wondering how people handle this with django rest serializers and what is the best practice?

VIEWS.PY

def serialize(self, request):
        if request.method =='POST':
            data = json.loads(request.body)
            #first validation
            if data['password'] != data['password2']:
                raise serializers.ValidationError({'msgType':'error','message':'Passwords do not match.'})
            #move to serializer
            else:                   
            serializer = userSerializer(data = data)
            data['username'] = data['email'] 
            if serializer.is_valid(raise_exception=True): 
                serializer.save() 
                response = {'msgType':'success', 'message':'Your account has been created successfully.'}
            elif serializer.errors:
                raise serializers.ValidationError({'msgType':'error', 'message': serializer.errors})
            return Response(response)

SERIALIZERS.PY

class nestedSerializer(serializers.ModelSerializer):

class Meta:
    model = Nested
    fields = ('agreed_terms_of_service')

def validate(self, data):
    return data


class userSerializer(serializers.ModelSerializer):

nested = nestedSerializer()

class Meta:
    model = User
    fields = ('pk','email', 'password', 'username','first_name','last_name','nested')

def validate(self, data):
    email = data['email']
    try:
        User.objects.get(email = email)
    except User.DoesNotExist:
        return data
    else:
        raise serializers.ValidationError({'msgType':'error', 'message':'A user with this email address already exists.'})
    return data

def create(self, validated_data):
    nested_data = validated_data.pop('extend')
    email = validated_data['email']
    user = User.objects.create(**validated_data)
    user.username = user.id
    user.set_password(validated_data['password'])
    user.save()
    nested = Nested.objects.create(user=user, **nested_data)
    return user

Models.py

class Nested(models.Model):
    user = models.OneToOneField(User)
    personalid = models.CharField(max_length=255)
    agreed_terms_of_service = models.BooleanField()
    city = models.CharField(max_length=255, blank=True, null=True)

Thank you for your help in advance. It is much appreciated.

Mee
  • 139
  • 2
  • 9
  • Also, If you see that I should be doing anything here in a different way please let me know. Thanks – Mee Oct 18 '15 at 15:32
  • can you post you "Nested" model? If agreed_terms_of_service does not have "blank=True", I'm surprised DRF is allowing users to submit the form without it. Are you using DRF 3.2.4? – SilentDev Oct 18 '15 at 20:40
  • Hi..thanks for looking into it...but it does not have "blank=True." Im using DRF 3.0 – Mee Oct 18 '15 at 22:54
  • can you post your "Nested" model? I'm trying to replicate the issue on my end. – SilentDev Oct 18 '15 at 23:06
  • I just added it to the question. – Mee Oct 18 '15 at 23:35

1 Answers1

1

First, I'd change your current validate() function to validate_email() (because all you're doing is validating that the email is not already in use). You should use validate() if you want access to multiple fields in your function. See the documentation here to read more about when you should use field-level validation and object-level validation: http://www.django-rest-framework.org/api-guide/serializers/#validation

Second, in your view, you do:

if data['password'] != data['password2']:
                raise serializers.ValidationError({'msgType':'error','message':'Passwords do not match.'})

If you're verifying that "password" and "confirm password" field match, I'd do that check in the validate() function of your serializer (since you'll be accessing both the 'password' and the 'password2' field.

Third, in your create method, I'd use User.objects.create_user to create a user (create_user will handle the hashing of the password, etc. That way, you don't need to explicitly do user.set_password(validated_data['password'])). See the answer here for more information: How to create a user in Django?

Lastly, to address the main issue. Your "agreed_terms_of_service" is a Boolean field, which means it accepts both True and False. What I'd try is this:

class nestedSerializer(serializers.ModelSerializer):

    class Meta:
        model = Nested
        fields = ('agreed_terms_of_service')

    def validate_agreed_terms_of_service(self, data):
        print(data) # to verify if data is even a boolean
        if data == True or data == 'True':
            return data
        raise serializers.ValidationError({'msgType':'error', 'message':'Please accept the terms and conditions.'})

and in your create function for your userSerializer, add a print statement at the beginning to see if create is being executed before the "agreed_terms_of_service" validation.

def create(self, validated_data):
    print("Creating the object before validating the nested field.")
    # I'd be surprised if DRF executes a create function before
    # even validating it's nested fields.

    # rest of the create code goes here

When you add the statements above, what does it print for "data" and does it print "data" before "creating the object"?

Community
  • 1
  • 1
SilentDev
  • 20,997
  • 28
  • 111
  • 214
  • this may be ridiculous, but nothing prints. I am running this in the browser and not a command line, but print(data) and print("Creating the object before validating the nested field.") seem to be ignored entirely. Now if I dont agree to the terms of service it does return the error and doesnt create the user, but if i then go back and click agree, it still raises that same validation error with the please accept the terms..... Good news is a user is not created. – Mee Oct 19 '15 at 01:16
  • @Mee How are you running the server? I'm assuming you do something like "python manage.py runserver"? If yes, then it should print to the terminal, not to the browser. Regardless though, there is progress. It's correctly not creating the user when the terms and services are not agreed too. It continues to raise the error even when it is agreed to because data != True. Can you update your post to show your front-end form? If you're using an checkbox like this: , what does your "value" equal? – SilentDev Oct 19 '15 at 01:26
  • ahh gotcha...sorry...yes it prints True in the console and does not print the other statement about creating before...the value in the checkbox is True – Mee Oct 19 '15 at 13:32
  • @Mee okay I edited my post. Change the line "if data == True:" to "if data == True or data == 'True':". It seems as if the "True" value being sent to the backend is not a boolean but a string. Let me know if it works. – SilentDev Oct 19 '15 at 19:40
  • I accepted your answer. I ended up doing a try except statement looking for a Key error to show that its blank. And if agreed_terms_of_service != True ....I wrote the error message to say stop messing with inspect element. Thanks a lot for your help. – Mee Oct 20 '15 at 15:23