Django Rest Framework: как передать данные во вложенный Serializer и создать объект только после пользовательской проверки - PullRequest
0 голосов
/ 16 сентября 2018

У меня есть две модели:

class Book(AppModel):
    title = models.CharField(max_length=255)

class Link(AppModel):
    link = models.CharField(max_length=255)

class Page(AppModel):
    book= models.ForeignKey("Book",related_name="pages",on_delete=models.CASCADE)
    link = models.ForeignKey("Link", related_name="pages", on_delete=models.CASCADE)
    page_no = models.IntegerField()
    text = models.TextField()

и serializers

class LinkSerializer(serializers.ModelSerializer):
    class Meta:
       model = Link
       fields = ['link']

class PageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Page
        fields = ('link','text','page_no')

    def validate_text(self, value):
        #some validation is done here.

    def validate_link(self, value):
        #some validation is done here.

class BookSerializer(serializers.ModelSerializer):
    pages = PageSerializer(many=True)
    class Meta:
        model = Book
        fields = ('title','pages')

    @transaction.atomic
    def create(self, validated_data):
        pages_data= validated_data.pop('pages')
        book = self.Meta.model.objects.create(**validated_data)
        for page_data in pages_data:
            Page.objects.create(book=book, **page_data)
        return book

Существует метод validate_text в PageSerializer.Метод create никогда не вызовет PageSerializer, а page_data никогда не будет проверен.

Поэтому я попробовал другой подход:

@transaction.atomic
def create(self, validated_data):
    pages_data = validated_data.pop('pages')
    book= self.Meta.model.objects.create(**validated_data)
    for page_data in pages_data:
        page = Page(book=book)
        page_serializer = PageSerializer(page, data = page_data)
        if page_serializer.is_valid():
            page_serializer.save()
        else:
            raise serializers.ValidationError(page_serializer.errors)
    return book

Опубликованные данные:

{
"title": "Book Title",
"pages": [{
"link":1,"page_no":52, "text":"sometext"}]
}

Но вышеприведенный подход выдает ошибку:

{
"link": [
    "Incorrect type. Expected pk value, received Link."
    ]
}

Я также обнаружил, почему эта ошибка вызвана: хотя я публикую данные со значением pk 1 Link,данные, передаваемые в PageSerializer из BookSerializer, выглядят так: {"link": "/go_to_link/", "page_no":52, "text": "sometext"}

Почему экземпляр Link передается в PageSerializer, а то, что я отправил, - pk из Link?НУЖНА ПОМОЩЬ ЗДЕСЬ.

Ответы [ 2 ]

0 голосов
/ 16 сентября 2018

Чтобы проверить вложенный объект с помощью вложенного сериализатора:

@transaction.atomic
def create(self, validated_data):
    pages_data = validated_data.pop('pages') #pages data of a book
    book= self.Meta.model.objects.create(**validated_data)
    for page_data in pages_data:
        page = Page(book=book)
        page_serializer = PageSerializer(page, data = page_data)
        if page_serializer.is_valid(): #PageSerializer does the validation
            page_serializer.save()
        else:
            raise serializers.ValidationError(page_serializer.errors) #throws errors if any
    return book

Предположим, вы отправляете данные как:

{
    "title": "Book Title",
    "pages": [{
        "link":2,#<= this one here
        "page_no":52, 
        "text":"sometext"}]
}

В приведенных выше данных мы отправляем id объекта Link. Но в методе create для BookSerializer, определенного выше, данные, которые мы отправили, изменяются на:

{
    "title": "Book Title",
    "pages": [{
        "link":Link Object (2),#<= changed to the Link object with id 2
        "page_no":52, 
        "text":"sometext"}]
}

А PageSerializer фактически предназначен для получения pk значения link, т. Е. "link": 2 вместо "link":Link Object (2). Отсюда выдает ошибку:

{ "link": [ "Incorrect type. Expected pk value, received Link." ] }

Таким образом, обходной путь заключается в переопределении метода to_internal_value вложенного сериализатора для преобразования полученного объекта Link Object (2) в его значение pk.

Итак, ваш PageSerializer класс должен быть:

class PageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Page
        fields = ('link','text','page_no')

    def to_internal_value(self, data): 
        link_data = data.get("link")
        if isinstance(link_data, Link): #if object is received
            data["link"] = link_data.pk # change to its pk value
        obj = super(PageSerializer, self).to_internal_value(data)
        return obj

    def validate_text(self, value):
        #some validation is done here.

    def validate_link(self, value):
        #some validation is done here.

и родительский сериализатор:

class BookSerializer(serializers.ModelSerializer):
    pages = PageSerializer(many=True)
    class Meta:
        model = Book
        fields = ('title','pages')

    @transaction.atomic
    def create(self, validated_data):
        pages_data = validated_data.pop('pages')#pages data of a book
        book= self.Meta.model.objects.create(**validated_data)
        for page_data in pages_data:
            page = Page(book=book)
            page_serializer = PageSerializer(page, data = page_data)
            if page_serializer.is_valid(): #PageSerializer does the validation
                page_serializer.save()
            else:
                raise serializers.ValidationError(page_serializer.errors) #throws errors if any
        return book

Это должно позволить вложенному сериализатору выполнять проверку вместо записи проверки внутри метода create родительского сериализатора и нарушения DRY.

0 голосов
/ 16 сентября 2018

При вызове serializer.is_valid(raise_exception=True/False) автоматически вызывается функция проверки вложенного сериализатора.Когда вы вызываете serializer.save(**kwargs), сериализатор передает проверенные данные в ваши create(self, validated_data) или update(self, instance, validated_data) функции сериализатора.Более того, в проверенных данных ваши поля ForeignKey возвращали объект.

def create(self, validated_data):
    pages_data = validated_data.pop('pages') # [{'link': Linkobject, ...}]
    book= self.Meta.model.objects.create(**validated_data)
    for page_data in pages_data:
        page = Page(book=book)
        page_serializer = PageSerializer(page, data = page_data) # here you are sending object to validation again
        if page_serializer.is_valid():
            page_serializer.save()
        else:
            raise serializers.ValidationError(page_serializer.errors)
    return book
...