Django REST frameworkでAPIを作ってみた

# はじめに

Vue.js+Django REST frameworkを試す前段階として、まずDjango REST frameworkでAPIを実装します。

# 開発環境
```shell
$ python --version
Python 3.5.1

$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.2
BuildVersion:	18C54
```

# プロジェクトの作成

Djangoのプロジェクトを作成します。

```shell
# Djagoをインストール
pip install django

# プロジェクトを作成
django-admin startproject myproject

cd myproject

# 起動
python manage.py runserver
```

```shell:ディレクトリ構成
myproject
|_ myproject
|_ manage.py
|_ db.sqlite3
```

http://localhost:8000/ で画面が表示されているを確認します。

![screencapture-localhost-8000-2018-12-17-13_02_38.png](https://qiita-image-store.s3.amazonaws.com/0/324886/028d52cd-5e53-60cf-2610-d9209d804ecc.png)


# Django REST framework

## 設定

まずはDjango REST frameworkをインストールします。

```shell
pip install django-rest-framework
```

先程作ったプロジェクトのsettings.pyにrest_frameworkを追加します。

```python:myproject/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
]
...
```

次にAPIのエンドポイントを設定します。

```python:myproject/urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include, url
from rest_framework import routers

router = routers.DefaultRouter()

urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/', include(router.urls)),
]
```

ここでDjango REST frameworkが正しく機能しているか確認します。
http://localhost:8000/api で以下の画面が実行されればOKです。

![screencapture-localhost-8000-api-2018-12-17-13_02_29.png](https://qiita-image-store.s3.amazonaws.com/0/324886/d209bfb2-90fb-5737-9e75-a8eb4dcb483a.png)

## アプリケーションの追加

以下のdjango-adminのコマンドを使用してアプリケーションを作ります。

```shell
django-admin startapp myapp
```

```shell:ディレクトリ構成
myproject
|_ myproject
   |_ __init__.py
   |_ settings.py
   |_ urls.py
   |_ wsgi.py
|_ myapp
   |_ migrations
   |_ __init__.py
   |_ admin.py
   |_ apps.py
   |_ models.py
   |_ tests.py
   |_ views.py
|_ manage.py
|_ db.sqlite3

```

アプリケーションをsettings.pyに追加します。

```python:saicoro/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'myapp',
]
...
```
ここからはガリガリAPIを実装していきます。

## Models

```python:myapp/models.py
from django.db import models


class Spot(models.Model):
    # 名前
    name = models.CharField(max_length=50)
    # カテゴリー
    category = models.CharField(max_length=5, blank=True)
    # ジャンル
    genre = models.CharField(max_length=50, blank=True)
    # 都道府県
    address_prefecture = models.CharField(max_length=10, blank=True)
    # 市区町村
    address_city = models.CharField(max_length=10, blank=True)
    # 丁目番地等
    address_street = models.CharField(max_length=100, blank=True)
    # 緯度
    latitude = models.CharField(max_length=50, blank=True)
    # 経度
    longitude = models.CharField(max_length=50, blank=True)
    # 作成日
    created_at = models.DateTimeField(auto_now_add=True)
    # 更新日
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('created_at',)
```

以下のコマンドでマイグレーションします。（DBはとりあえずデフォルトのSQLiteのまま）

```shell
python manage.py makemigrations
python manage.py migrate
```

## Serializers

```python:myapp/serializers.py
from rest_framework import serializers
from .models import Spot


class SpotListSerializer(serializers.ModelSerializer):
    # 名前
    name = serializers.CharField(max_length=50)
    # カテゴリー
    category = serializers.CharField(max_length=5, allow_blank=True)
    # 都道府県
    address_prefecture = serializers.CharField(max_length=10, allow_blank=True)

    class Meta:
        model = Spot
        fields = ('id', 'name', 'category', 'address_prefecture')


class SpotSerializer(serializers.ModelSerializer):
    # 名前
    name = serializers.CharField(max_length=50)
    # カテゴリー
    category = serializers.CharField(max_length=5, allow_blank=True)
    # ジャンル
    genre = serializers.CharField(max_length=50, allow_blank=True)
    # 都道府県
    address_prefecture = serializers.CharField(max_length=10, allow_blank=True)
    # 市区町村
    address_city = serializers.CharField(max_length=10, allow_blank=True)
    # 丁目番地等
    address_street = serializers.CharField(max_length=100, allow_blank=True)
    # 緯度
    latitude = serializers.CharField(max_length=50, allow_blank=True)
    # 経度
    longitude = serializers.CharField(max_length=50, allow_blank=True)

    class Meta:
        model = Spot
        fields = (
            'name', 'category', 'genre', 'address_prefecture', 'address_city', 'address_street', 'latitude',
            'longitude')
```

## Renderer

Jsonとして取得したいので、その処理をrenderers.pyとしてまとめます。

```python:myapp/renderers.py
import json
from rest_framework.renderers import JSONRenderer


class SpotJSONRenderer(JSONRenderer):
    charset = 'utf-8'

    def render(self, data, accepted_media_type=None, renderer_context=None):
        return json.dumps({
            'spots': data
        })
```

## Views

```python:myapp/views.py
from rest_framework import status
from rest_framework.generics import ListAPIView, RetrieveAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response

from .models import Spot
from .renderers import SpotJSONRenderer
from .serializers import SpotListSerializer, SpotSerializer


class SpotListApiView(ListAPIView):
    model = Spot
    queryset = Spot.objects.all()
    permission_classes = (AllowAny, )
    renderer_classes = (SpotJSONRenderer, )
    serializer_class = SpotListSerializer


class SpotRetrieveApiView(RetrieveAPIView):
    permission_classes = (AllowAny, )
    renderer_classes = (SpotJSONRenderer, )
    serializer_class = SpotSerializer

    def retrieve(self, request, spot_id, *args, **kwargs):
        spot = Spot.objects.get(id=spot_id)
        serializer = self.serializer_class(spot)

        return Response(serializer.data, status=status.HTTP_200_OK)
```

## Urls

apiのエンドポイントを定義します。

```python:myapp/urls.py
from django.conf.urls import url
from .views import SpotListApiView, SpotRetrieveApiView

app_name = 'myapp'
urlpatterns = [
    url(r'^spots/$', SpotListApiView.as_view()),
    url(r'^spots/(?P<spot_id>\w+)/?$', SpotRetrieveApiView.as_view()),
]
```

myprojectのurls.pyを更新します。

```python:myproject/urls.py
from django.conf.urls import include, url
from django.contrib import admin
from django.urls import path
from rest_framework import routers
from spot import urls

router = routers.DefaultRouter()

urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/', include(router.urls)),
    url('api/', include(urls, namespace='spot')),
]
```

ここまでの実装を確認するために、データをとりあえず追加します。

```
pip install ipyhton
python manage.py shell

In [1]: from myapp.models import Spot

In [2]: Spot.objects.create(name="秋葉原UDX", category="オフィス", genre="" , address_prefecture="東京都", address_city="千代田区", address_street="外神田4丁目14-1", latitude="35.700689", longitude="139.772498")                                                  
Out[2]: <Spot: Spot object (1)>

In [3]: Spot.objects.create(name="東京タワー", category="観光地", genre="" , address_prefecture="東京都", address_city="港区", address_street="芝公園4丁目2-8", latitude="35.658816", longitude="139.745476")                                                        
Out[3]: <Spot: Spot object (2)>

In [4]: Spot.objects.create(name="東京スカイツリー", category="観光地", genre="" , address_prefecture="東京都", address_city="墨田区", address_street="押上1丁目1-2", latitude="35.710385", longitude="139.810743")                                                  
Out[4]: <Spot: Spot object (3)>

In [5]: quit()
```

ブラウザで http://localhost:8000/api/spots/2 を開くと結果が確認できます。

<img width="368" alt="スクリーンショット 2018-12-17 19.20.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/324886/0ea136e0-08b9-2a74-4eca-8d5085ea2724.png">

# 終わりに

とりあえずGETだけでしたが、簡単にAPIを実装できたと思います。
みんなDjangoでAPIを実装すべし！

## 参考資料
 [Django REST framework](https://www.django-rest-framework.org/)
 [Cute cats web app Django + VueJs— Create a REST API with DRF](https://medium.com/@jrmybrcf/how-to-build-a-project-with-django-vuejs-create-a-rest-api-endpoint-b57374a89661)