Python Django入門 (6)


スマホAPIを作る
--------------

ここまで、HTMLのレスポンスを返す、Webアプリケーションの例を見てきました。

次に、iOS/AndroidアプリのバックエンドとしてJSONを返す例を作ってみます。  
[Django REST framework](http://www.django-rest-framework.org/)のように、REST APIを作るモジュールを導入することもできますが、まずは自分で書いてみることにします。

### apiアプリケーションの作成

mybook プロジェクトの下に api というアプリケーションを作成します。

```bash
$ python manage.py startapp api
```

mybook プロジェクトのディレクトリの下に、以下のファイルが作成されました。

```bash
mybook/
    api/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py
```

### JSONを返すビュー

基本は、jsonモジュールを使って、Python辞書オブジェクトをJSONに変換します。

```python
import json

data = {'id': 1, 'name': 'hoge'}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
```

* ensure_ascii=False  # 日本語が含まれる時、このように指定。
* indent=2  # 空白2個で整形する場合。省略すると整形しない。

ただし、Python辞書は順不同になるため、JSONの中身も順不同となってしまいます。

そこで、collections.OrderedDict 順序付き辞書を使います。

> なお、OrderedDictが使えるのは、Python 2.7 からなので、サーバ環境によって Python 2.6 以下の場合は、[orderddict](https://pypi.python.org/pypi/ordereddict)を使います。

`api/views.py` は以下のようになります。

```python
import json
from collections import OrderedDict
from django.http import HttpResponse
from cms.models import Book


def render_json_response(request, data, status=None):
    """response を JSON で返却"""
    json_str = json.dumps(data, ensure_ascii=False, indent=2)
    callback = request.GET.get('callback')
    if not callback:
        callback = request.POST.get('callback')  # POSTでJSONPの場合
    if callback:
        json_str = "%s(%s)" % (callback, json_str)
        response = HttpResponse(json_str, content_type='application/javascript; charset=UTF-8', status=status)
    else:
        response = HttpResponse(json_str, content_type='application/json; charset=UTF-8', status=status)
    return response


def book_list(request):
    """書籍と感想のJSONを返す"""
    books = []
    for book in Book.objects.all().order_by('id'):
        
        impressions = []
        for impression in book.impressions.order_by('id'):
            impression_dict = OrderedDict([
                ('id', impression.id),
                ('comment', impression.comment),
            ])
            impressions.append(impression_dict)
        
        book_dict = OrderedDict([
            ('id', book.id),
            ('name', book.name),
            ('publisher', book.publisher),
            ('page', book.page),
            ('impressions', impressions)
        ])
        books.append(book_dict)
    
    data = OrderedDict([ ('books', books) ])
    return render_json_response(request, data)
```

render_json_response() はJSONP にも対応しています。 
スマホのAPIだけでなく、JavascriptのAjaxでJSONを取得する場合にも応用できます。 

### JSONを返すURL

`api/urls.py` というファイルを新規作成し、以下のように記述します。

```python
from django.urls import path
from api import views

app_name = 'api'
urlpatterns = [
    # 書籍
    path('v1/books/', views.book_list, name='book_list'),   # 一覧
]
```

この `api/urls.py` を、プロジェクトの `mybook/urls.py` で include します。

```python
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('cms/', include('cms.urls')),
    path('api/', include('api.urls')),  # ここを追加
    path('admin/', admin.site.urls),
]
```

### INSTALLED_APPS への追加

api アプリケーションをインストールしたことを、プロジェクトに教えてあげる必要があります。  
model も template もないので、なくても動きますが、一応やっておきましょう。  
`api/apps.py` を開いてみると、ApiConfig というクラスが定義されています。
これを `mybook/settings.py` の `INSTALLED_APPS` の最後に `'api.apps.ApiConfig',` という文字列で追加します。

```python
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'cms.apps.CmsConfig',   # cms アプリケーション
    'api.apps.ApiConfig',   # api アプリケーション
    'bootstrap4',           # django-bootstrap4
]
```

### 結果の確認

それでは、ブラウザで以下のURLを開いて下さい。

```bash
http://127.0.0.1:8000/api/v1/books/
```

結果は以下のようになります。

```
{
  "books": [
    {
      "id": 1,
      "name": "Django 入門",
      "publisher": "GeekLab.Nagano",
      "page": 100,
      "impressions": [
        {
          "id": 1,
          "comment": "あああ\r\nいいい"
        },
        {
          "id": 2,
          "comment": "ううう\r\nえええ"
        },
        {
          "id": 4,
          "comment": "コメント\r\nです。"
        }
      ]
    },
    {
      "id": 2,
      "name": "書籍２",
      "publisher": "GeekLab.Nagano",
      "page": 200,
      "impressions": []
    }
  ]
}
```

### iOS/Androidアプリからのデータの受信

iOS/Androidアプリに対してデータを送信する場合は JSON でよいですが、データを受信したい場合はどうするのでしょうか。

* 簡単なデータの場合は、Webアプリのフォーム受信と同じものを書いて、スマホ側では http form post を模倣してもらいます。
    - こうすることによって、データのエラーチェックは、フォームのバリデーションの仕組みが使えます。
    - 正常かエラーかは、JSONで結果を返すようにします。
* 繰り返しがある複雑なデータは、スマホ側からJSONをPOSTしてもらい、json.loads() でデコードします。

JSONで受信する場合、request の raw_post_data からJSON文字列を取り出せます。

```python
import json

def xxxx_post(request):
    python_obj = json.loads(request.raw_post_data)  # RequestのBodyが直接JSON文字列となるようPOSTしてもらう
      :
```

### スマホAPIのまとめ

スマホAPIだけを作るのであれば、データ投入は Django の管理サイトの機能を使い、API部分だけを書く、といったことも可能です。  
簡単にスマホのバックエンドを作成することができます。

なお、本格的にAPIを作成する場合は、[Django REST framework](http://www.django-rest-framework.org/)の導入も検討してください。

最後に
-----

ここまでで、この講座は終了です。

この講座の後に、公式チュートリアル その1〜7 をやってみることによって、さらに理解が深まると思います。

* [Django ドキュメント](https://docs.djangoproject.com/ja/3.0/)