DRF пытается создать объект User при использовании во вложенном сериализаторе

В документацииDRF говорится, что "По умолчанию вложенные сериализаторы доступны только для чтения. Если вы хотите поддерживать операции записи во вложенное поле сериализатора, вам нужно будет создать методы create() и/или update(), чтобы явно указать, как должны сохраняться дочерние отношения."<< В целом это верно, за исключением одного случая. Когда класс User является вложенным объектом в сериализаторе. Случай использования заключается в добавлении существующего пользователя во вновь созданную организацию.

Рассмотрим мой простой пример, в котором я заметил эту проблему:

views.py

class UserOrganizationViewSet(viewsets.ModelViewSet):
    # authentication_classes = (JwtAuthentication, SessionAuthentication)
    # permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
    serializer_class = UserOrganizationSerializer
    queryset = UserOrganization.objects.all()

models.py:

class UserOrganization(models.Model):
    name = models.CharField(max_length=256)
    users = models.ManyToManyField(User, blank=True, related_name="user_organizations", through='OrganizationMember')

    def __str__(self):
        return self.name

class OrganizationMember(models.Model):
    organization = models.ForeignKey(UserOrganization, on_delete=CASCADE)
    user = models.ForeignKey(User, on_delete=CASCADE)
    joined = AutoCreatedField(_('joined'))

serializers.py:

  1. Первая версия - класс User имеет свой собственный простой сериализатор и используется как вложенный сериализатор:
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        fields = ['username']


class UserOrganizationSerializer(serializers.ModelSerializer):
     users = UserSerializer(many=True, required=False)
     class Meta:
         model = UserOrganization
         fields = ['id', 'name', 'users']
         read_only_fields = ['id']

В этом случае я могу получить данные через метод GET (Организация, созданная через панель администратора):

enter image description here

Но когда я пытаюсь создать организацию, я получаю ошибку, которая говорит, что пользователь уже существует: enter image description here

Итак, я проверил, что произойдет, когда я попытаюсь вставить несуществующее имя пользователя в передаваемые данные JSON. Согласно ожиданиям DRF говорит, что мне нужно переопределить метод create():

{
  "name": "Test2",
  "users": [
    {
      "username": "admins"
    }
  ]
}

AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `organization.serializers.UserOrganizationSerializer`, or set `read_only=True` on nested serializer fields.

И есть первое странное поведение. DRF все еще пытается создать объект User: enter image description here

Мой метод создания выглядит следующим образом:

def create(self, validated_data):
     print("validated_data", validated_data)
     users = validated_data.pop('users', [])
     user_organization = UserOrganization.objects.create(**validated_data)
     print("users", users)

     for user in users:
          userSerializer = UserSerializer(user)
          print("userSerializer.data", userSerializer.data)
          username = userSerializer.data['username']
          print("username", username)
          user_organization.users.add(get_user_model().objects.get(username=username))
     return user_organization

В этом случае мой метод вообще не вызывается.

В случае, когда я передал пользователя, которого не существует, вызывается мой метод create() и выбрасывается исключение, как и должно быть. (Я знаю, что не обрабатываю ошибки, это просто песочница):

{
  "name": "Test2",
  "users": [
    {
      "username": "admin1"
    }
  ]
}

django.contrib.auth.models.User.DoesNotExist: User matching query does not exist.
  1. Новый класс модели UserProfile и сериализатор для этого класса, используемый как вложенный:

class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserProfile
        fields = ['name']


class UserOrganizationSerializer(serializers.ModelSerializer):
     profiles = UserProfileSerializer(many=True)
     class Meta:
         model = UserOrganization
         fields = ['id', 'name', 'profiles']
         read_only_fields = ['id']


     def create(self, validated_data):
          print("validated_data ", validated_data)
          users = validated_data.pop('profiles', [])
          user_organization = UserOrganization.objects.create(**validated_data)
          print("create", users)

          for user in users:
               print("username = ", user['name'])
               user_organization.profiles.add(UserProfile.objects.get(name=user['name']))
          user_organization.save()
          return user_organization

Небольшое изменение было необходимо для изменения поля users на profiles внутри UserOrganization.

Создание организации с существующим профилем пользователя (созданным администратором) через POST:

enter image description here

В этом сценарии нет такой проблемы, как в первом случае.

  1. Окончательное решение с использованием SlugReleatedField работает как ожидалось. Сериализатор для User.
  2. не используется.
class UserOrganizationSerializer(serializers.ModelSerializer):
     users = serializers.SlugRelatedField(many=True, queryset=get_user_model().objects.all(), slug_field='username')

     class Meta:
         model = UserOrganization
         fields = ['id', 'name', 'users']
         read_only_fields = ['id']

Приведенный выше код делает то, что мне нужно и очень прост. Есть небольшая проблема, связанная с производительностью при получении всех пользователей из БД в этой строке: queryset=get_user_model().objects.all()

enter image description here

Может ли кто-нибудь объяснить, почему это происходит, что мы можем наблюдать в сценарии 1?

Простите, что этот пост получился таким длинным. Я постарался сократить его настолько, насколько это возможно. Я могу выложить весь проект на github, если это будет необходимо для читабельности.

Вернуться на верх