(勉強会用メモ) REST APIs with Django : Chapter 2-3

勉強会

ちょっと業務でRESTfulなAPIを作成する必要が出てきました。

3/27 (水) に再度勉強会開くのでぜひ。

connpass.com

3/14の内容復習編。

Djangoプロジェクトの作成

Python環境にはpipenvを使用します。

# 仮想環境の作成(python 3.6)
$ pipenv install --python 3.6
# 作成した仮想環境の起動
$ pipenv shell 

startprojectを使用すると、適切なディレクトリ構成でプロジェクトの鋳型を作成してくれます。

$ django-admin startproject library_project

作成したばかりは以下のような構成になっています。

$ tree library_project/
library_project/
├── library_project
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
  • __init__.py
    Pythonでは、ディレクトリをパッケージの一部として使用する際にこのファイルが存在する必要があります。

  • settings.py
    全ての設定に関する項目を指定します。

  • urls.py
    トップレベルのURLに関するルーティングを定義します。

  • wsgi.py
    WebサーバーとWebアプリケーションをつなぐ共通のインターフェースに関する設定ファイルです
    本番環境にデプロイする際にはいじる必要があります。

  • manage.py
    Django関連のコマンドはこのスクリプトを介して実行します。webサーバーの立ち上げなど。

migrate

初期データベースの作成には、migrate コマンドを使用します。
Djangoはデフォルトでsqlite3を使用します。

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK

http://127.0.0.1:8000 に接続すると。。

f:id:kimoppy126:20190320182945p:plain

Djangoのデフォルト画面が広がっています。

アプリケーションの作成

Djangoプロジェクトは複数のアプリケーションを持つことができます。
startapp コマンドを使用すると、またしても適切なディレクトリ構成でアプリケーションの鋳型を作成してくれます。

$ django-admin startapp library_project

アプリケーションを1つ持ったプロジェクトは、以下のような構成になっています。

$ tree
.
├── books
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── db.sqlite3
├── library_project
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
  • admin.py
    管理画面に関する設定ファイルです。

  • app.py
    アプリケーションに関する設定ファイルです。

  • migrations/
    データベースの変更に使用するSQL文はpython manage.py makemigrationsコマンドにより、この中に格納されます。

  • models.py
    データベースのモデルを定義するファイルです。

  • tests.py
    アプリケーションのテストに使用するモジュールを格納します。

  • views.py
    request/responseのロジックに関わる部分について記述します。

これらとは別に、アプリケーションごとにurls.pyを作成し、各アプリでのルーティングを定義することが多いです。

INSTALLED_APPS での注意点

djangoでは、settings.py中のINSTALLED_APPS という環境変数にクラスを追加していくことでアプリケーションを認識させます。

INSTALLED_APPS = [

    # local
    'books.apps.BookConfig',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

When several applications provide different versions of the same resource (template, static file, management command, translation), the application listed first in INSTALLED_APPS has precedence. https://docs.djangoproject.com/en/2.1/ref/settings/#installed-apps

djangoではINSTALLED_APPSに指定したアプリケーションは最初にリストされるアプリケーションのリソースが優先されます。

そのため、独自アプリケーションに関する設定は、デフォルトで定義された管理画面に関する設定の上に記述することに注意します。

model名の登録

models.py で定義したクラスに __str__メソッドを定義することで、管理サイトで表示するデータベース中のカラム名が指定できます。

class Book(models.Model):
    title = models.CharField(max_length=250)
    subtitle = models.CharField(max_length=250)
    author = models.CharField(max_length=100)
    isbn = models.CharField(max_length=13)
    def __str__(self):
        return self.title

migration

python manage.py makemigrations でSQL文の発行、python manage.py migrateで発行したSQL分の適用を行います。

$ python manage.py makemigrations
Migrations for 'books':
  books/migrations/0001_initial.py
    - Create model Book
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, books, contenttypes, sessions
Running migrations:
  Applying books.0001_initial... OK

Admin

admin.pyadmin.site.register(Book)を追記することで、管理画面からBookモデルを管理できるように変更できます。

from django.contrib import admin
from.models import Book

# Register your models here.
admin.site.register(Book)

f:id:kimoppy126:20190320185103p:plain

管理画面では、モデルの編集を行うことができます。

Views

Djangoが提供している汎用Viewを継承するのが楽です。
汎用Viewを使用する場合、以下のようにそのViewを適用するtemplate及びmodelを適切な変数名で定義します。

class BookListView(ListView):
    model = Book
    template_name = 'book_list.html'

URLs

ルートからbooks.urlsにルーティングされるように設定します。

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('books.urls')),
]

include 関数を使用し、アプリケーションごとの urls.pyを読み込むように設定しています。
books.urlsでは、ルートからBookListViewにルーティングされるように設定します。

urlpatterns = [
    path('', BookListView.as_view(), name='home'),
]

こうすることで、ルートにアクセスしたリクエストとBookListView が対応することになります。

template

Djangoのtemplateでは、jinja2テンプレートエンジンを使用することができます。

<h1>All books</h1>
{% for book in object_list %}
  <ul>
    <li>Title: {{ book.title }}</li>
    <li>Subtitle: {{ book.subtitle }}</li>
    <li>Author: {{ book.author }}</li>
    <li>ISBN: {{ book.isbn }}</li>
  </ul>
{% endfor %}

Django REST frameworkの導入

Django REST frameworkを導入するには、以下の手順を踏む必要があります。 REST framework用に作成するアプリケーションは通常のアプリケーションと異なりデータベースを持たないため、
モデルを定義する必要も、migrationファイル(SQL文が書かれたファイル)を作成する必要も、データベースを更新する必要もありません。

  1. djangorestframeworkのインストール
  2. INSTALLED_APPSrest_framework を追記
  3. Serializer、View、ルーティングの定義

1. djangorestframework のインストール

pipで入れることができます。

$ pipenv install djangorestframework==3.8.1

2. INSTALLED_APPSrest_framework を追記

INSTALLED_APPS'rest_framework'を追加します。

INSTALLED_APPS = [
    'rest_framework',
]

3. ルーティング、Serializer、View、の定義

APIの場合もstartappコマンドを使うと便利です。

 $ python manage.py startapp api

URLs

api/というルーティングを新たに追加します。

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('books.urls')),
    path('api/', include('api.urls')), # new
]

Serializer

APIエンドポイントで扱いやすいJSONやXMLに変換するためのモノです。
最低限のSerializerであれば、以下のように作成することができます。

class BookSerializer(serializer.ModelSerializer):
    class Meta:
        model = Book
        fields = ('title', 'subtitle', 'author', 'isbn')

Views

ViewはAPI用の汎用Viewが提供されているため、それを使うと楽です。

class BookAPIView(generics.ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

APIを提供しているURL(今回だとhttp://localhost:8000/api/)にアクセスすると、以下のようなそれっぽいAPIコンソールが出現します。
f:id:kimoppy126:20190320185657p:plain

CORS (Cross-Origin Resource Sharing) について

異なるドメイン名(例:mysite.com vs yoursite.com)や
異なるポート(例:localhost:3000 vs localhost:8000)とクライアントがやり取りを行う際、セキュリティー的に問題が生じます。
詳細は以下のサイトがわかりやすかったです。

qiita.com

django-cors-headersを使えば、CORS(Cross-Origin Resource Sharing)ヘッダーをレスポンスに追加することで、クロスサイトHTTPリクエストを行うことが許可されているオリジンホスト名を制限することができます。

django-cors-headersを使用するには、以下の手順を踏む必要があります。

  1. django-cors-headersのインストール
  2. INSTALLED_APPS 及びMIDDLEWAREに追記
  3. CORS_ORIGIN_WHITELISTにリクエストを制限するホスト名を記述

1. django-cors-headersのインストール

pipで入れることができます。

$ pip install django-cors-headers

2. INSTALLED_APPS 及びMIDDLEWAREに追記

以下の設定をsettings.pyに追記します。

INSTALLED_APPS = [
    'corsheaders'
]
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
 ]

3. CORS_ORIGIN_WHITELISTにリクエストを制限するホスト名を記述

例えばクロスサイトHTTPリクエストをlocalhost:3000のみに制限する場合は、以下のようにCORS_ORIGIN_WHITELIST環境変数に定義します。

# new
CORS_ORIGIN_WHITELIST = (
    'localhost:3000'
)