PythonのWebフレームワークとして長年利用されているDjangoの勉強を始めたので基礎編として学んだことをアウトプットしていきます。
はじめに
PythonのWebフレームワークは主に以下になります。
・Django (中~大規模向け)
・Flask (小~中規模向け)
・bottle (小規模向け)
・Tornado (ノンブロッキングなのでAPI向き)
各フレームワークの細かい説明は省略しますが、代表的なPythonフレームワークが上記になります。
中でも日本ではRailsやLaravelなどのWebフレームワークほど企業に普及はされていませんが、海外ではDjangoやFlaskは比較的人気なWebフレームワークでDjangoはInstagramやSpotify, YouTubeなどの有名企業も利用をしています。FlaskはNetflixが利用しています。
Netflixは自社のPython利用についての見解も公式ブログにまとめていて、Pythonがネットワークプログラミングに強いこととデータ分析や機械学習にも親和性が高いことからPythonを利用していることが分かります。
Google Trendでも一時期は超軽量フレームワークのbottleが首位にたった時期がありますがDjangoがもっとも多くの割合を占めていることが分かります。
Djangoの強みとしてはフルスタックで機能が豊富であること、認証系が容易に構築・カスタマイズができること、管理画面などが準備されていて、セキュリティ周りの対策もされていてビジネスでも多くの実績があることが強みで、RailsやLaravelなどとの違いとしては言語に加えWebフレームワークとしての思想が異なっていることが使ってみて感じた違いだと思います。
アーキテクチャ
Djangoは一般的なMVCフレームワークではなくMTV(Model, Template, View)フレームワークです。コントローラーはなく、Templateが存在しています。
上図の通りコントローラーのようなどのURLリクエストがきたらどのビューに振り分けるのかを役割をするのがディスパッチャで、ビューのデータ出力役割をViewとTemplateが担います。
なのでMVCとMTVでは実際のところ差はありませんが、ControllerからModelに問い合わせをしていたところがDjangoではビューから問い合わせをしているところが少し違和感があるかと感じました。
異なる思想としてはDjangoでは雛型のプロジェクトを作り、その中に機能単位(ドメイン単位)でアプリケーションを作成していくという仕組みです。アプリケーションはプラガブルで別プロジェクトに繋げること可能になります。
プロジェクトの役割
ディレクトリ階層は以下のようになります。
プロジェクトを作成した時にできるプロジェクトモジュール群が作成されます、機能を追加するごとにアプリケーションをどんどん作成して、ドメインごとに機能を追加することができる仕組みになっています。
それでは実際にどんなモジュール群があるのかプロジェクトとアプリケーションを作成していきます。
まず、事前に準備する環境としては以下を用意してください。
・Python3
・Pipenv
$ mkdir django-basic-practices
$ cd django-basic-practices
$ pipenv install //仮想環境とパッケージ管理を提供 Pipfile, Pipfile.lock生成
$ pipenv install django // djangoインストール
$ django-admin startproject config .
プロジェクト作成コマンドにてドットでdjango-basic-practices配下にプロジェクト群を展開するようにしていますが、これがなければconfigプロジェクトのディレクトリを作成し、同一名のconfigプロジェクト群が展開されるため先にプロジェクト名のディレクトリを作成して実行しています。
また、質問に答えるだけでプロジェクトに適した雛形を作ってくれるツールなどもあります。
作成したプロジェクトの中身を見ると以下のようなディレクトリ構成になっていることが分かります。
$ tree
.
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py // wsgiと同じだが非同期処理を得意とし、3.0からサポートされた。
│ ├── settings.py // プロジェクト固有の設定ファイル
│ ├── urls.py //URLconfとも言われ、urlとviewをつなぐファイル
│ └── wsgi.py //webサーバとアプリケーション連携のエントリーポイント
├── manage.py // 管理コマンドツール
├── static //静的ファイル
└── templates // htmlなどのviewファイルを
configはプロジェクトにつけた名前です、全体のプロジェクトの設定ファイルとなるためconfigという名前でディレクトリを作成しました。
それではまず実際にサーバーを立ち上げてdjangoの画面を確認していきましょう。
立ち上げる際に必要になるのはmanage.pyです。管理コマンドツールとしてLaravelでいうArtisanのような立ち位置です。
$ python manage.py runserver
Djangoが動いていることが確認ができたらここまではOKです。次にアプリケーションを作成していきます。
$ python manage.py startapp accounts
これでアプリケーションが作成できます。
モジュール群を見てみましょう
$ tree
.
├── __init__.py
├── admin.py //管理画面を操作するモジュール
├── apps.py // アプリケーションの実態
├── migrations // マイグレーションファイル
│ └── __init__.py
├── models.py // モデル定義・ビジネスロジックを担当
├── tests.py // テストを書く
└── views.py // ビュー
URL単位、ドメインごとにロジックを切り分けて書くことができます。
また、urls.pyを追加し、プロジェクト側のurls.pyにアプリケーションのurls.pyを組み込んでurls.pyの肥大化及びアプリケーション内で管理することができます。
またアプリケーションの用途によってforms.pyやmiddleware.pyなども必要に応じて追加することができます。
簡単ではありますがプロジェクトとアプリケーションのモジュール群を確認していきました。また追加の機能がある場合はアプリケーションを追加していく形になるのがDjangoならではの開発手法なのかと感じました。
View
MTVのVの部分であるViewについてもう少し詳しくみていきます。
アーキテクチャの図からurlConfディスパッチャからView関数にリクエストを振り分けます。
View関数はそのリクエストからバリデーション処理を行ったり、リダイレクト処理を行うなどをし、Modelからデータの取得を行い、templatesにデータを送ります。
実際にViewのコードから役割をみていきます。
例えばHello Worldをtemplateに出力するViewを書いていきます。
まずはaccounts/urls.pyを作成します。
from django.urls import path
from . import views
app_name = 'accounts'
urlpatterns = [
path('hello/', views.hello)
]
プロジェクト側のurls.pyに組み込む必要があるため以下のように編集します。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('accounts.urls'))
]
ここまでできたらViewに関数を書いていきます。
from django.shortcuts import render
def hello(request):
return render(request, 'hello.html', {
'message': 'Hello World!',
})
Templatesを有効にするためにプロジェクト側のsettings.pyにtemplatesディレクトリを指定します。
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
templatesに以下のhello.htmlを作成すればHello Worldが確認することができます。
<h1>{{ message }}</h1>
views.pyにhello関数を作成しましたが、Classベースで書くこともできます。Classで書く方がViewの再利用が可能になるため出来るだけClassで書くことがベターなのかと感じました。
それではview.pyの関数をClassに変えていきます。
from django.shortcuts import render
from django.views import View
class HelloView(View):
def get(self, request):
return render(request, 'hello.html', {
'message': 'Hello World!',
})
hello = HelloView.as_view()
Classに変えることができました、これでgetの時とpostの時とでも同一クラス内で振る舞いを分けることができるようになります。
また、Djangoのviewには以下のような汎用Viewと呼ばれるクラスベースのviewクラスが存在します。
django.views.generic.base.View
django.views.generic.base.TemplateView // テンプレート出力に特化
django.views.generic.base.RedirectView // リダイレクトに特化
django.views.generic.list.ListView // 一覧に特化
django.views.generic.detail.DetailView // 詳細に特化
django.views.generic.edit.FormView // フォームに特化
django.views.generic.edit.CreateView // modelオブジェクト登録に特化
django.views.generic.edit.UpdateView // modelオブジェクト更新に特化
django.views.generic.edit.DeleteView // modelオブジェクト削除に特化
用途によって特化したViewクラスを用いることで、よくある一覧ページやフォーム、登録・更新などの処理を全てDjangoがよしなにデータを作成し開発をより効率化することができます。
例えばListViewを使ってアカウント20件ずつ一覧で表示させるとすると、以下のように実装することができます。
変数はListViewのViewクラスの中にある値をオーバーライドして取得しています。あとはtemplate側でデータを受け取って出力することでページが完成します。
from django.views import genericfrom .models import Accountsclass ListView(generic.ListView):
template_name = "accounts/list.html"
model = Accounts
paginate_by = 20
簡単に実装することができましたが、アプリケーションが膨れるとオーバーライドする変数が多くなったり返って複雑になることもあるため、都度都度リファクタリングして改良していく必要があります。
Model
DjangoのModelにはLaravelやRailsなどでも利用されているORマッパー機能を提供しています。
また、DjangoでもMigration機能があることからテーブルを作成する際には
Model作成 → ModelからMigrationファイル生成 → Migration実行
の順にテーブルを作成していきます。
それでは一度テーブルを作成していきます。
まずは以下のようなModelを作成します。
from django.db import models
class Accounts(models.Model):
class Meta:
db_table = 'accounts'
nickname = models.CharField(
verbose_name='ニックネーム',
max_length=50
)
age = models.IntegerField(
verbose_name='年齢'
)
create_date = models.DateTimeField(
verbose_name='作成日',
default=None,
null=True,
)
def __str__(self):
return self.nickname
class Profiles(models.Model):
class Meta:
db_table = 'account_profiles'
name = models.CharField(
verbose_name='名前',
null=False,
max_length=50
)
email = models.EmailField(
verbose_name='メールアドレス',
unique=True,
)
account = models.OneToOneField(
Accounts,
verbose_name='アカウント',
on_delete=models.PROTECT
)
Modelを有効にするためにプロジェクトのsettings.pyのINSTALLED_APPSにアプリケーションを登録します。登録が完了したらMigrationファイルを作成し実行していきます。
$ python manage.py makemigrations accounts // 0001_initial.py生成
$ python manage.py sqlmigrate accounts 0001 // 実行前にクエリを確認
$ python manage.py migrate
$ sqlite3 db.sqlite3 // defaultデータベース
> .tables
account_profiles auth_user_groups
accounts auth_user_user_permissions
auth_group django_admin_log
auth_group_permissions django_content_type
auth_permission django_migrations
auth_user django_session
無事テーブルが作成されていることがわかりました。
authやsessionなどは自動で作成されます。
次にModelからQuery Set APIを通じてデータを作成、取得をしてみます。
Modelは直接DBとのやり取りを行うのではなくQuery Set APIという層がDBと仲介して連携します。
以下でデータ登録・取得、そして1:1で紐づいたテーブル同士のリレーションからもデータを取得できることができていることがわかります。
$ python manage.py shell
>>> from accounts.models import Accounts, Profiles
>>> Accounts.objects.all()
>>> from django.utils import timezone
>>> a = Accounts(nickname='kosa3',age=26,create_date=timezone.now())
>>> a.save
>>> a.id
1
>>> a.nickname
'kosa3'
>>> p = Profiles(name='kosachan3',email='test@hogehoge.com',account_id=1)
>>> p.save()
>>> p.name
'kosachan3'
>>> p.account
<Accounts: kosa3>
また、LaravelでいうEager LoadingをDjangoではselect_relatedで実現します。
// N+1
for profile in Profiles.objects.all():
print(profile.account) // 都度都度DB問い合わせを行う// Join Table
for profile in Profiles.objcts.all().select_related('accounts'):
print(profile.account) //事前に取得しているので出力だけ
Template
TemplateはViewの説明時にも出たViewから渡ってきたデータを表示するhtmlファイルのことです。DTLとも言われて変数やフィルタ、テンプレートタグなどを利用できます。
ここではDTLの基本を紹介します。
・変数表示
Djangoの変数を出力は以下で実現できます。また、Djangoにはtemplate側に自動的に変数を割り当ててくれる request
, user
, messages
, perms
などがあります。これはプロジェクトのsettings.pyにてcontext_processorsに設定されているものがtemplateに変数として割り当てています。
{{ 変数名 }}
・フィルタ
フィルタは文字列の長さやデータがない時のdefault出力、dateフォーマットなど便利に変数の内容をフィルタリングしてくれます。
{{ 変数名 | default: '-' }}
{{ 変数名 | date: "Y/m/d" }}
・テンプレートタグ
layoutファイルにblockにhtmlを差し込んだり、if文やforなどのループを使うときに利用します。
{% タグ名 "引数" %}{% if account.is_admin %}
管理者です
{% else %}
一般です
{% endif %}
layoutファイルのblockにhtmlを差し込むには事前にlayoutにblockを作成し、子ファイルにてextendsでlayoutを呼び出し、blockを上書きします
// layout側
{% block title %}
レイアウト
{% endblock %}// 子ファイル
{% extends "layout.html"%}{% block title %}
子ファイル
{% block %}
まとめ
以上で、Djangoの基礎をアウトプットしました、まだ公式ドキュメントのチュートリアルや書籍を通じてのみでしか学んでいないことと、Pythonにあまり触れてこなかったので理解が浅い部分もありますがDjangoの基礎をざっくり理解してきました。まだまだミドルウェアや開発ツールなど基礎的なアウトプットをして、実際にアプリケーションを作っていきたいです。
また、Djangoを学ぶにあたり以下書籍がとても理解しやすくチュートリアルでもモヤモヤするところなどを気づく参考になりました。