Edited at

Python Django チュートリアル SPA編(1)

More than 1 year has passed since last update.

勉強会用の資料です.

以前本家チュートリアルを辿った資料を作っていましたが,

しばらく放置している間にdjangoのバージョンが2になったのと,

シングルページアプリケーション化を目標にするため,一度仕切り直します.

フロントエンドはvue-cli,バックエンドはdjango(rest-frameworks)を利用して,

本家チュートリアルをSPA化していきます.

例のごとくgitに作業開始,途中のソースコードを置くので,途中から始めるかたはcloneしてください.

ソースコードはgithubに置いてます → https://github.com/usa-mimi/tutorial-spa


環境構築

仮想環境を作成してから作業することを推奨します.

仮想環境の作成方法は過去記事等を参考にしてください.

私はvirtualenv(virtualenvwrapper)を使用していますが,

python3.3からはvenvが標準でついているので好きなものを使用してください.

この記事を書いている時点でのpython, pip, Djangoのバージョンは以下の通りです.

python 3.6.4

pip 9.0.3
Django 2.0.3


バージョンが多少前後しても動くと思いますが,python3.7でdjango1.11を動かそうとするとエラーになります.



仮想環境の作成

まず仮想環境を作成します.

virtualenvwrapperを使用している場合は作り方が異なりますのでこの節はスキップして大丈夫です.

仮想環境の名前は tutorial-spa にしました.

また,pythonは2系ではなく3系を使用したいので -p python3 オプションでpython3を指定しています.

$ mkvirtualenv -p python3 tutorial-spa

Running virtualenv with interpreter /usr/local/bin/python3
Using base prefix '/usr/local/Cellar/python/3.6.4_3/Frameworks/Python.framework/Versions/3.6'
New python executable in /Users/shimomura/.virtualenvs/tutorial-spa/bin/python3.6
Also creating executable in /Users/shimomura/.virtualenvs/tutorial-spa/bin/python
Installing setuptools, pip, wheel...done.
virtualenvwrapper.user_scripts creating /Users/shimomura/.virtualenvs/tutorial-spa/bin/predeactivate
virtualenvwrapper.user_scripts creating /Users/shimomura/.virtualenvs/tutorial-spa/bin/postdeactivate
virtualenvwrapper.user_scripts creating /Users/shimomura/.virtualenvs/tutorial-spa/bin/preactivate
virtualenvwrapper.user_scripts creating /Users/shimomura/.virtualenvs/tutorial-spa/bin/postactivate
virtualenvwrapper.user_scripts creating /Users/shimomura/.virtualenvs/tutorial-spa/bin/get_env_details


作業ディレクトリの用意

tutorialアプリのためのディレクトリを作成し,

その中にdjango用のディレクトリとフロント(js)用のディレクトリを配置するようにします.


チュートリアルのためディレクト階層を深くし,バックエンドとフロントエンドをまとめてますが,

実際に作成する場合はそれぞれ別リポジトリで管理するほうがいいです.

同じリポジトリにまとめるとフロントとバックエンドのバージョンの差異はなくなりますが,

フロントとバックエンドの作業分担がしづらくなります.

また,circleCIなどでpush時に自動テストを実行するCIアプリを利用している場合

フロントの修正だけでバックエンドのテストが実行されてしまいます.


$ mkdir tutorial-spa

$ cd tutorial-spa

まずはディレクトリを作って移動するだけです.

ここにバックエンドとフロントエンドのプログラムを作っていきます.


djangoプロジェクトの作成(バックエンドの設置)

ソース: → f34594e

仮想環境にworkonした上でまずはdjangoをinstallし,新規プロジェクトを作成します.


djangoのインストール

(tutorial-spa) $ pip install django

Collecting django
Using cached Django-2.0.3-py3-none-any.whl
Collecting pytz (from django)
Using cached pytz-2018.3-py2.py3-none-any.whl
Installing collected packages: pytz, django
Successfully installed django-2.0.3 pytz-2018.3
pip install django 3.39s user 2.91s system 93% cpu 6.737 total


tutorialプロジェクト作成

(tutorial-spa) $ django-admin startproject tutorial


ここまででこういうファイル構成になっているはずです.

tutorial-spa/

└── tutorial/
├── manage.py
└── tutorial/
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py


pollsアプリケーションの追加

本家: https://docs.djangoproject.com/ja/2.0/intro/tutorial01/

pollsアプリを追加します.

本家ではviews.pyを編集してますが,API化する場合は通常のレスポンスは必要ないの記述は不要です.


追加

ソース: f34594eac92ba1

startapp でpollsアプリを追加して, settings.py のINSTALL_APPSに追加します.


  • プロジェクトのルートディレクトリに移動

manage.pyがある,プロジェクトの(tutorial)ルートディレクトリに移動します.

manage.pyを利用して様々なコマンド処理(アプリの新規作成など)をすることができます.

(tutorial-spa) $ python manage.py startapp polls


tutorial/settings.py

INSTALLED_APPS = [

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


polls/models.py の編集

ソース: ac92ba1f3c16d6

本家: https://docs.djangoproject.com/ja/2.0/intro/tutorial02/

polls/models.py にpollsで使用するモデルを記述します.


この辺は手抜きで本家からのコピペです.



polls/models.py

from django.db import models

class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')

class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)



DBへの適用

ソース: f3c16d6f6cc667

makemigrations コマンドとmigrate コマンドを実行してDBを作成します.

実行すると db.sqlite3 ファイルが作成されます.


makemigrations はmodels.py(と存在する場合は前回のmigrateファイル)から,

DBを作成(もしくは変更/削除)するためのmigrateファイルを作成するコマンドです.

models.pyを変更した場合は makemigrations でmigrateファイルを作成し,

migrate コマンドでDBへ変更を適用する,という流れになります.


初めて実行する場合はdjangoが使用するテーブルも作成されます.

(tutorial-spa)$ python manage.py makemigrations polls

Migrations for 'polls':
polls/migrations/0001_initial.py
- Create model Choice
- Create model Question
- Add field question to choice

(tutorial-spa) $ python manage.py migrate

Operations to perform:
Apply all migrations: admin, auth, contenttypes, polls, 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 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 polls.0001_initial... OK
Applying sessions.0001_initial... OK


管理サイトに追加

ソース: f6cc6677945f45

管理サイトからDBの中身を確認できるように, polls/admin.py を編集して

Question モデルと Choice モデルを追加します.


polls/admin.py

from .models import Question, Choice

admin.site.register(Question)
admin.site.register(Choice)



確認

ソース: 7945f45 (このセクションでの変更なし)

管理画面から見えることを確認します.


  • 管理画面に入れるようにsuperuserを作成します.


superuserを作成

(tutorial-spa) $ python manage.py createsuperuser

Username (leave blank to use 'shimomura'): admin
Email address:
Password:
Password (again):
Superuser created successfully.


  • 開発サーバを立ち上げ


開発サーバを立ち上げ

(tutorial-spa) $ python manage.py runserver

Performing system checks...

System check identified no issues (0 silenced).
March 31, 2018 - 07:26:23
Django version 2.0.3, using settings 'tutorial.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.


この状態でブラウザを開き,以下のURLを叩くと管理ページへのログイン画面が表示されるので,

そこで先ほど作成した username, passwordを入力します.

http://localhost:8000/admin/

ログイン後,pollsの下に2つのモデルがあることを確認します.


API化

DjangoRESTframeworkを使用してAPI化を行います.

http://www.django-rest-framework.org/


ライブラリのインストール/設定

ソース: 7945f452d3388c

pipで djangorestframework をインストールします.

(tutorial-spa) $ pip install djangorestframework


tutorial/settings.py

INSTALLED_APPS = [

'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # <-------- 追加
'polls',
]

ついでなのでこのタイミングでtimezoneと言語を日本語に変更します.


tutorial/settings.py

# LANGUAGE_CODE = 'en-us'

LANGUAGE_CODE = 'ja'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'




これで管理サイトが日本語化されます.


APIレスポンス(serializerクラス)の定義

ソース: 2d3388cd802bad

APIの戻り値を設定するためにserializerを定義していきます.

serializerはdjangoのFormのようなものですが,表示のためのwidgetを持っていません.

その代わり,データを階層的に表現できます.

また,新規作成時の処理,更新時の処理をそれぞれ独立メソッドで定義します.


ModelFormの場合はsaveメソッド内でinstanceがあるかどうかを自分で記述する必要があります


serializerクラスの定義は慣例的に serializers.py ファイルを作り,その中に記述していきます.


polls/serializers.py

from rest_framework import serializers

from .models import Question, Choice

class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = (
'id',
'question_text',
'pub_date',
)

class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = (
'id',
'question',
'choice_text',
'votes',
)



fieldsは羅列せずに,fields = '__all__' と書くこともできます.

__all__と書くとModelの全フィールドが対象となります(ModelFormのallと同じです)



Viewクラスの定義

ソース: d802bad9aff5a9

次にAPIのためのViewクラスを定義します.

DjangoRESTFrameworkではAPIに特化してViewクラスを提供しています.

RESTではあるリソースとURIを紐付け,さらに HTTPメソッドCRUD操作 に紐付ける,ということがよく行われます.


CRUDは,Create, Read, Update, Deleteの略です.


厳密には異なる部分があり,よく議論の対象となっていますが,大抵の場合は

POST→Create,GET→Read,PUT(PATCH)→Update,DELETE→Deleteと割り当てることができます.

そのため,DRFではそれぞれのメソッドを定義したViewSetを提供しています.

今回はその派生クラスであるReadOnlyModelViewSetを使ってみます.

なお,ソースコードは通常のviewと区別するため,api_views.pyというファイルを作成し,そこに記述していきます.


polls/api_views.py

from rest_framework.viewsets import ReadOnlyModelViewSet

from .models import Question
from .serializers import QuestionSerializer

class QuestionViewSet(ReadOnlyModelViewSet):
queryset = Question.objects.all() # ここが対象となるレコードの指定.今回は全部
serializer_class = QuestionSerializer # 戻り値を定義したSerializer



url設定

ソース: 9aff5a9aac490f

通常のdjango同様,作成したviewとurlを紐付けます.

今回はViewSetを使用したため,通常のdjangoと若干書き方がことなります.

ViewSetではメソッドをHTTPメソッドと紐付けるために,as_view()の呼び出し時に引数に辞書を与え,

ViewSet.as_view({'get': 'list', 'post': 'create'}) のように書きます.

この操作もほぼ決まりきったルールがあるため,DRFではRouterクラスを提供しています.

RouterはViewSetのメソッドを自動的にURLに展開してくれます.

このソースコードも,通常のurls.pyと区別するため, api_urls.pyに書くことにします.


polls/api_urls.py

from rest_framework.routers import DefaultRouter

from . import api_views

question_router = DefaultRouter()
question_router.register(r'', api_views.QuestionViewSet)


さらにRootのurls.pyを編集し,polls/api_urls.py で定義したrouterを紐付けます.


tutorial/urls.py

from django.contrib import admin

from django.urls import path, include # includeを追加

from polls.api_urls import question_router # 定義したquestion_routerをimport

api_urlpatterns = [ # apiのURL一覧 (まだquestionだけ)
path('questions/', include(question_router.urls)), # 慣例として複数形にする
]

urlpatterns = [
path('admin/', admin.site.urls),
path('api/1.0/', include(api_urlpatterns)), # api/1.0/としてapi一覧を登録
]


apiはurlの一部にv11.0などのようにバージョン番号を付けておくと後々の改修時に役立ちます.

api用のURLは一箇所でまとめて定義したほうが,フロントエンド開発時見直す時に見やすいです.

今回はしてませんが,tutorial/api_urls.py として別に書き出してもいいと思います.


確認

admin画面 ( http://localhost:8000/admin/polls/question/ ) から適当なテストデータを登録し,

apiの戻り値を確認してみましょう.

DRFではブラウザからアクセスするとapiの戻り値をキレイに整形して表示してくれます.

http://localhost:8000/api/1.0/questions/ にアクセスすると以下のように表示してくれるはずです.

また,ReadOnlyModelViewSet ではリストの他に,詳細表示用のメソッド (retrieve) が定義されており,

DefaultRouter によって /pk/ として自動でURLが登録されます.

http://localhost:8000/api/1.0/questions/1/ にアクセスすると以下のように表示してくれるはずです.


関連ライブラリの吐き出し

ソース: aac490f8b39332

djangoやdjangorestframeworkなど,pipでインストールしたライブラリを書き出しておきましょう.

pip7.1からconstraintsが使えるようになったので,本チュートリアルではそれを活用していきます.

インストール済みのライブラリはシェルで pip freeze と打つことで確認できます.

従来のやり方ではこれを requirments.txt に書き出してましたが,今回は constraints.txt として書き出します.

$ pip freeze > constraints.txt

また,手動で requirements.txt を編集し, pip install コマンドで指定したライブラリを書きます.


requirements.txt

Django

djangorestframework

ライブラリを復元するときは

$ pip install -r requirements.txt -c constraints.txt

とすればOKです.


$ pip freeze > requiremnts.txt で書き出し,

$ pip install -r requirements.txt で復元しても結果としては同じですが,

pip install コマンドではライブラリが必要としている関連ライブラリも自動でインストールされます.

そのため,自分が必要としていないライブラリまでrequirements.txtに含まれてしまいます.

先ほどpip freezeで吐き出したconstraints.txt の中身を確認すると以下のようになっているはずです.


constraints.txt

Django==2.0.3

djangorestframework==3.7.7
pytz==2018.3

pytzはインストールした覚えがないと思いますが,こいつはDjangoの関連ライブラリとして自動でインストールされます.

このように,自分が本当に必要でインストールしたものと,結果としてインストールされたライブラリ群を分けて管理することで,

後で必要のないライブラリを削除したり,アップデートを行うことが容易になります.



次はフロント側を作っていきます.

チュートリアル2へ

チュートリアルまとめ