SEIYU風のECサイトを作りましょう（3）Django REST frameworkで爆速APIを作りましょう

# 前回までの記事
1.[Django REST framework + Vue.js「SEIYU風のECサイトを作りましょう」全6回（予定）](https://qiita.com/Syoitu/items/62e6ec1437d98d6b9d28)
2.[SEIYU風のECサイトを作りましょう（1）要求分析とプロジェクト初期化](https://qiita.com/Syoitu/items/eae447432c6135ff1174)
3.[SEIYU風のECサイトを作りましょう（2）Xadminを使って管理画面を一新します](https://qiita.com/Syoitu/items/11fac037759220b30cd2)

# 前書き
![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/c0acf730-fe19-9e89-e9d5-6492e483a966.png)
今回の記事は*Django REST framework*を使用して、APIを作っていきます。
テスト用のデータが必要なため、**商品(goods)**と**カテゴリ(GoodsCategory)**のデータを予め用意します。
管理画面から手入力でもできますが、本記事のリポジトリにある`db_tools`フォルダをコピーして使用することをオススメします。
[本記事リポジトリ](https://github.com/huanshenyi/qiita-Django-supermarket)

#### 使い方
`qiita-Django-supermarket/api/db_tools/`配下にある、以下の二つのファイルを実行して下さい。
そうすればテストデータがDBに追加されます。
**import_category_data.py**
**import_goods_data.py**


# 商品(goods)API
まずは**Serializers**を作ります。
Djangoのformsとして考えていいです。

```shell
cd apps/goods
vim serializer.py
```

```python:apps/goods/serializer.py
from rest_framework import serializers
from .models import Goods


class GoodsSerializer(serializers.ModelSerializer):
    class Meta:
        model = Goods
        fields = "__all__"
```
次は**goods**の**views.py**を編集します。

```python:apps/goods/views.py
from rest_framework import mixins
from rest_framework import viewsets

from .models import Goods
from .serializer import GoodsSerializer


class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
     queryset = Goods.objects.all()
     serializer_class = GoodsSerializer
```
最後は**urls.py**に以下を追加します。

```python:api/urls.py
...
from django.urls import path, re_path, include
from rest_framework.routers import DefaultRouter
from goods.views import GoodsListViewSet
...
router = DefaultRouter()
router.register(r'goods', GoodsListViewSet)
...

urlpatterns = [
    ...
    re_path('^', include(router.urls)),
    ...
]
```

以上の三点を追加すれば、商品リストのAPIは出来上がりです。
以下コマンドを実行し、サーバーを再起動します。

```
python manage.py runserver
```
`http://127.0.0.1:8000/`にアクセスし、正しくデータが取得できたことを確認します。
<img width="799" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/e1905df4-1776-e5ba-9d3a-f51df07b980f.png">
<img width="777" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/cc6ae027-e4d5-1b26-525f-9ecdfd8d18af.png">

データあるだけではAPIとして不親切なので、オプションをつけていきます。

### ページング

*Django REST framework*にページングを追加する方法は以下の二つがあります。

- **setting.py**にグローバル配置する
- 各**views.py**にローカルなページングを追加する

今回はローカルに追加する方法を使用します。
goodsの**views.py**に以下を追加します。

```python:apps/goods/views.py

...
from rest_framework.pagination import PageNumberPagination
...
class GoodsSetPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100
...
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
     ....
     pagination_class = GoodsSetPagination
```
以上の内容追加後、再度`http://127.0.0.1:8000/goods/`にアクセスします。
ページングが確認できたらオーケーです。
<img width="849" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/3b41a149-14bf-6e11-32ca-1356da6c3dbc.png">

[公式ドキュメント-ページング](https://www.django-rest-framework.org/api-guide/pagination/#pagination)

### フィルタ
商品に関するフィルタ、よくあるのは以下の3つです。

- 値段順に並び替え
- 値段区間内検索
- キーワード検索

それら全部フロントでやれますが、勉強のため、バックエンドで実装します。
先ずは**settings.py**に以下を追加します。

```python:api/settings.py
INSTALLED_APPS = [
    ...
    'django_filters'
]

```

次は**views.py**に以下を追加します。

```python:apps/goods/views.py
...
from django_filters.rest_framework import DjangoFilterBackend
...
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
     ...
     filter_backends = (DjangoFilterBackend,)
     filterset_fields = ('name', 'shop_price')
     ... 
```
以上の二点完成後、`http://127.0.0.1:8000/goods/`にアクセスします。
フィルタという項目が追加されたことが確認できます。
<img width="795" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/1d6b0b33-d3cf-9b91-00cf-1ebfdb39f7d6.png">
商品名を入力すれば、フィルタが機能してることが確認できます
<img width="606" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/09b18ade-f769-0f1e-0ac7-0d724e6122f7.png">
<img width="780" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/bad5286b-5211-99a7-c549-2014125c0611.png">
今のフィルタは商品名が完全一致でなければ表示されません。
親切とは言えませんので、もう少し工夫して部分一致を可能にします。

goodsの配下に**filters.py**ファイルを作ります。

```python:apps/goods/filters.py
import django_filters
from .models import Goods


class GoodsFilter(django_filters.rest_framework.FilterSet):
    """
    goodsのfilter
    """
    price_min = django_filters.NumberFilter(field_name="shop_price", lookup_expr='gte')
    price_max = django_filters.NumberFilter(field_name="shop_price", lookup_expr='lte')

    class Meta:
        model = Goods
        fields = ['price_min', 'price_max']
```
**views.py**に追加とコメントアウトを行います。

```python:apps/goods/views.py
...
from rest_framework import filters
from .filters import GoodsFilter
...
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
     ...
     # filterset_fields = ('name', 'shop_price')
     filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
     filterset_class = GoodsFilter
     search_fields = ['name', 'goods_brief']
     ordering_fields = ['shop_price']
```
`http://127.0.0.1:8000/goods/`にアクセスします。

<img width="793" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/0fdd377f-2053-43ab-db00-9b7cab4e36cb.png">


- 値段順に並び替え
- 値段区間内検索
- キーワード検索

以上3つの商品に関するフィルタが完成しました。

### 外部キーの処理
Goodsモデルには**category**という外部キーが存在します。
今のAPIでは数字のIDとして表示してますが、参照先の詳細を表示できるようにしましょう。
<img width="435" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/6f11747a-ed89-1968-4afe-7d608535617a.png">

goodsの**serializer.py**を修正します。

```python:apps/goods/serializer.py
...
from .models import GoodsCategory
...
class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = GoodsCategory
        fields = "__all__"
...
class GoodsSerializer(serializers.ModelSerializer):
    category = CategorySerializer()
    ...
```
修正後、カテゴリの詳細も表示できるようになりました。

<img width="703" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/44dcb135-bf73-473b-defd-2180b2ab468b.png">

# カテゴリ(category )API
カテゴリの内容は商品ほど多くはないですが、自身を外部キーとして扱うカテゴリの階級が存在します。
それを実装しましょう。

```python:apps/goods/serializer.py
class CategorySerializer3(serializers.ModelSerializer):
    class Meta:
        model = GoodsCategory
        fields = "__all__"


class CategorySerializer2(serializers.ModelSerializer):
    sub_cat = CategorySerializer3(many=True)

    class Meta:
        model = GoodsCategory
        fields = "__all__"

class CategorySerializer(serializers.ModelSerializer):
    sub_cat = CategorySerializer2(many=True)
    ...
```
次は**views.py**に以下を追加します。

```python:apps/goods/views.py
...
from .models import GoodsCategory
from .serializer import CategorySerializer
...
class CategoryViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    queryset = GoodsCategory.objects.filter(category_type=1)
    serializer_class = CategorySerializer

```
**urls.py**に以下を追加します。

```python:api/urls.py
...
from goods.views import CategoryViewSet
...
router.register(r'categorys', CategoryViewSet, base_name="categorys")
...
```
以上の修正できたら、`http://127.0.0.1:8000/`にアクセスして確認します。
階層分けされたデータが確認できるはずです。
<img width="803" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/30ee7731-701f-84f8-7fcf-a99ff965a4a5.png">

<img width="788" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/bc8a0a75-581c-df19-8ac3-f87db8cc22e4.png">

以上でカテゴリAPIが完成です。

# APIドキュメント

もしバックエンドとフロントエンドを二人で担当する場合。
フロントエンドエンジニアが作業をしやすくするために、
バックエンドエンジニアはAPIのドキュメントを作る必要があります。
*Django REST framework*にはドキュメント自動生成する機能は備えてあります。
それを機能を有効化しましょう。

**setting.py**に以下を追加します。

```python:api/settings.py

...
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'
}
...
```
ドキュメント用のルートを追加します。
**urls.py**に以下を追加します。

```python:api/urls.py

...
from rest_framework.documentation import include_docs_urls
...

urlpatterns = [
    ...
    path('docs/', include_docs_urls(title="shop")),
]
```
`http://127.0.0.1:8000/docs/`にアクセスし、ドキュメントの画面を確認します。
<img width="899" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/5fd221f8-366c-6f35-a9e4-300cce43d7c9.png">
以下のように空白のカラムがある場合には、ヘルプテキストを追加する必要があります。
<img width="475" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/0e17a9cf-b2ce-898c-4248-2d51f184fde8.png">
空白のカラムに**help_text**を追加してください。

```python:apps/goods/filters.py

price_max = django_filters.NumberFilter(field_name="shop_price", lookup_expr='lte', help_text="最大額")
```
<img width="503" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/acc97e00-8b44-e365-4bf5-ef103012dca7.png">

以上、APIの制作は一旦完了です。
新規登録、ログインなどのAPIは**Vue.js**プロジェクトの制作と共に作っていきます。
最後まで読んで頂きありがとうございます。

#次回予告
**Vue.js**プロジェクトの初期化