Blog の
前提
以下の
OS
% sw_vers ProductName: Mac OS X ProductVersion: 10.13.3 BuildVersion: 17D47
Python の
version % python3 -V Python 3.6.2
Django の
version python3 -m pip list | grep Django Django (1.11.11)
Djagno rest framework の
version python3 -m pip list | grep rest djangorestframework (3.6.4)
参考
API で 取得する Model の リレーションに ついて
リレーションは
Blog Entry と、
Model のEntryPageRelated
、EntryAbstract
に
作成したAPI
以下、
rest_framework.py
from puput.models import EntryPage from puput.models import EntryPageRelated from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework import serializers, viewsets from rest_framework.response import Response from rest_framework.viewsets import ReadOnlyModelViewSet from django.http import Http404 class EntryPageSerializer(serializers.ModelSerializer): class Meta: model = EntryPage fields = ('id','title', 'slug', 'gist_id') # Serializers define the API representation. class EntryPageRelatedSerializer(serializers.ModelSerializer): entrypage_from = serializers.SlugRelatedField( many=False, read_only=True, slug_field='gist_id') entrypage_to = EntryPageSerializer(many=False, read_only=True) class Meta: model = EntryPageRelated fields = ('entrypage_from','entrypage_to') # ViewSets class EntryPageRelatedViewSet(ReadOnlyModelViewSet): serializer_class = EntryPageRelatedSerializer permission_classes = [IsAuthenticatedOrReadOnly] def get_queryset(self): gist_id = self.kwargs['pk'] queryset = EntryPageRelated.objects.all() if gist_id: entry_page = EntryPage.objects.filter(gist_id=gist_id).first() queryset = queryset.filter(entrypage_from=entry_page) return queryset """ Retrieve a model instance. """ def retrieve(self, request, *args, **kwargs): queryset = self.get_queryset() if not queryset: raise Http404("Not Found..") serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
説明
プログラムに
EntryPageSerializer
これがNest した Serializer に なります。 EntryPageRelatedSerializer
これは、API で 使用する Serializer です。
内部で、フィールド、 entrypage_to は、 EntryPageSerializer
を参照しています。
フィールド、entrypage_from は、 特に Model の フィールドを 必要としなかったため、 SlugRelatedField
でgist_id のみを 取得するようにしました。 EntryPageRelatedViewSet
API となるViewSet です。 get_queryset URLの
一部と して 渡される gist_id で 結果を 絞り込みたかったので、 get_queryset
内で、パラメータ pk を 取得し、 その 結果の 有無で 全件取得するか 絞り込みするかを 切り替えています。 retrieve
pk での絞り込みした データ取得の ための、 retrieve
メソッドをオーバーライドしました。
おそらく正しい 使い方ではなく、 本来の 実装で、 get_queryset
の結果が、 予期せず 更に 絞り込まれてしまい、 結果が 0 件に なっていたので、 止むを 得ず オーバーライドしました。 おそらくもっと いいやり方が あるのかと 思います。
実はこの ケースは、 ReadOnlyModelViewSet
を使用すべきではないのかもしれません。
OutPut
APIを
これは
[ { "entrypage_from": "3459f9cd97ac22b201336931759dc4d9", "entrypage_to": { "id": 67, "title": "初台で花見をした", "slug": "acbc4d1aa0a28f781d180b87a8e8fb3c", "gist_id": "acbc4d1aa0a28f781d180b87a8e8fb3c" } }, { "entrypage_from": "3459f9cd97ac22b201336931759dc4d9", "entrypage_to": { "id": 45, "title": "all.json を一次加工後に、手作業で変換したRedPen の辞書ファイル", "slug": "3df15ae935eb394972f9bdd2f87d43a2", "gist_id": "3df15ae935eb394972f9bdd2f87d43a2" } }, { "entrypage_from": "3459f9cd97ac22b201336931759dc4d9", "entrypage_to": { "id": 63, "title": "GIthub Repository フォーク後、PullRequest作成までに実行するコマンドのメモ書き", "slug": "e6a04e124e1771e3e92f863ebbd27229", "gist_id": "e6a04e124e1771e3e92f863ebbd27229" } }, { "entrypage_from": "3459f9cd97ac22b201336931759dc4d9", "entrypage_to": { "id": 46, "title": "all.json から RedPen の SuggestExpression ルールの辞書ファイルを作成する", "slug": "4faae2b829480531826ff6bfa4745d9e", "gist_id": "4faae2b829480531826ff6bfa4745d9e" } }, { "entrypage_from": "3459f9cd97ac22b201336931759dc4d9", "entrypage_to": { "id": 41, "title": "scikit-surprise の 入力ファイル向けに、spreadsheet の データを変換するスクリプト", "slug": "47dcc7f47262a5972286b1b0fd624910", "gist_id": "47dcc7f47262a5972286b1b0fd624910" } } ]
発生した トラブルに ついて
Serializer に 対する many 属性の 指定
エラー内容
'Model' object is not iterable
対処
Nest したSerializer で、 object is not iterable
エラーが発生していました。
Serializer には、many 属性を 指定できますが、 この 指定を していなかったため、 エラーが 発生していました。
私のケースでは、 False
の指定が 必要で、 デフォルトだと、 True
で動作するのかと 思います。
entrypage_to = EntryPageSerializer(many=False, read_only=True)
pkキーワードが 含まれていない
エラー内容
AssertionError: Expected view EntryPageRelatedViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.
対処
<pk >
キーワードを追加した。 url(r'^related/(?P<pk>.+)/$', entry_page_related, name='entry_page_related'),
補足
<pk >
キーワードは、ViewSet の lookup_field
の設定で 変更する ことができます。 lookup_field = 'my_pk'
ViewSet に querysetフィールドが 定義されていない
エラー内容
assert queryset is not None, '`base_name` argument not specified, and could ' \ AssertionError: `base_name` argument not specified, and could not automatically determine the name from the viewset, as it does not have a `.queryset` attribute.
対処
Router でurl を 定義する 際、 base_name の 指定が ない 場合は、 queryset を 元に basename を 決定する 動作に なるようです。
ViewSet には、get_queryset ファンクションを 定義していて、 queryset を 指定していなかったので、 router.register で base_name を 指定するようにしました。 # ex) router.register(r'snippets', views.SnippetViewSet, "snippests")
以上です。
コメント