Djangoで画像を配信できるwebAPIを作る

# 概要

　この記事は初心者の自分がRESTful なAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっています。

前回の [Django Rest Framework で JWTによるAPIの認証機能を実装](https://qiita.com/Ajyarimochi/items/c6274c4aa8f21f573265) で認証機能を実装し、ある程度実用に耐えうるAPIとなりました。ただ現在のクーポンは文字情報だけでビジュアル面で弱いのが現状。

そこで今回はDjangoで画像ファイルを扱うための「Pillow」というパッケージを使い、クーポンの画像もAPIで配信出来るようにします。

Djangoで新しく画像を扱うAPIを作る場合も「Pillow」に関する部分は同じなので、参考になればと思います。

### 画像をwebAPIを介してクライアント側で表示する仕組み

1. webAPI側のサーバで画像を保持する
2. リクエストに対して画像のURLをJsonでレスポンスする
3. クライアントが再度サーバにアクセスして画像を取得し画面上に表示する



### 参考

* [はじめてのDjango (7) 画像データの管理やページへの表示，アップロードの方法などについて知ろう](https://qiita.com/j54854/items/1f0560142e39d888251c)
* [【Django】画像をアップロードして表示する](https://ymgsapo.com/2018/11/05/show-image-imagefield/)
* [Django本番環境でメディアファイル公開に必要な設定](https://sinyblog.com/django/media_file_001/)

### 環境

* Mac OS 10.15
* pipenv 2018.11.26
* Python 3.7.4
* Django 2.2.6
* pillow 7.0.0


# 手順
* pillowをインストールする
* モデルを定義する
* settings.pyにMEDIA_ROOTとMEDIA_URLを定義
* urls.pyに設定を追加する
* 必要に応じてpsettings.pyに設定を追加する
* マイグレートする
* 動作確認

### pillowをインストールする

　自分の環境は pipenv でpythonの仮想環境を作っているので pipenvのシェルに入った後、下記の通りpillowをインストールします。インストール後に Pipfileを見てpillowがインストールされた事を確認します。
 
```
$ pipenv install pillow
```

```
$ cat Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
django = "*"
djangorestframework = "*"
django-filter = "*"
djangorestframework-jwt = "*"
pillow = "*" ←追加されている

[requires]
python_version = "3.7"

```


pipやpip3で環境構築している場合は、

```
$ sudo pip install pillow
又は
$ sudo pip3 install pillow
```
でインストールします。


### モデルを定義する

　models.pyのCouponクラスに画像を格納するモデルフィールドを追加します。pillowをインストールすると画像ファイルを扱うフィールド型`ImageField`を使えるようになります。

　定義はこのようにします。`upload_to=`の部分は最低限必要です。

```
[フィールド名] = models.ImageField(upload_to=‘[画像ファイルを格納するフォルダの相対パス]’)
```


　「画像ファイルを格納するフォルダの相対パス」の起点は、settings.pyで定義する`MEDIA_ROOT`で指定したパスになります。


　下記の通り、imageというフィールド名でImageFieldを追加しました。
なお、私のケースではnullを許容`（null=True)`しないとマイグレーションファイルを作成する際にエラーになりました。

```models.py

class Coupon(models.Model):
    code = models.CharField(max_length=20)
    benefit = models.CharField(max_length=1000)
    explanation = models.CharField(max_length=2000)
    image = models.ImageField(upload_to='images/', null=True) #追加
    store = models.CharField(max_length=1000)
    start = models.DateField()
    deadline = models.DateField()
    status = models.BooleanField()

```


### settings.pyにMEDIA_ROOTとMEDIA_URLを定義

　前述の通り、プロジェクト名のフォルダ配下のsettings.pyに`MEDIA_ROOT`には画像ファイルを相対パスで参照する際の起点のURLを指定します。
　私の場合、画像ファイルを格納するフォルダをアプリのフォルダ配下に作りたかったので、アプリのフォルダのURLを指定しました。プロジェクトのフォルダまでは`BASE_DIR`で定義されているので、下記のように記載しました。

　画像ファイルを格納するフォルダは初回の画像アップロード時に自動生成されるそうですが、知らなかったので`mkdir images`で自分で作りました。

```ami_coupon_api/settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'coupon')

```



　`MEDIA_URL`はURLで参照する際に起点となるアドレスを指定します。
　私の場合、CouponモデルのデータをGETする際のアドレスが`[ip(ドメイン）]:[ポート]/api/coupons/`なので、イメージに参照する際は`[ip(ドメイン）]:[ポート]/api/coupons/images`となるように`/api/coupons/`を起点として設定しました。


```プロジェクト名のフォルダ/settings.py
MEDIA_URL = '/api/coupons/'

```




### urls.py に設定を追加する

　URLで画像を参照するには urls.py に設定が必要です。
　プロジェクト名のディレクトリ配下のurls.pyを開き、下記import文とurlpatternsの宣言を追加します。画像のような静的ファイルを扱うための設定で、ほとんど定型文のように扱えるようです。


```ami_coupon_api/urls.py
from django.conf import settings #画像参照のため追加
from django.contrib.staticfiles.urls import static #画像参照のため追加
from django.contrib.staticfiles.urls import staticfiles_urlpatterns #画像参照のため追加


urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

```


### 必要に応じてpsettings.pyに設定を追加する
　Django Rest Framework を使っていて serializer.py にレスポンスするモデルフィールドを設定している場合は、画像ファイルのURLをレスポンスするように設定を変更をします。

　下記の`fields = `の部分です。`’__all__’`の場合は全てのモデルフィールドの値をレスポンスする設定なので変更は不要です。

```serializer.py

from rest_framework import serializers
from .models import Coupon

class CouponSerializer(serializers.ModelSerializer):
    class Meta:
        model = Coupon
        fields = '__all__'
```



### マイグレートする
　モデルの定義を変更したので、DBに変更を反映するためにマイグレートをします。

　マイグレーションファイルを作成→マイグレートの流れになります。（pipenvを使っている場合は仮想環境のシェルに入っている状態で実行）

```
$ python manage.py makemigrations [アプリ名]
$ python manage.py migrate
```


### 動作確認
　以上で画像ファイルを扱うための設定は完了となりますので、djangoのadminページを使って直接画像を投入し、実際に表示されるか確認しました。

　djangoサーバを立ち上げdjangoのadminページにログインしてcouponモデルのページを開くと、画像をアップデートできるようになっています。クーポン用に作成した画像を直接アップデートします。
<img width="585" alt="edit_data.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/069ea88f-c022-9698-23da-f61624244d00.png">



　次にcurlコマンドでGETリクエストに対するレスポンスの json を取得します。

```
$ curl -X GET http://127.0.0.1:8000/api/coupons/
```

　下記の通り、画像ファイルのURLが含まれているのでコピーしてブラウザでアクセスします。


[{"id":1,"code":"0001","benefit":"お会計から1,000円割引","explanation":"5,000円以上ご利用のお客様限定。他クーポンとの併用不可。","image":"**http://127.0.0.1:8000/api/coupons/images/coupon-image-001_GKrT1ju.png**" ,"store":"全店","start":"2019-10-01","deadline":"2019-12-31","status":true}


　ブラウザに設定したクーポンの画像が表示されたら成功です。
<img width="350" alt="coupon_image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/d4449038-ec86-43fe-8db0-ed35341c60e1.png">