この記事のゴール
再帰的なリレーションを持つモデル(木構造など)の場合を考える。
DjangoRESTFrameworkを用いて再帰的にjsonを返すAPIを作成する。
環境
MacOS(10.11.6 / ElCapitane)
Python 3.5.2
Django 1.10.4
DjangoRESTFramework 3.5.3
Model
再帰的なリレーションを持つモデルを定義します。カテゴリの中に、更にサブカテゴリを持つモデルを考えます。逆リレーション名はsubcategoriesとします。
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()を返すようにします。
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)とかで取得する)
class CategoryListViewSet(viewsets.ModelViewSet):
queryset = Category.objects.filter(id='e.g...root_category_id').prefetch_related('subcategories__subcategories')
serializer_class = CategorySeriarizer
別の方法
次のようなRecursiveFieldを定義して返してあげることでも実現可能です。こちらも融通が効くのでよいかなと思います。
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が返ってきます。
[
{
"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