본문 바로가기

졸업 프로젝트

[Django] Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead.

# 오류

 

User Serializer를 만드는데 위와 같은 오류가 났다.

class SignUpSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('__all__')
        extra_kwargs = {"password": {"write_only":True}}

    def create(self, validated_data):
        user = User.objects.create(**validated_data)
        user.set_password(validated_data['password'])
        user.save()

        return user

원래 짜던 코드와 다르게 짜긴 했는데 차이가 무엇일까...해서 검색도 하고 질문도 해서 답을 찾았다.

 

class SignUpSerializer(serializers.Serializer):
    class Meta:
        model=User
        fields=['id', 'username','password','nickname', 'email', 'gender', 'age']
        extra_kwargs = {"password": {"write_only":True}}

    gender_list = (
        ('남', '남'),
        ('여', '여')
    )

    username = serializers.CharField(max_length=20) # 아이디
    password = serializers.CharField()
    email = serializers.EmailField()
    nickname = serializers.CharField(max_length=20) # 이름
    gender = serializers.ChoiceField(
        choices=gender_list
    )
    age = serializers.IntegerField()

    def create(self, validated_data):

        if User.objects.filter(username=validated_data['username']).exists() or User.objects.filter(email=validated_data['email']).exists():
            raise serializers.ValidationError('username 존재 or email 존재')


        else:
            user = User.objects.create(
                username=validated_data['username'],
                nickname=validated_data['nickname'],
                email=validated_data['email'],
                gender=validated_data['gender'],
                age=validated_data['age']
            )
            user.set_password(validated_data['password'])
            user.save()
            return user

위가 수정한 코드이다.

하나하나 작성하기 귀찮았는데 결국 이렇게 하게 됨.

 


# 원인

 

User.objects.crerate(**validated_data)는 **validated_data 안에 있는 모든 필드들에 대하여 직접 할당을 시도한다.

만약 모델의 필드에서 ManyToMany 필드가 있다면 직접할당을 할 수 없다. 

대신 set()이나 add()를 사용해야 한다.

즉, Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead. 는 ManyToMany 필드에 직접할당을 시도할 때 나는 오류.

 

난 MTM 필드를 사용한 적이 없는데... 라고 할 수 있지만 migrations 파일을 보면 있다.

AbstractUser를 사용했으므로 기본 User Model에 존재했었다....

class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('auth', '0012_alter_user_first_name_max_length'),
    ]

    operations = [
        migrations.CreateModel(
            name='User',
            fields=[
                ('password', models.CharField(max_length=128, verbose_name='password')),
                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
                ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
                ('id', models.UUIDField(editable=False, primary_key=True, serialize=False)),
                ('username', models.CharField(max_length=20, unique=True)),
                ('nickname', models.CharField(max_length=20)),
                ('email', models.EmailField(max_length=254, unique=True)),
                ('gender', models.CharField(choices=[('여', '여'), ('남', '남')], default='여', max_length=2)),
                ('age', models.IntegerField(validators=[django.core.validators.MinValueValidator(10), django.core.validators.MaxValueValidator(80)])),
                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
            ],
            options={
                'verbose_name': 'user',
                'verbose_name_plural': 'users',
                'abstract': False,
            },
            managers=[
                ('objects', django.contrib.auth.models.UserManager()),
            ],
        ),
    ]

그러므로 만약 create(**validated_data)를 사용하고 싶다면 pop() 메서드를 이용해 해당 필드를 빼거나,

내가 한 방식처럼 할당 받을 필드를 직접 명시해준다.

 

def create(self, validated_data):
    groups_data = validated_data.pop('groups', None)  # ManyToManyField 이름이 'groups'라고 가정
    user = User.objects.create(**validated_data)
    if groups_data:
        user.groups.set(groups_data)
    user.set_password(validated_data['password'])
    user.save()
    return user

pop() 사용 예시

 


# 회고

 

VSC만 되는지는 모르겠는데 ctrl + 좌클릭 을 누르면 해당 함수와 관련된 링크 혹은 해당 단어가 포함된 부분을 찾아준다.

특히 Django의 함수를 재정의할 때는 제대로 찾아봐야겠다는 생각을 했다.

실제로 create() 함수가 내장된 BaseSerailizer 클래스로 가보면 이렇게 나와있다.

자주 사용하는 max_length도 있고, allow_empty 키워드가 정의된 것도 보인다.

함수도 있음.

 

MTM 오류 관련해서 찾아봤는데 명시하면 필드를 자동으로 pop하나?

 

class MinimumLengthValidator:
    """
    Validate that the password is of a minimum length.
    """

    def __init__(self, min_length=8):
        self.min_length = min_length

    def validate(self, password, user=None):
        if len(password) < self.min_length:
            raise ValidationError(
                ngettext(
                    "This password is too short. It must contain at least "
                    "%(min_length)d character.",
                    "This password is too short. It must contain at least "
                    "%(min_length)d characters.",
                    self.min_length,
                ),
                code="password_too_short",
                params={"min_length": self.min_length},
            )

    def get_help_text(self):
        return ngettext(
            "Your password must contain at least %(min_length)d character.",
            "Your password must contain at least %(min_length)d characters.",
            self.min_length,
        ) % {"min_length": self.min_length}

비밀번호 조건 때문에 관련 함수도 찾아봤다.

DRF 라이브러리 안에 있었음.

min_length같은 것좀 수정하고 싶은데 직접 건드려도 되나?? 나중에 회의하고나서 수정할 예정