Django REST Framework - Update object with FK to a model with unique field

1.7k Views Asked by At

I have the following models:

# models.py
class NPSUser(AbstractBaseUser, TimeStampedModel):
    email = models.EmailField(unique=True)

    first_name = models.CharField(max_length=40, blank=True)
    last_name = models.CharField(max_length=40, blank=True)

class Account(models.Model):
    user = models.OneToOneField(NPSUser, related_name='%(class)s_role', primary_key=True)

    class Meta:
        abstract = True

class Director(Account):
    tenant = models.OneToOneField(Tenant, related_name='director')

With the corresponding serializers:

# serializers.py
class NPSUserSerializer(serializers.ModelSerializer):

    password = serializers.CharField(write_only=True, required=False)
    confirm_password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = NPSUser
        fields = ('id', 'email', 'created', 'modified', 'first_name',
                  'last_name', 'password', 'confirm_password', 'director_role',
                  'manager_role', 'employee_role', 'projectmanager_role',
                  'collector_role')
        read_only_fields = ('id', 'created', 'modified', 'director_role',
                            'manager_role', 'employee_role', 'projectmanager_role',
                            'collector_role')

    def create(self, validated_data):
        return NPSUser.objects.create(**validated_data)

    def update(self, instance, validated_attrs):
        instance.email = validated_attrs.get('email', instance.email)
        instance.first_name = validated_attrs.get('first_name', instance.first_name)
        instance.last_name = validated_attrs.get('last_name', instance.last_name)

        instance.save()
        # password validation here...
        return instance

class DirectorSerializer(serializers.ModelSerializer):

    user = NPSUserSerializer()

    class Meta:
        model = Director

    def create(self, validated_data):
        user_data = validated_data.pop('user')

        user = NPSUser(**user_data)
        user.set_password(user_data['password'])
        user.save()

        director = Director.objects.create(user=user, **validated_data)

        return director

I use viewset.ModelViewSet in the views.py and if I already have a user with the name 'John' and '[email protected]' email in the database and I make a PUT request to update the NPSUser through its Director relationship, like this: PUT /api/v1/directors/1/ {id: 1, tenant: 1, user: {id: 1, first_name: 'Jane', last_name: 'King', email: '[email protected]'}} the following error is returned from the server: {"user":{"email":["This field must be unique."]}}, which means that serializer.is_valid() does not pass.

And the proper question is: How can I update the NPSUser object that has a unique=True field from another object that has a relationship field with NPSUser using serializers in Django REST Framework?

1

There are 1 best solutions below

0
On BEST ANSWER

I've just had the same problem. My solution was to overload the email field this way:

email = serializers.EmailField(unique=False)

and them I implemented my own validation logic to avoid duplicate e-mails in the serializer defining a def validate(self, data) method. In this validation method I check if there an id in in data dict, and, in the case there is, i verify if the e-mail is the same or if it has changed. My code was something like this.

class ClientUserCreateUpdateSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(required=False)
    email = serializers.EmailField(required=False)

    def validate(self, data):
        client_user_id = int(data.get('id', -1))
        is_update = client_user_id > 0

        if not is_update:
            if not 'email' in data:
                raise serializers.ValidationError({'email': [_('This field is required')]})
            elif ClientUser.objects.filter(is_active=True, email=data['email']).exists():
                raise serializers.ValidationError({'email': [_('The e-mail address \"%(email)s\" is already being used') %  {'email': data.get('email')}]})
        elif 'email' in data and ClientUser.objects.get(id=data['id']).email != data['email']:
            if ClientUser.objects.filter(is_active=True, email=data['email']).exists():
                raise serializers.ValidationError({'email': [_('The e-mail address \"%(email)s\" is already being used') %  {'email': data.get('email')}]})

        return data

    class Meta:
        model =  ClientUser
        fields = ('id', 'first_name', 'last_name', 'email')

Hope it helps.