Django rest framework nested serializer with self

2019-07-09 08:52发布

问题:

I've tried a few solutions posted elsewhere for this problem but with no luck. It seems like it is not natively supported in DRF. Does anyone have suggestions on how to accomplish this?

I have a reports model and a section model. A section is defined as follows:

class Section(models.Model):
    title = models.CharField(max_length=255)
    report = models.ForeignKey(Report)
    order = models.PositiveIntegerField()
    section = models.ForeignKey('self', related_name='section_section', blank=True, null=True)
    content = models.TextField(blank=True)

I want to have it display data like so under reports:

[
    {
        "id": 1,
        "title": "test",
        "subtitle": "test",
        "section_set": [
            {
                "id": 1,
                "title": "test",
                "report": 1,
                "order": 1,
                "section_set": [
                    {
                        "id": 1,
                        "title": "test",
                        "report": 1,
                        "order": 1,
                        "section": null,
                        "content": "<p>test</p>"
                    },
                    {
                        "id": 2,
                        "title": "test",
                        "report": 1,
                        "order": 1,
                        "section": 2,
                        "content": "<p>test</p>"
                    },
                    {
                        "id": 3,
                        "title": "test",
                        "report": 1,
                        "order": 1,
                        "section": null,
                        "content": "<p>test</p>"
                    }
                ],
                "content": "<p>test</p>"
            },
            {
                "id": 2,
                "title": "test",
                "report": 1,
                "order": 1,
                "section": 2,
                "content": "<p>test</p>"
            },
            {
                "id": 3,
                "title": "test",
                "report": 1,
                "order": 1,
                "section": null,
                "content": "<p>test</p>"
            }
        ]
    }
]

My current (attempted) implementation looks like this:

class SubsectionSerializer(serializers.ModelSerializer):
class Meta:
    model = Section


class SectionSerializer(serializers.ModelSerializer):
    section = SubsectionSerializer()

    class Meta:
        model = Section
        fields = ('id', 'title', 'report', 'order', 'section', 'content')


class CountryReportSerializer(serializers.ModelSerializer):
    section_set = SectionSerializer(many=True)

    class Meta:
        model = CountryReport
        fields = ('id', 'title', 'subtitle', 'section_set')


class MapsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Map
        fields = ('id', 'country', 'map_image', 'report')

but the output looks like this:

{
    "id": 1,
    "title": "test",
    "subtitle": "test",
    "section_set": [
        {
            "id": 1,
            "title": "Section 1",
            "report": 1,
            "order": 1,
            "section": null,
            "content": "<p>test</p>"
        },
        {
            "id": 2,
            "title": "Section 2",
            "report": 1,
            "order": 1,
            "section": null,
            "content": "<p>test</p>"
        },
        {
            "id": 3,
            "title": "Subsection 1",
            "report": 1,
            "order": 1,
            "section": {
                "id": 1,
                "title": "Section 1",
                "order": 1,
                "content": "<p>test</p>",
                "report": 1,
                "section": null
            },
            "content": "<p>test</p>"
        }
    ]
}

回答1:

Got it working with the following solution:

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data


class SectionSerializer(serializers.ModelSerializer):
    children = RecursiveField(many=True)

    class Meta:
        model = Section
        fields = ('id', 'order', 'title', 'content', 'parent', 'children')


class CountryReportSerializer(serializers.ModelSerializer):
    section_set = serializers.SerializerMethodField('get_parent_sections')

    @staticmethod
    def get_parent_sections(self, obj):
        parent_sections = Section.objects.get(parent=None, pk=obj.pk)
        serializer = SectionSerializer(parent_sections)
        return serializer.data

    class Meta:
        model = CountryReport
        fields = ('id', 'title', 'subtitle', 'section_set')


class MapsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Map
        fields = ('id', 'country', 'map_image', 'report')


回答2:

The way you are defining subsection doesn't link it to your section field, just like the error suggests. Have you tried defining your serializer simply like this:

class SectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Section

Because Section has a FK to section, it should be returned as you would expect from the serializer.

To ensure that the JSON results returned by this serializer contain nested JSON objects, instead of only FKs, there are two routes you can take:

1), depth=

class SectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Section
        depth=2

This will follow FKs down, building JSON objects as it goes to the depth that you specify.

2) Define a SubSerializer to handle the JSON object creation:

class SubsectionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Section

class SectionSerializer(serializers.ModelSerializer):
    section = serializers.SubsectionSerializer()
    class Meta:
        model = Section
        fields = ('id', 'title', 'report', 'order', 'section', 'content')

------------------------EDIT---------------------------

For clarity, it might make sense to rename the section related bits of your model:

class Section(models.Model):
    title = models.CharField(max_length=255)
    report = models.ForeignKey(Report)
    order = models.PositiveIntegerField()
    parent_section = models.ForeignKey('self', related_name='child_sections', blank=True, null=True)
    content = models.TextField(blank=True)

With the new names, you should be able to use the following serializer:

class SectionSerializer(serializers.ModelSerializer):
    child_sections = serializers.SubsectionSerializer(many=True)
    class Meta:
        model = Section
        fields = ('id', 'title', 'report', 'order', 'child_sections', 'content')


回答3:

KISS method , RecursiveField serialize just return values

class RecursiveField(serializers.ModelSerializer): 
    def to_representation(self, value):
        serializer_data = TypeSerializer(value, context=context).data
        return serializer_data
    class Meta:
            model = Type
            fields = '__all__'

class TypeSerializer(serializers.ModelSerializer):
    extends_type = RecursiveField(allow_null=True)

    class Meta:
        model = Type
        fields = '__all__'

in model.py

class Type(models.Model):
    id = models.AutoField(primary_key=True)
    extends_type = models.ForeignKey('self', models.SET_NULL, null=True)

you can enter in loop easily, to avoid that , we can copy TypeSerializer to SubTypeSerializer and count or control deep with on key counter in context under

def to_representation(self, value):
    lvalue = context.get('count', 0)
    lvalue += 1 
    context.update({'count': lvalue})
    serializer_data = SubTypeSerializer(value, context=context).data
    return serializer_data

class TypeSerializer(serializers.ModelSerializer):
    extends_type = RecursiveField(allow_null=True)
    def to_representation(self, instance):
        self.context.update({'count': 0})
        return super().to_representation(instance)

    class Meta:
        model = Type
        fields = '__all__'

class SubTypeSerializer(serializers.ModelSerializer):
    extends_type = RecursiveField(allow_null=True)

    class Meta:
        model = Type
        fields = '__all__'