適当に作る python Djnago REST frameworkで作る初めてのAPI

#:beginner:日記APIを作成し、公開するところまでを初期の状態からやっていきます。 

完成品はこちらです。実際に僕が日記を運用しているAPIです。:joy:笑
http://www.kojimaiton-philosophy.com/api/diaries/

## :pencil:日記REST_APIの使い方

### CRUDで日記に必要な最低限の機能を提供します:rocket:

`GET`は誰でも取得可能だが、`POST``PUT``DELETE`は誰からでもアクセスさせるわけにはいかないので、アクセストークンをヘッダーに付ける。
`XXXXXX_XXXXXX` のところを取得したアクセストークンに置き換える。

#### GET

```
リストで日記を取得。 ページネーション、 フィルター、 リミット・オフセットも使用可能。
$ curl http://localhost/api/diaries/

日付を主キーにして、日付の日記一件を取得。
$ curl http://www.kojimaiton-philosophy.com/api/diaries/2016-12-19/
```

#### POST

```
日記に必要な内容を入れて、新規で作成する  ※日付を主キーにしているので、同じ日付は二回POSTできない。
$ curl -X POST http://www.kojimaiton-philosophy.com/api/diaries/ -d "date=2017-03-25" -d "title=日記のタイトル" -d "body=日記の内容" -d "publishing=true" -H "Authorization: JWT XXXXXX_XXXXXX"`
```

#### PUT

```
更新したい日付をパスに指定して、変更したいデータに書き換えて更新する。
$ curl -X PUT http://www.kojimaiton-philosophy.com/api/diaries/2017-03-25/ -d "date=2017-03-25" -d "title=日記のタイトルの変更" -d "body=日記の変更" -d "publishing=false" -H "Authorization: JWT XXXXXX_XXXXXX"
```

#### DELETE

```
削除したい日付をパスに指定して、データを削除。
$ curl -X DELETE http://www.kojimaiton-philosophy.com/api/diaries/2017-03-25/ -H "Authorization: JWT XXXXXX_XXXXXX"
```

## :earth_asia: 環境

CentOS: 7.3.1
Python: 3.6.0
Django: 1.10.6
Restframework: 3.6.2
MySQL: 5.7.17
Nginx: 1.11.13

## :chestnut:設定

### 基本設定

```
yumをアップデート
$ sudo yum update -y
gitをインストール
$ sudo yum install git -y
必要なライブラリを入れておく
$ sudo yum install zlib-devel bzip2 bzip2-devel readline-devel openssl-devel sqlite3 sqlite-devel gcc -y
```

### ポート関連の設定

```
練習のためにfirewalldを切っておく
$ systemctl stop firewalld
firewalld自動起動停止
$ systemctl disable firewalld
SELinixの確認
$ getenforce
もし Enforcingの場合は、 以下で編集しSELINUX=enforcingをSELINUX=disabledに書き換える。
$ sudo vi /etc/selinux/config
書き換えた場合は設定を反映させるためにマシンを再起動
$ sudo shutdown -r now
Disabledであることを確認する
$ getenforce
```

### pyenvのインストール

```
$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ exec $SHELL -l
確認
$ pyenv -v
```

### pythonのインストール

```
バーションを確認
$ pyenv install -l
今回は 3.6.0 インストール ※インストールが終わるのにかなり時間が掛かる
$ pyenv install 3.6.0
確認
$ pyenv versions
デフォルトを変更
$ pyenv global 3.6.0
確認
$ pyenv versions
```

### Djangoと使用するライブラリのインストール

```
Djangoのインストール(python3.6からはpipがデフォルトで使用可能)
$ pip install django
REST_Frame_Workのインストール
$ pip install djangorestframework
フィルタリング
$ pip install django-filter
JWTによるアクセストークン
$ pip install djangorestframework-jwt
Djangoでmysqlを塩う
$ pip install PyMySQL
Djangoを本番サーバーで実行
$ pip install uwsgi
```

## :seedling:Djangoプロジェクトの作成

```
プロジェクトを作成
$ django-admin startproject django_rest_framework
$ cd django_rest_framework
ホスト名を修正。自分で使っているIP・ドメインを入れる事。
$ sudo vi django_rest_framework/settings.py
ALLOWED_HOSTS = ['160.16.65.138', 'localhost', '127.0.0.1',]
manage.pyファイルがあるディレクトでサーバーを起動し、ブラウザで接続を確認する。 http://ドメイン名:8000 で確認できる。 サーバーの終了はCtrl+C
$ python manage.py runserver 0.0.0.0:8000
サーバー起動中はsettings.pyのdebugモードがデフォルトでtrueになっているので、ファイルを編集すれば、即サーバーに反映される。
```
ブラウザから接続を確認
![スクリーンショット 2017-04-16 午後7.16.13.png](https://qiita-image-store.s3.amazonaws.com/0/33553/e212386a-f5ee-303c-0c3f-e54ccc14bfce.png)

## :white_sun_rain_cloud:DBの設定

### CentOS側のDBの設定
SQLiteを使えば手軽だが、ここではしっかりと汎用的に使えるMySQLを採用します。(設定が長いですが:joy:)

```
先ほど、実行してできてしまったdb.sqlite3を削除する。　
$ ls
db.sqlite3  diary  manage.py
$ rm db.sqlite3
centos7からはmariaDBがデフォルト入っているので、削除
$ sudo yum remove mariadb-libs -y
別バージョンのmysqlを使用していた場合、そのデータが残っている可能性があるため削除
$ rm -rf /var/lib/mysql/
MySQLのリポジトリを追加
$ sudo yum -y install http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
インストール可能な内容を確認
$ yum repolist enabled | grep "mysql.*-community.*"
infoコマンドで情報を確認
$ yum info mysql-community-server
インストール
$ sudo yum -y install mysql-community-server
バージョン確認
$ mysql --version
自動起動
$ sudo chkconfig mysqld on
起動
$ sudo systemctl start mysqld
デフォルトのパスワードの確認
$ sudo vi /var/log/mysqld.log
log ファイルから以下の仮のパスワードを見つけ出す。 今回、自分のパスワードは (E,irsThV0uB のようだ
2017-04-01T20:05:05.561190Z 1 [Note] A temporary password is generated for root@localhost: N4IYvEp&)6%!
以下を実行するとパスワードを求められるので、上記で見つけ出したパスードを入力する
$ mysql -u root -p
仮パスワードを変更しないと、一切の処理を受け付けてくれないので変更する。 ただしこのパスワードは8文字以上、英字、数字、記号をそれぞれ含めないといけないと受け付けてくれないので、注意。
$ ALTER USER root@localhost IDENTIFIED BY '@Mysql0001';
設定されたら、mysqlを一旦exit
$ exit;
以下のファイルに一行加えて日本語設定をする。 [mysqld]の下に、character-set-server=utf8 を追加。
$ sudo vi /etc/my.cnf
記述はこんな感じ↓
[mysqld]
character-set-server=utf8
設定を反映
$ sudo systemctl restart mysqld
Djangoで使用するためのDBを作成する
$ mysql -u root -p
ここではdiarydbという名前にする。
$ create database diarydb;
作成したら終了
$ exit
```

### Django側のDB設定

settings.pyを編集
`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py
import os
# pymysqlをimportする
import pymysql

# mysqlを使用するように変更
pymysql.install_as_MySQLdb()
DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.mysql',
       # データベース名
       'NAME': 'diarydb',
       # ユーザー名
       'USER': 'root',
       # パスワード
       'PASSWORD': '@Mysql0001',
       # サーバのIPアドレスやホストを。空欄はローカルホスト
        'HOST': '',
       # ポート
       'PORT': '3306',
       'OPTIONS': {
           # 制約を厳しくチェック
           'sql_mode': 'traditional',
       },
       # テスト用ユーザー
        'TEST_NAME': 'auto_tests',
   }
}
```

DjangoとMySQLが接続できるか確認

```
migrationファイルを作る
$ python manage.py makemigrations
migrationファイルを元にDBに反映する
$ python manage.py migrate
```
DBの設定が終了...長い:joy:

## :herb:Djangoプロジェクトを編集

日記appを作成

```
$ cd django_rest_framework
$ python manage.py startapp diary
```

現状のディレクトリ構成はコチラ。大枠ここに少しづつファイルを足す感じです:muscle:

```
└── django_rest_framework
    ├── diary
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   └── __init__.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    ├── django_rest_framework
    │   ├── __init__.py
    │   ├── __pycache__
    │   │   ├── __init__.cpython-36.pyc
    │   │   ├── settings.cpython-36.pyc
    │   │   ├── urls.cpython-36.pyc
    │   │   └── wsgi.cpython-36.pyc
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── manage.py
```

### モデルを定義

モデルはDBと一対一になる。
models.py を作成
`$ vi diary/models.py`

```py3:diary/models.py
# coding: utf-8
from django.db import models
from datetime import date

class Diary(models.Model):
    date = models.DateField(default=date.today, primary_key=True)
    title = models.CharField(max_length=128)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    publishing = models.BooleanField(default=True)
```

作成した `model` を `settings.py` に定義
`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # diaryを追加
    'diary',
]
```

もう一度DBをマイグレーション

```
$ python manage.py makemigrations
$ python manage.py migrate
```

DjangoのDBの管理画面に入る

```
admin用のユーザー作成
$ python manage.py createsuperuser
サーバーを起動。
$ python manage.py runserver 0.0.0.0:8000
```

管理画面にアクセス http://自分のIP・ドメイン:8000/admin/

![スクリーンショット 2017-04-16 午後9.29.46.png](https://qiita-image-store.s3.amazonaws.com/0/33553/33683e0f-b8e7-94ce-024a-0598370d0246.png)


先ほど `createsuperuser` で作成した `Username` と `Password` を入力して管理画面に入る。
GUIをポチポチして、モデルを編集したり、追加したりできます。

![スクリーンショット 2017-04-16 午後9.26.57.png](https://qiita-image-store.s3.amazonaws.com/0/33553/4df198db-6967-6e8d-26ff-d50bcb1b0c99.png)

ここに先ほど作成したモデルもGUIから操作できるように加えます。
`$ vi diary/admin.py`

```py3:diary/admin.py
# coding: utf-8
from django.contrib import admin
from .models import Diary


@admin.register(Diary)
class Diary(admin.ModelAdmin):
    pass
```

管理画面に `DIARY` が加わっています:muscle:
APIで使用するために、ここで日記をいくつか作って置きましょう。
Diarys -> Add diary -> SAVE

![スクリーンショット 2017-04-16 午後10.20.37.png](https://qiita-image-store.s3.amazonaws.com/0/33553/7c4efdf6-e4f6-1abc-40c0-4ffd2e83344a.png)

## :ear_of_rice:REST frameworkの導入

### REST frameworkを定義

`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # diaryを追加
    'diary',
    # frameworkを追加
    'rest_framework',
]
```

### Serializerを定義

APIへの出力対象のフィールドは Serializerクラス内で制限する
`$ vi diary/serializer.py`

```py3:diary/serializer.py
# coding: utf-8
from rest_framework import serializers
from .models import Diary


class DiarySerializer(serializers.ModelSerializer):
    class Meta:
        model = Diary
        fields = ('date', 'title', 'body', 'publishing',)
```

### ViewSetを定義

ViewSetはコントローラー
`$ vi diary/views.py`

```py3:diary/views.py
# coding: utf-8
from rest_framework import viewsets
from .models import Diary
from .serializer import DiarySerializer


class DiaryViewSet(viewsets.ModelViewSet):
    queryset = Diary.objects.all()
    serializer_class = DiarySerializer
```
### Urlsを定義

UrlsはURLのルーター設定

作成した`DiaryViewSet` を登録する
`$ vi diary/urls.py`

```py3:diary/urls.py
# coding: utf-8
from rest_framework import routers
from .views import DiaryViewSet

router = routers.DefaultRouter()
router.register(r'diaries', DiaryViewSet)
```
外部からアクセスされるルーティーングの設定
`$ vi django_rest_framework/urls.py`

```py3:django_rest_framework/urls.py
# coding: utf-8
from django.conf.urls import url, include
from django.contrib import admin
from diary.urls import router as diary_router

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include(diary_router.urls)),
]
```
### 実行して確認してみる

サーバーを立ち上げる
`$ python manage.py runserver 0.0.0.0:8000`
ブラウザで接続
`http://自分のIP・ドメイン:8000/api/diaries/`

![スクリーンショット 2017-04-18 4.19.31.png](https://qiita-image-store.s3.amazonaws.com/0/33553/01317a39-41ec-e0c8-87ab-9572354a915d.png)

curlで叩くと、`json`が返ってきている。

```
$ curl http://160.16.65.138:8000/api/diaries/
[{"date":"2017-04-15","title":"タイトルテスト","body":"中身テスト","publishing":true},{"date":"2017-04-16","title":"タイトル","body":"中身！","publishing":true}]
```
 
ここで注意点ですが、上記の写真の`GET`横のボタンでプルダウンからjsonで見ると文字化けします。:scream:
Chromeのバージョンによっては文字化けしてないかもしれませんが、それは自動で`UTF-8`に変換してくれているからです。
Safariなどで`UTF-8`指定をしないまま見ると、文字化けしていると思います。
下記を直すにはクライアントに返す`json`に`UTF-8`をこちらで指定しなければいけません。

![スクリーンショット 2017-04-19 3.23.12.png](https://qiita-image-store.s3.amazonaws.com/0/33553/4d81afa7-856d-1644-9868-8b4fdb0d30aa.png)


### jsonをutf-8指定にする

`REST Framework` のドキュメントにはデフォルトでUTF-8と書いてあるが:joy:
`$ vi diary/renderers.py`
`UTF-8`を指定した`UTF8CharsetJSONRenderer`クラスの作成
 デフォルトの`rest_framework.renderers.JSONRenderer` で`UTF-8`になっていそうだが、出来なかった。。。

```py3:diary/renderers.py
from rest_framework.renderers import JSONRenderer


class UTF8CharsetJSONRenderer(JSONRenderer):
    charset = 'utf-8'
```

`settings.py`に以下を追記
`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'diary.renderers.UTF8CharsetJSONRenderer',
    )
}
```
文字化けが直った:muscle:

![スクリーンショット 2017-04-19 3.21.25.png](https://qiita-image-store.s3.amazonaws.com/0/33553/a74e004b-7442-f64e-e03d-a6716e708081.png)

## :palm_tree:CURDの確認

IPの所は自分のに変更してリクエストする事:point_up:

### POST(Create)

```
新規作成
$ curl -X POST http://160.16.65.138:8000/api/diaries/ -d "date=2017-04-22" -d "title=日記のタイトル" -d "body=POSTのテスト" -d "publishing=true"
```

### PUT(Update)

```
更新
$ curl -X PUT http://160.16.65.138:8000/api/diaries/2017-04-22/ -d "date=2017-04-22" -d "title=日記のタイトルの変更" -d "body=PUTのテスト" -d "publishing=false"
```

### GET(Read)

```
リストの取得
$ curl http://160.16.65.138:8000/api/diaries/
日付で取得
$ curl http://160.16.65.138:8000/api/diaries/2017-04-22/
```

### DELETE(Delete)

```
削除
$ curl -X DELETE http://160.16.65.138:8000/api/diaries/2017-04-22/
```

## :date:ページネーション

ページネーションを追加
`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py
REST_FRAMEWORK = {
    # 追加
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 3,
    'DEFAULT_RENDERER_CLASSES': (
        'diary.renderers.UTF8CharsetJSONRenderer',
    )
}
```
取得するとjsonのルートに`next`と`previous`が追加され、前後のページを辿れる。
`$ curl http://160.16.65.138:8000/api/diaries/`

```
{
    count: 4,
    next: "http://160.16.65.138:8000/api/diaries/?limit=3&offset=3",
    previous: null,
    results: [
        {
            date: "2017-04-15",
            title: "タイトルテスト",
            body: "中身テスト",
            publishing: true
        },
        {
            date: "2017-04-16",
            title: "タイトル",
            body: "中身！",
            publishing: true
        },
        {
            date: "2017-04-21",
            title: "hoge",
            body: "ssss",
            publishing: true
        }
    ]
}
```

## :spider_web:フィルター

`views.py` を編集
`$ vi diary/views.py`

```py3:diary/views.py
# coding: utf-8
from rest_framework import viewsets
from .models import Diary
from .serializer import DiarySerializer


class DiaryViewSet(viewsets.ModelViewSet):
    queryset = Diary.objects.all()
    serializer_class = DiarySerializer
    # フィルタを追加
    filter_fields = ('publishing',)
```

`settings.py`を編集
`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py
REST_FRAMEWORK = {
    # フィルター追加
    'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
    # ページネーション追加
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 3,
    'DEFAULT_RENDERER_CLASSES': (
        'diary.renderers.UTF8CharsetJSONRenderer',
    )
}
```

公開フラグを`false`のものだけフィルタしてみる `?publing=false` をリクエストに付ける

`$ curl http://160.16.65.138:8000/api/diaries/?publing=false`

```
{
    count: 1,
    next: null,
    previous: null,
    results: [
        {
            date: "2017-04-22",
            title: "hogehoge",
            body: "hoge",
            publishing: false
        }
    ]
}
```

## :boy_tone1:認証

### 認証方法は色々あるが今回はJWT(Json Web Token)を使用

`settings.py`を編集
`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py
REST_FRAMEWORK = {
    # JWTの認証追加
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
    'NON_FIELD_ERRORS_KEY': 'detail',
    'TEST_REQUEST_DEFAULT_FORMAT': 'json',
    # フィルター追加
    'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
    # ページネーション追加
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 3,
    'DEFAULT_RENDERER_CLASSES': (
        'diary.renderers.UTF8CharsetJSONRenderer',
    )
}

# JWTの認証追加
JWT_AUTH = {
    # トークンの期限をここでは無効にしてみる
    'JWT_VERIFY_EXPIRATION': False,
}
```

`urls.py` を編集
`$ vi django_rest_framework/urls.py`

```py3:django_rest_framework/urls.py
# coding: utf-8
from django.conf.urls import url, include
from django.contrib import admin
from diary.urls import router as diary_router
# 認証を追加
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include(diary_router.urls)),
    # 認証を追加
    url(r'^api-auth/', obtain_jwt_token),
]
```

### JWTのアクセストークンの取得

`createsuperuser`で作成した`admin` ユーザーの`username`と`password`を入れてリクエスト

```
$ curl http://160.16.65.138:8000/api-auth/ -d "username=XXXXXXX&password=XXXXXXXX"
```

成功した場合レスポンスに`token`が入ってくる。

```
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJlbWFpbCI6Im11dS5rb2ppbWEudGVzdEBnbWFpbC5jb20iLCJ1c2VybmFtZSI6ImRqYW5nbyIsImV4cCI6MTQ5Mjg1NTMxMH0.m07BcTiAkA79HZ0BC8BsgYOA-SbqmC5GMN5g_QBizZw"}
```
ユーザー名やパスワードが違うと以下のように失敗する

```
{"detail":["Unable to login with provided credentials."]}
```


`views.py` を編集し、認証を掛ける
`$ vi diary/views.py`

```py3:diary/views.py
# coding: utf-8
from rest_framework import viewsets
from .models import Diary
from .serializer import DiarySerializer
# 認証追加
from rest_framework import permissions


class DiaryViewSet(viewsets.ModelViewSet):
    queryset = Diary.objects.all()
    serializer_class = DiarySerializer
    # フィルタを追加
    filter_fields = ('publishing',)
    # 認証追加 CURD全てに認証掛けたい場合は (permissions.IsAuthenticated,)にする
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
```

### 認証が掛かっているか試す

新規作成

`$ curl -X POST http://160.16.65.138:8000/api/diaries/ -d "date=2017-04-21" -d "title=認証" -d "body=認証のテスト" -d "publishing=true"`

アクセストークンがないので以下のように失敗すればOK!

```
{"detail":"Authentication credentials were not provided."}
```

取得したアクセストークンを付けてPOSTする ※アクセストークンは自分のに変えること

```
$ curl -X POST http://160.16.65.138:8000/api/diaries/ -d "date=2017-04-10" -d "title=認証" -d "body=認証のテスト" -d "publishing=true" -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJlbWFpbCI6Im11dS5rb2ppbWEudGVzdEBnbWFpbC5jb20iLCJ1c2VybmFtZSI6ImRqYW5nbyIsImV4cCI6MTQ5Mjg1NTMxMH0.m07BcTiAkA79HZ0BC8BsgYOA-SbqmC5GMN5g_QBizZw"
```

作成できたら、`GET`以外全てに認証が掛かっているかを確認する:fist:
※ 参考`permissions.IsAuthenticatedOrReadOnly` なので、　`OrReadOnly` を取れば、`GET` にも認証が掛かる。

### 404ページを通常のに修正。

現状ルートにアクセスすると、Djangoのデフォルトでは以下のようになる
http://160.16.65.138:8000/

![スクリーンショット 2017-04-22 19.51.16.png](https://qiita-image-store.s3.amazonaws.com/0/33553/43db3a7b-f7d3-a2f5-0bbe-1076ed4ff345.png)

下の方の文言通りに、 `settings.py`を編集し、`DEBUG = False` に設定する ※ `DEBUG = False`にするとサーバーを起動しながら、自動でファイルの変更を反映する事ができないので注意
`You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.`

`$ vi django_rest_framework/settings.py`

```py3:django_rest_framework/settings.py

...省略

# DEBUG = Falseにすると通常の404ページになる
DEBUG = False

...省略

```

アクセスすると通常の404ページになった！

![スクリーンショット 2017-04-22 19.57.24.png](https://qiita-image-store.s3.amazonaws.com/0/33553/bb169c66-4986-99c3-2714-8c156cc7afde.png)


## :robot:テストケース

```
tests.pyを削除
$ rm diary/tests.py
テスト用ディレクトリ作成。ここにテストを入れる
$ mkdir diary/tests
pythonのディレクトリと認識されるように`__init__.py` を追加。 特に何も記述はしない。
$ vi diary/tests/__init__.py
```

認証とCRUDのテストをするための `test_diary.py` を作成

`$ vi diary/tests/test_diary.py`

```py3:diary/tests/test_diary.py
# coding: utf-8
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework_jwt.compat import get_user_model
from rest_framework_jwt.settings import api_settings


class DiaryTest(APITestCase):
    def setUp(self):
        # アクセストークンの発行
        User = get_user_model()
        self.username = 'test_user'
        self.email = 'test_user@gmail.com'
        self.user = User.objects.create_user(self.username, self.email)
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        payload = jwt_payload_handler(self.user)
        token = jwt_encode_handler(payload)
        self.auth = 'JWT {0}'.format(token)
        self.url = '/api/diaries/'

    def test_diary_api(self):
        # POST
        data = {
            "date": "2011-11-11",
            "title": "title",
            "body": "body",
            "publishing": True,
        }
        response = self.client.post(self.url, data, HTTP_AUTHORIZATION=self.auth)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data, data)

        # GET
        expected_get_data = {
            "count": 1,
            "next": None,
            "previous": None,
            "results": [
                {
                    "date": "2011-11-11",
                    "title": "title",
                    "body": "body",
                    "publishing": True,
                }
            ]
        }
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, expected_get_data)

        # PUT
        data2 = {
            "date": "2010-10-10",
            "title": "title",
            "body": "body",
            "publishing": False,
        }
        response = self.client.put(self.url + '2011-11-11/', data2, HTTP_AUTHORIZATION=self.auth)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, data2)

        # DELETE
        response = self.client.delete(self.url + '2010-10-10/', HTTP_AUTHORIZATION=self.auth)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
```

### テストを実行

以下を実行すると、 `tests` の中の test_xxxx.pyが実行される。
`$ ./manage.py test`

テストが通った!

```

[xxxxx@tk2-208-13884 django_rest_framework]$ ./manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.049s

OK
Destroying test database for alias 'default'...

```

## :mailbox_with_mail:リバースプロキシの設定(APIのエンドポイントの:8000を消す)

### nginxのインストール

リポジトリを追加するので `nginx.repo` のファイルを作成
`$ sudo vi /etc/yum.repos.d/nginx.repo`

```nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1
```

### インストールと起動

```
nginxをインストール
$ sudo yum install nginx -y
バージョンの確認
$ nginx -v
自動起動設定
$ sudo systemctl enable nginx
起動
$ sudo systemctl start nginx
```

#### デフォルトページの確認

表示の確認をする。 http://自分のサーバーのIP/
以下は自分の例。
`http://160.16.65.138/`

![スクリーンショット 2017-04-22 20.47.25.png](https://qiita-image-store.s3.amazonaws.com/0/33553/1428bcf8-defb-675a-0911-d5207c34fdd8.png)


※上記の `index.html` のデフォルトのパスは `/usr/share/nginx/html/index.html`

### リバースプロキシを設定する

/etc/nginx/conf.d 以下に XXX.confという命名でファイルを作成する
ココでは server.conf とし、80 番で受けたリクエストを 8000 番に転送する
`$ sudo vi /etc/nginx/conf.d/server.conf`

```server.conf
server {
     listen 80;
     # アクセス可能なIPアドレス、もしくはドメイン。以下は自分の例
     server_name 160.16.65.138;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $http_host;
     proxy_redirect off;
     proxy_max_temp_file_size 0;
     location / {
        # 転送先
        proxy_pass http://localhost:8000;
     }:joy:
}
```

nginx再起動させ設定を反映させる

```
$ sudo systemctl restart nginx
``` 
実行すると、ブラウザからはデフォルトHTTPの`80`でアクセスできる用になるので、 `8000`が不要になる
`python manage.py runserver 0.0.0.0:8000`

![スクリーンショット 2017-04-22 21.09.22.png](https://qiita-image-store.s3.amazonaws.com/0/33553/3c991411-e95b-3952-be63-dd001b890013.png)


## :camping:Djangoをデーモンで起動し、本番環境で実行する

サーバーから`exit`してもバックグラウンドで動き続けるようにしてみる。

```
ルートにパスワードを設定
$ sudo passwd root
ルートになる
$ su
本番環境のデーモンでDjangoを実行
$ uwsgi --http :8000 --module django_rest_framework.wsgi --daemonize /var/log/uwsgi-django.log
```

以上で、バックグラウンドで動いているはず！
バックグラウンドで動いている`uwsgi`の止め方は動かしてるサーバーの`PID`を`kill`する。

調べる
`$ netstat -ntlp`

`uwsgi` の横に表示されているが、`PID`なので、
この場合は `kill -9 32215` でサーバーが停止する

```
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      32181/nginx: master
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      861/sshd
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      32215/uwsgi
tcp        0      0 127.0.0.1:43047         0.0.0.0:*               LISTEN      32215/uwsgi
tcp6       0      0 :::22                   :::*                    LISTEN      861/sshd
tcp6       0      0 :::3306                 :::*                    LISTEN      19382/mysqld
```

`uwsgi` の読み方はウィスキーらしい？ 
"上杉"と読んでいた:joy:

##:cat: まとめ

#### Githubに完成品をあげて置きます:bulb:
https://github.com/MuuKojima/django_rest_framework

#### 実際に僕が日記を運用しているAPIです。:joy:笑
http://www.kojimaiton-philosophy.com/api/diaries/
拡張しつつ、生涯掛けて運用していきます:muscle:
こちらはAWSで運用しています！

## 参考
http://qiita.com/redamoon/items/eabaacabb5b1a0c34ca3
http://qiita.com/seizans/items/05a909960820dd92a80a
http://racchai.hatenablog.com/entry/2016/05/08/070000
http://var.blog.jp/archives/70125676.html