"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

Django REST Framework (DRF)のAPIView・api_viewを使ったWebApiプロキシ/python・django

DjangoのREST Framework (以後、DRF)のClassベースビュー「APIView」と、FunctionベースViewの「api_view」を使い、別のWebApiを処理して結果を返す「WebApiプロキシ」を2通りの方法で作って比較してみます。

f:id:arakan_no_boku:20190607195913j:plain

 

まずはDRFについて

 

Django REST Framework (DRF)はDjangoAPIを開発するフレームワークです。

DRFDjangoの違いのひとつめは、レスポンスの返し方です。

Djangoだと、HTTPResponseでしたが、DRFはResponseにで返します。

もうひとつはViewです。

DRFDjangoと同様にViewを返しますが、HTMLではなく、JSON等を返します。

DRFのViewは以下の観点で大きく2つにわかれます。

  • 特定のモデル(DB・クエリセット)に紐づく
  • 特定のモデル(DB・クエリセット)に紐づかない

です。 

 

特定のモデル(DB・クエリセット)に紐づくビュー

 

Generic viewとViewSetsです。

どちらも 作成, 一覧, 取得, 削除, 更新 などのお作法に従って使う場合、可能な限り簡略化して書けるようになっています。

Serializer というデータ の入出力を扱い、モデルへの橋渡しをする「DjangoのFormのAPI版みたいな」クラスでと関連づけるだけで、処理を書かなくても、ちゃんと機能するAPIが書けたりするので、とても「FrameWork」っぽいViewです。

Generic viewはやりたい処理に対応する多数のクラスの総称みたいな感じで、ViewSetsは、 取得, 一覧, 登録, 更新, 削除 などのよくあるCRUD操作をまとめて扱えるViewです。

ModelViewSetsを継承すると、GenericViewと同様にSerializer を定義するだけで、 取得, 一覧, 登録, 更新, 削除ができるAPIが書けるので、間違いなく便利ですね。

それに、ネットでDRFの情報を探すと、大体ViewSetsを使う例がでてくるので、サンプルにもこまらないのかなと思います。

Generic view

www.django-rest-framework.org

ViewSets

www.django-rest-framework.org

 

特定のモデル(DB・クエリセット)に紐づかないビュー

 

ところが・・。

今回のWebApiプロキシみたいにDBを使わない(モデルを使わない)場合には、GenericViewもViewSetsも使えません。

その場合は「APIView」か「api_view」を使います。 

www.django-rest-framework.org

APIViewが「クラスベースのView」

api_viewが「関数型のView」です。

今回は、この両方を使って同じ処理を書いて、比較もあわせてやってみます。

 

RESTful APIについて

 

ちなみに、DRFの「REST」は「REpresentational State Transfer」の略です。

HTTPメソッドでアクセスしてデータの送受信を行うのを、RESTなWebサービスと呼ぶ的な感じで使われてます。

RESTの設計原則がRESTfulで、おおよそ、以下の4つの原則に従うのをRESTful なWebサービスと呼ぶようです。

  • アドレス指定が可能なURLで公開されている
  • インターフェースがHTTPメソッドの利用に統一されている
  • リソースをHTMLやテキスト、XMLJSONなど多様な形式で表現できる
  • ステートレスな通信

もう少し詳しい話は、このあたりの記事がわかりやすいと思ったので、リンク貼っておきます。

qiita.com

 

さてWebApiプロキシを作ります

 

APIは、郵便番号取得APIを使います。

zipcloud.ibsnet.co.jp

前回でインストールした環境と作業用フォルダに作ります。

arakan-pgm-ai.hatenablog.com

 

関数型ビューは「views.py」に書きます

 

まずは、ソースコードです。

rest/views.py

import requests
from rest_framework.decorators import api_view
from rest_framework.response import Response


@api_view(['GET', 'POST'])
def get_address_from_zip(request):
    zipcode = request.GET.get(key="zipcode", default="1000011")
    resp = requests.get(
        'http://zipcloud.ibsnet.co.jp/api/search?zipcode=' +
        str(zipcode) +
        '&limit=1')
    return Response(resp.json(), status=resp.status_code)
 

補足します。

アノテーション 

@api_view(['GET', 'POST'])

がついて、Responseで返している以外は、普通のメソッドです。

間違いやすいとしたら、 

Response(resp.json())

ではなくて

 Response(resp.json(), status=resp.status_code)

のように、statusを引き継いでいる部分ですかね。

前者でも何となく動くんですけど、元のAPIのStatusが隠れてしまったりするので後者の方がいいみたいです。

 

クラスベースのViewは任意のクラスで作成します

 

今回は「 AddressFromZip」クラスを「addressFromZip.py」として作ります。

まずは、ソースコードから。

rest/address_from_zip.py

import requests
from rest_framework.views import APIView
from rest_framework.response import Response


class AddressFromZip(APIView):
    def get(self, request):
        zipcode = request.GET.get(key="zipcode", default="1000011")
        resp = requests.get(
            'http://zipcloud.ibsnet.co.jp/api/search?zipcode=' +
            str(zipcode) +
            '&limit=1')
        return Response(resp.json(), status=resp.status_code)

補足します。 

クラスベースのViewは「APIView」を継承して、メソッドの「get」「post」をオーバーライドする形になります。

上記例では、getだけオーバーライドしてますけど、getとpostの両方をオーバーライドしてもかまいません。

この名前を任意のものに変えると、動きませんので要注意です。

 

 urls.pyでパスを設定する

 

WebApiプロキシといっても、一応利用する側から見たらAPIですから、URLのネーミングについては、一般的な規則にそってつけることにします。

この辺が参考になります。

cloud.google.com

qiita.com

さて、ソースです。

rest/urls.py 

from django.contrib import admin
from django.urls import path
from . import views
from . import adress_from_zip as zip

urlpatterns = [
    path(
        'admin/',
        admin.site.urls),
    path(
        'api/v1/addresses/zip/get',
        zip.AddressFromZip.as_view(),
        name='cls-v'),
    path(
        'api/v1/addresses/zip',
        views.get_address_from_zip,
        name='fnc-v'),
]

補足します。 

URLの構成は、「api/v1/addresses/zip」を基本にしています。

「v1」がバーション。

「addresses」は取得するのが住所(アドレス)なので、その複数形。

「zip」はzipコードから取得するという意味。

原則、名詞しか使わないという基準に基づいて決めてます。

 

クラスベースの場合

 

以下のように指定してます。

'api/v1/addresses/zip/get',
adressFromZip.AddressFromZip.as_view(),
name='cls-v'),

クラスベースの場合、get・postのオーバーライドした方をURLの最後につけて、クラス名.as_view() で定義します。

as_view()によって、URLのgetにマッピングされる・・という仕組みたいですね。

 

関数ベースの場合

 

以下のように指定しています。

'api/v1/addresses/zip',
views.getAddressFromZip,
name='fnc-v'),

Djangoの普通のViewの指定に似ています。

関数名を渡して、任意のURLとマッピングしているだけですから。

 

さて試してみます

 

プロジェクトフォルダをカレントフォルダにして、djangoをインストールした仮想環境をactiveにします。

それでお馴染み「python manage.py runserver」します。

郵便番号はパラメータで渡すので、こんな感じ。

ちなみに郵便番号は「1000013」にしてみました。

クラスベースの場合

http://localhost:8000/api/v1/addresses/zip/get?zipcode=1000013

関数ベースの場合

http://localhost:8000/api/v1/addresses/zip?zipcode=1000013

 

URLをたたいてみると

f:id:arakan_no_boku:20190615175823p:plain

ちゃんと、とれてます。

 

JavaScriptからコールしてみる

 

Nuxt.js自己流チュートリアルの環境で、プラグインとして作ってみます。

プラグインの作り方その他は、こちらの記事と同じです。

arakan-pgm-ai.hatenablog.com

この記事では、あえて郵便番号の分を書いてないので、それを作ります。

とりあえず、ソースコードです。

getOneAdressFromZip.js

import Vue from 'vue'
import axios from 'axios'

Vue.prototype.$getOneAdressFromZip = (zipCode) =>{
    let url = `http://localhost:8000/api/v1/addresses/zip/get?zipcode=${zipCode}`
    return axios.get(url)
    .then((res) => {
        console.log(res.data)
        return res.data
  })
}

axiosに渡すURLが、さっき定義したものになってます。

実行するときは、Nuxt自己流チュートリアルの作業フォルダをカレントにして。

npm run dev

ですね。

djangoとNuxt(Node.js)と2つのプロンプトが立ち上がっている状態で、チュートリアルを実行します。

f:id:arakan_no_boku:20190615181508p:plain

さて、住所所得してみます。

テスト専用でないブラウザなので、CORS対策がうまくいってないと、Networkエラーになるはずですが。

f:id:arakan_no_boku:20190615181638p:plain

ちゃんと取得できました。

OKみたいです。

今回は、こんなところで・・。

ではでは。