Лучший способ создать объект со многими во многих полях, используя Django RF API - PullRequest
0 голосов
/ 18 января 2019

Я начал проект с идеей, с которой я справлюсь больше, чем я знаю, но эй ... некоторые из нас учатся глупо, верно?

В настоящее время есть 2 модели: User модель, которая используеткласс AbstractUser, а затем у меня есть модель Role, которая предоставляет множество ролей, которые могут со временем меняться, поэтому мне нужно, чтобы она была динамичной.

Вот как выглядит мой api/models.py:

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')

У меня несколько проблем, но в настоящее время, когда я использую почтальон или представления DRF API, я не могу создать пользователя с ролями или без ролей.

Попытка с ролями:

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

Попытка без ролей:

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

Теперь я знаю, что должен сначала создать пользователя, а затем назначить роль, однако я не могу найти способ сделать это черезAPI.

Вот мой UserSerializer класс:

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

Я видел рекомендации по созданию файла form.py и добавлению метода сохранения, но я не уверен, что он применяется так, как это будетбыть строго API в качестве моего бэкэнда и будет реализовывать AngularJS для моего интерфейса.

Кроме того, каждый раз, когда я пытаюсь сохранить или обновить пользователя в сериализаторе, User.roles.id возвращает пользователя, у которого нет атрибута с именем role...

Так что я надеюсь, что кто-то сможет помочь.Спасибо.

1 Ответ

0 голосов
/ 18 января 2019

Во-первых, не имея отношения к вашему вопросу, поле ваши роли m2m должно быть обновлено до:

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

related_name - это то, как вы ссылаетесь на текущую модель (User) из модели, переданной в ManyToManyField (Role). С моим предложением вы написали бы 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'}],
}

Когда вы POST к нему, вы должны использовать:

{
    '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 вместо.

...