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:
- Первая версия - класс 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 (Организация, созданная через панель администратора):
Но когда я пытаюсь создать организацию, я получаю ошибку, которая говорит, что пользователь уже существует:
Итак, я проверил, что произойдет, когда я попытаюсь вставить несуществующее имя пользователя в передаваемые данные 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:
Мой метод создания выглядит следующим образом:
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.
- Новый класс модели 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:
В этом сценарии нет такой проблемы, как в первом случае.
- Окончательное решение с использованием SlugReleatedField работает как ожидалось. Сериализатор для User. не используется.
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()
Может ли кто-нибудь объяснить, почему это происходит, что мы можем наблюдать в сценарии 1?
Простите, что этот пост получился таким длинным. Я постарался сократить его настолько, насколько это возможно. Я могу выложить весь проект на github, если это будет необходимо для читабельности.