Best method to create an object with many to many field using Django RF API

时间:2019-01-18 18:36:30

标签: django django-models django-rest-framework django-rest-viewsets

I have started a project with an idea that's more than I know I can handle but hey... some of us learn the dumb way right?

Currently have 2 models: User model that uses the AbstractUser class and then I have a Role model that provides many roles that may change over time so I need it to be dynamic.

This is what my api/models.py looks like:

from django.db import models
from django.contrib.auth.models import AbstractUser
from django import forms    

class Role(models.Model):
    '''
    The Role entries are managed by the system,
    automatically created via a Django data migration.
    '''
    CABLER = 1
    CLIENT = 2
    ENGINEER = 3
    PROJECTMANAGER = 4
    SALES = 5
    PARTNER = 6
    ADMIN = 6

    ROLE_CHOICES = (
        (CABLER, 'cabler'),
        (CLIENT, 'client'),
        (ENGINEER, 'engineer'),
        (PROJECTMANAGER, 'project manager'),
        (SALES, 'sales'),
        (PARTNER, 'partner'),
        (ADMIN, 'admin'),

    )

    id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)

    def __str__(self):
        return self.get_id_display()   

class User(AbstractUser):
    roles = models.ManyToManyField(Role, related_name='roles')

I'm running into several problems but currently when I use postman or The DRF API views I can't create a user with roles or without roles.

Attempting with roles:

Direct assignment to the forward side of a many-to-many set is prohibited. Use roles.set() instead.

Attempting without roles:

{
    "roles": [
        "This list may not be empty."
    ]
}

Now, I know I have to first create the User and then assign the role, however, I cannot seem to find a way to do it through the API.

Here's my UserSerializer class:

class UserSerializer(serializers.ModelSerializer):
    # roles = serializers.CharField(source='get_roles_display', )
    # roles = RoleSerializer(many=True, read_only=False, )

    class Meta:
        model = User
        fields = ('url', 'username', 'password', 'email', 'roles')

        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        password = validated_data.pop('password')
        instance = User(**validated_data)
        if password is not None:
            instance.set_password(password)
            instance.save()

        return instance

I have seen recommendations to create a form.py file and add a save method but I'm not certain that it applies as this will be strictly an API as my backend and will be implementing AngularJS for my front end.

Also any time I try to save or update the user under the serializer User.roles.id returns a User does not have an attribute named roles...

So I'm hoping someone may be able to help. Thanks.

1 个答案:

答案 0 :(得分:0)

首先且与您的问题无关,您的角色m2m字段应更新为:

roles = models.ManyToManyField(Role, related_name='users')

related_name是您如何从传递到ManyToManyField(角色)的模型中引用当前模型(User)的方法。根据我的建议,您应该写Role.objects.first().users而不是Role.objects.first().roles

第二,如果可能的话,我建议传递角色以创建/更新用户作为主键列表。即使这意味着创建仅用于写入的第二个序列化器属性。例如:

class UserSerializer(serializers.ModelSerializer):
    roles = RoleSerializer(many=True, read_only=True)
    role_ids = serializers.PrimaryKeyRelatedField(
        queryset=Role.objects.all(),
        many=True, write_only=True)

    class Meta:
        model = User
        fields = ('url', 'username', 'password', 'email', 'roles', 'role_ids')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        password = validated_data.pop('password')
        # Allow the serializer to handle the creation. Don't do it yourself.
        instance = super().create(**validated_data)
        if password is not None:
            instance.set_password(password)
            instance.save()

        return instance

当您的API吐出用户时,其外观应为:

{
    'url': '...',
    'username': '...',
    'email': '...',
    'roles': [{'id': 1, 'name': 'client'}],
}

开机自检时应使用:

{
    'url': '...',
    'username': '...',
    'email': '...',
    'password': '...',
    'role_ids': [1, 2],
}

顺便说一句,出现错误User does not have an attribute named roles.的原因是因为您无法将roles传递到User的构造函数中。建立实例后,需要设置User.roles。您应该已经完成​​:

user = User(**validated_data)
user.roles.set(roles) # If you need to re-write the entire list.

尽管我头顶上方,但我不确定它是否会起作用。您可能需要事先保存。因此,我建议您改用super().create