勉強会用の資料です.
以前本家チュートリアルを辿った資料を作っていましたが,
しばらく放置している間に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し,新規プロジェクトを作成します.
(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-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化する場合は通常のレスポンスは必要ないの記述は不要です.
追加
ソース: f34594e → ac92ba1
startapp でpollsアプリを追加して, settings.py のINSTALL_APPSに追加します.
- プロジェクトのルートディレクトリに移動
 
manage.pyがある,プロジェクトの(tutorial)ルートディレクトリに移動します.
manage.pyを利用して様々なコマンド処理(アプリの新規作成など)をすることができます.
(tutorial-spa) $ python manage.py startapp polls
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',
]
polls/models.py の編集
ソース: ac92ba1 → f3c16d6
本家: https://docs.djangoproject.com/ja/2.0/intro/tutorial02/
polls/models.py にpollsで使用するモデルを記述します.
この辺は手抜きで本家からのコピペです.
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への適用
ソース: f3c16d6 → f6cc667
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
管理サイトに追加
ソース: f6cc667 → 7945f45
管理サイトからDBの中身を確認できるように, polls/admin.py を編集して
Question モデルと Choice モデルを追加します.
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
確認
ソース: 7945f45 (このセクションでの変更なし)
管理画面から見えることを確認します.
- 管理画面に入れるように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を入力します.
ログイン後,pollsの下に2つのモデルがあることを確認します.
API化
DjangoRESTframeworkを使用してAPI化を行います.
ライブラリのインストール/設定
ソース: 7945f45 → 2d3388c
pipで djangorestframework をインストールします.
(tutorial-spa) $ pip install djangorestframework
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # <-------- 追加
    'polls',
]
ついでなのでこのタイミングでtimezoneと言語を日本語に変更します.
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'
これで管理サイトが日本語化されます.
APIレスポンス(serializerクラス)の定義
ソース: 2d3388c → d802bad
APIの戻り値を設定するためにserializerを定義していきます.
serializerはdjangoのFormのようなものですが,表示のためのwidgetを持っていません.
その代わり,データを階層的に表現できます.
また,新規作成時の処理,更新時の処理をそれぞれ独立メソッドで定義します.
ModelFormの場合はsaveメソッド内でinstanceがあるかどうかを自分で記述する必要があります
serializerクラスの定義は慣例的に 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クラスの定義
ソース: d802bad → 9aff5a9
次に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というファイルを作成し,そこに記述していきます.
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設定
ソース: 9aff5a9 → aac490f
通常の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に書くことにします.
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を紐付けます.
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の一部にv1や1.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/ にアクセスすると以下のように表示してくれるはずです.
関連ライブラリの吐き出し
ソース: aac490f → 8b39332
djangoやdjangorestframeworkなど,pipでインストールしたライブラリを書き出しておきましょう.
pip7.1からconstraintsが使えるようになったので,本チュートリアルではそれを活用していきます.
インストール済みのライブラリはシェルで pip freeze と打つことで確認できます.
従来のやり方ではこれを requirments.txt に書き出してましたが,今回は constraints.txt として書き出します.
$ pip freeze > constraints.txt
また,手動で requirements.txt を編集し, pip install コマンドで指定したライブラリを書きます.
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へ](https://qiita.com/maisuto/items/2bf1b072f189a65d6f6e)
→ [チュートリアルまとめ](http://qiita.com/maisuto/items/17653f344d1f64afa019)




