Search code examples
djangorestdjango-rest-frameworkdjango-serializer

Nested serializer get_or_create unique constraint validation error


I Have a UserModelSerializer:

class UserModelSerializer(ModelSerializer):

    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'date_joined', 'is_active', 'is_verified', 'is_superuser', 'is_staff', 'is_hidden', 'created_date']
        read_only_fields = ['id', 'date_joined', 'is_active', 'is_verified', 'is_superuser', 'is_staff', 'is_hidden', 'created_date']

and a UserBanModelSerializer:

class UserBanModelSerializer(ModelSerializer):
    user = UserModelSerializer()

    class Meta:
        model = UserBan
        fields = ['id', 'user', 'until', 'reason', 'description', 'created_date']
        read_only_fields = ['id', 'created_date']

    def create(self, validated_data):
        user_data = validated_data.pop('user')
        user, created = User.objects.get_or_create(**user_data)
        user_ban = UserBan.objects.create(user=user, **validated_data)

        return user_ban

I want my representation to have complete User data and also i want the frontend developer to be able to send the represented data back to server without any problem(if I only add UserModelSerialzer to to_representation method of UserBanModelSerializer frontend developer needs to send user id instead of the whole user data

But when I use is_valid method on serializer I get this exceptions:

"username": [
    "A user with that username already exists."
],
"email": [
    "A user with that email already exists."
]

and I'm aware that I can remove validators from username and email like this:

extra_kwargs = {
    'email': {'validators': []},
    'username': {'validators': []},
}

but I dont want to remove validators form username and let the database decide to unique constraint

I dont want to create a User when I want to create a UserBan I only want it to be created if the User is not found


Solution

  • I ended up using this style which suites my needs

    class UserModelSerializer(ModelSerializer):
    
        class Meta:
            model = User
            fields = ['id', 'username', 'email', 'date_joined', 'is_active', 'is_verified', 'is_superuser', 'is_staff', 'is_hidden', 'created_date']
            read_only_fields = ['id', 'date_joined', 'is_active', 'is_verified', 'is_superuser', 'is_staff', 'is_hidden', 'created_date']
    
    
    class UserBanModelSerializer(ModelSerializer):
        user = UserModelSerializer(read_only=True)
    
        class Meta:
            model = UserBan
            fields = ['id', 'user', 'until', 'reason', 'description', 'created_date']
            read_only_fields = ['id', 'created_date']
    
        def create(self, validated_data):
            user_data = self.initial_data.pop('user')
            user, created = User.objects.get_or_create(**user_data)
            response = UserBan.objects.create(user=user, **validated_data)
            
            return response
    

    Reponse has serialized data of user and the passed data to create method also has the the same data and then user is created or get from the database

    For more robust way you can alternatively use this style:

    class UserBanModelSerializer(ModelSerializer):
        user = UserModelSerializer(read_only=True)
    
        class Meta:
            model = UserBan
            fields = ['id', 'user', 'until', 'reason', 'description', 'created_date']
            read_only_fields = ['id', 'created_date']
    
        def create(self, validated_data):
            user_data = self.initial_data.pop('user')
    
            try:
                user = User.objects.get(id=user_data.get('id'))
            except User.DoesNotExist:
                raise EntityNotFoundException(message='User not Found!')
    
            response = UserBan.objects.create(user=user, **validated_data)
            
            return response
    

    which incorrect user id results in an exception.