Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

:pencil:日記REST_APIの使い方

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

GETは誰でも取得可能だが、POSTPUTDELETEは誰からでもアクセスさせるわけにはいかないので、アクセストークンをヘッダーに付ける。
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

: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

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

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)

作成した modelsettings.py に定義
$ vi django_rest_framework/settings.py

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

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

スクリーンショット 2017-04-16 午後9.26.57.png

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

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

:ear_of_rice:REST frameworkの導入

REST frameworkを定義

$ vi django_rest_framework/settings.py

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

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

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

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

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

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指定をしないまま見ると、文字化けしていると思います。
下記を直すにはクライアントに返すjsonUTF-8をこちらで指定しなければいけません。

スクリーンショット 2017-04-19 3.23.12.png

jsonをutf-8指定にする

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

diary/renderers.py
from rest_framework.renderers import JSONRenderer


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

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

django_rest_framework/settings.py
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'diary.renderers.UTF8CharsetJSONRenderer',
    )
}

文字化けが直った:muscle:

スクリーンショット 2017-04-19 3.21.25.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

django_rest_framework/settings.py
REST_FRAMEWORK = {
    # 追加
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 3,
    'DEFAULT_RENDERER_CLASSES': (
        'diary.renderers.UTF8CharsetJSONRenderer',
    )
}

取得するとjsonのルートにnextpreviousが追加され、前後のページを辿れる。
$ 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

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

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

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

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 ユーザーのusernamepasswordを入れてリクエスト

$ 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

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

下の方の文言通りに、 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

django_rest_framework/settings.py
...省略

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

...省略

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

スクリーンショット 2017-04-22 19.57.24.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

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

※上記の 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

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

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

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

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

調べる
$ 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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away