프로젝트

UserSerializer에서 ManyToManyField

content0474 2025. 1. 3. 20:43

Django 프로젝트를 개발하다 보면, 기본 User 모델의 groups나 user_permissions 필드를 다룰 때 TypeError가 생길수있다. 

이번에 User 모델을 직접 생성하거나 데이터를 직렬화하는 과정에서 아래와 같은 에러가 발생했다.


TypeError at /accounts/signup/
Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead.


에러 원인
Django의 groups와 user_permissions 필드는 ManyToManyField
ManyToManyField는 직접 할당할 수 없음
아래와 같이 User(**validated_data) 형태로 객체를 생성하려 하면, 내부적으로 직접 할당이 시도되면서 에러가 발생함

 

해결 방법
ManyToManyField 데이터를 처리하려면 set() 메서드를 사용해야 함. 

set() 메서드를 호출하기 전, validated_data에서 해당 데이터를 분리해야 함 -> .pop() 메서드를 활용


기존 코드 (에러 발생 코드)

class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)

    class Meta:
        model = User
        fields = "__all__"
        extra_kwargs = {
            'email': {'required': True},
            'first_name': {'required': True},
            'last_name': {'required': True},
            'nickname': {'required': True},
            'birthday': {'required': True},
            'phone_number': {'required': True},
        }

    def validate(self, data):
        if data['password'] != data['password2']:
            raise serializers.ValidationError({
                "password": "비밀번호가 일치하지 않습니다."
            })
        return data

    def create(self, validated_data):
        validated_data.pop('password2')
        password = validated_data.pop('password')
        user = User(**validated_data)
        user.set_password(password)
        user.save()
        return user


위 코드에서 groups와 user_permissions 필드가 포함된 데이터를 처리하지 못해 에러가 발생함.

수정된 코드

class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)
    groups = serializers.PrimaryKeyRelatedField(many=True, queryset=Group.objects.all(), required=False)
    user_permissions = serializers.PrimaryKeyRelatedField(many=True, queryset=Permission.objects.all(), required=False)

    class Meta:
        model = User
        fields = "__all__"
        extra_kwargs = {
            'email': {'required': True},
            'first_name': {'required': True},
            'last_name': {'required': True},
            'nickname': {'required': True},
            'birthday': {'required': True},
            'phone_number': {'required': True},
        }

    def validate(self, data):
        if data['password'] != data['password2']:
            raise serializers.ValidationError({
                "password": "비밀번호가 일치하지 않습니다."
            })
        return data

    def create(self, validated_data):
        groups = validated_data.pop('groups', [])
        user_permissions = validated_data.pop('user_permissions', [])
        validated_data.pop('password2')
        password = validated_data.pop('password')

        user = User(**validated_data)
        user.set_password(password)
        user.save()

        # ManyToManyField 처리
        user.groups.set(groups)
        user.user_permissions.set(user_permissions)

        return user

 

주요 변경 사항
groups와 user_permissions를 분리
validated_data.pop()을 사용해 ManyToManyField 데이터를 따로 분리함
groups와 user_permissions가 없을 수도 있으니 default=[] 설정을 추가

ManyToManyField 데이터 설정
user.groups.set(groups)와 user.user_permissions.set(user_permissions)로 데이터를 설정

Serializer 필드 정의
PrimaryKeyRelatedField를 사용해 groups와 user_permissions 필드를 명시적으로 정의


정리
Django의 ManyToManyField는 .set() 또는 .add() 메서드로 처리해야 함
직렬화 과정에서 데이터는 pop()으로 분리 후 처리하는 것이 안정적
Serializer에 필요한 필드를 명시적으로 정의해도 좋다.