Это очень распространенная ситуация при разработке API с DRF.
Проблема
До того, как DRF достигнет метода create()
, он проверяет ввод, который, как я предполагаю, имеет аналогичную форму
{
"classes": 3,
"stream_name": "example"
}
Это означает, что, поскольку было указано, что
classes = ClassSerializer()
DRF пытается построить словарь classes
из целого числа. Конечно, это не удастся, и вы можете видеть это из словаря ошибок
{"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}
Решение 1 (требуется новое поле для записи {field_name}_id
)
Возможное решение - установить read_only=True
в вашем ClassSerializer
и используют альтернативное имя для поля при написании , обычно используется {field_name}_id
. Таким образом, проверка не будет выполнена. См. этот ответ для получения более подробной информации.
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
'classes_id',
)
extra_kwargs = {
'classes_id': {'source': 'classes', 'write_only': True},
}
Это чистое решение, но требует изменения пользовательского API. Если это не вариант, перейдите к следующему решению.
Решение 2 (требуется переопределение to_internal_value
)
Здесь мы переопределяем метод to_internal_value
. Это где вложенный ClassSerializer
выдает ошибку. Чтобы избежать этого, мы устанавливаем это поле на read_only
и управляем проверкой и анализом в методе.
Обратите внимание, что, поскольку мы не объявляем поле classes
в доступном для записи представлении, действие по умолчанию super().to_internal_value
- игнорировать значение из словаря.
from rest_framework.exceptions import ValidationError
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
def to_internal_value(self, data):
classes_pk = data.get('classes')
internal_data = super().to_internal_value(data)
try:
classes = Classes.objects.get(pk=classes_pk)
except Classes.DoesNotExist:
raise ValidationError(
{'classes': ['Invalid classes primary key']},
code='invalid',
)
internal_data['classes'] = classes
return internal_data
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
)
В этом решении вы можете использовать одно и то же имя поля для чтения и записи, но код немного запутанный.
Дополнительные примечания
- Вы неправильно используете аргумент
related_name
, см. этот вопрос . Это наоборот,
classes = models.ForeignKey(
Classes,
related_name='streams',
on_delete=models.CASCADE,
)
В этом случае это должно быть streams
.