Python
Django
django-rest-framework

Django REST Frameworkで再帰的なリレーションを持つモデルを返すAPIを作成したい

More than 1 year has passed since last update.

この記事のゴール

再帰的なリレーションを持つモデル(木構造など)の場合を考える。
DjangoRESTFrameworkを用いて再帰的にjsonを返すAPIを作成する。

環境

MacOS(10.11.6 / ElCapitane)
Python 3.5.2
Django 1.10.4
DjangoRESTFramework 3.5.3

Model

再帰的なリレーションを持つモデルを定義します。カテゴリの中に、更にサブカテゴリを持つモデルを考えます。逆リレーション名はsubcategoriesとします。

models.py
class Category(models.Model):
    parentCategory = models.ForeignKey('self', blank=True, null=True, related_name='subcategories')
    name = models.CharField(max_length=200)
    description = models.CharField(max_length=500)

Serializer

ModelSerializerを継承したSubCategorySerializerを定義し、Categoryの逆リレーションであるsubcategoriesをSubCategorySerializerで上書きします。このときparentCategoryはserializers.PrimaryKeyRelatedField()を返すようにします。

serializers.py
class SubCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('name', 'description')

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()
    subcategories = serializers.SubCategorySerializer()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

View

ViewでSerializerにCategorySerializerを指定してあげればOKです。クエリセットは一番上の階層を一意に取得できるようなクエリにしましょう。get()だとオブジェクトを取得してしまうので、クエリを返すfilter()などにしましょう。(例えば、is_rootをモデルに定義しておいて、filter(is_root=True)とかで取得する)

view.py
class CategoryListViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.filter(id='e.g...root_category_id').prefetch_related('subcategories__subcategories')
    serializer_class = CategorySeriarizer

別の方法

次のようなRecursiveFieldを定義して返してあげることでも実現可能です。こちらも融通が効くのでよいかなと思います。

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

class CategorySerializer(serializers.Serializer):
    subcategories = RecursiveField(many=True)

    class Meta:
        model = Comment
        fields = ('parendCategory','name', 'description', 'subcategories')

結果

上記の例とは別モデルなのですが、単純な木構造かつ再帰的なリレーションを持つファイルシステムモデルだとこんなjsonが返ってきます。

hoge.json
[
    {
        "name": "RootDirectory",
        "is_root": true,
        "is_dir": true,
        "parent_file": null,
        "child_files": [
            {
                "name": "root_file",
                "is_root": false,
                "is_dir": false,
                "parent_file": 34,
                "child_files": []
            },
            {
                "name": "TestDir",
                "is_root": false,
                "is_dir": true,
                "parent_file": 34,
                "child_files": [
                    {
                        "name": "test.zip",
                        "is_root": false,
                        "is_dir": false,
                        "parent_file": 35,
                        "child_files": []
                    },
                    {
                        "name": "test_dir_file.png",
                        "is_root": false,
                        "is_dir": false,
                        "parent_file": 35,
                        "child_files": []
                    }
                ]
            }
        ]
    }
]

みんなもDjangoで楽してREST API作りましょう!!

参考

http://www.django-rest-framework.org/api-guide/relations/#custom-relational-fields
http://stackoverflow.com/questions/13376894/django-rest-framework-nested-self-referential-objects