Django метод создания вложенного сериализатора - PullRequest
1 голос
/ 27 марта 2020

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

##CLasses
class Classes(models.Model):
    class_name = models.CharField(max_length=255)
    class_code = models.CharField(max_length=255)
    created_date = models.DateTimeField(auto_now_add=True)
    def __str__(self):
        return self.class_name
    class Meta:
        ordering = ['class_code']
##Streams
class Stream(models.Model):
    stream_name = models.CharField(max_length=255)
    classes = models.ForeignKey(Classes,related_name="classes",on_delete=models.CASCADE)
    created_date = models.DateTimeField(auto_now_add=True)
    def __str__(self):
        return self.stream_name
    class Meta:
        ordering = ['stream_name']

Вот вид

class StreamViewset(viewsets.ModelViewSet):
    queryset = Stream.objects.all()
    serializer_class = StreamSerializer

Вот класс сериализатора

class StreamSerializer(serializers.ModelSerializer):
    # classesDetails = serializers.SerializerMethodField()
    classes = ClassSerializer()
    class Meta:
        model = Stream
        fields = '__all__'
    def create(self,validated_data):
        classes = Classes.objects.get(id=validated_data["classes"])
        return Stream.objects.create(**validated_data, classes=classes)
    # def perfom_create(self,serializer):
    #     serializer.save(classes=self.request.classes)
    #depth = 1
    # def get_classesDetails(self, obj):
    #     clas = Classes.objects.get(id=obj.classes)
    #     classesDetails =  ClassSerializer(clas).data
    #     return classesDetails

Я пробовал несколько способов включения метода создания но вот так отображается ошибка {"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}. Любой вклад будет высоко ценится

1 Ответ

1 голос
/ 27 марта 2020

Это очень распространенная ситуация при разработке 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.

...