Django
インストール
python の仮想環境を作成
python -m venv .venv
requirements.txt
の作成とインストール
Django~=2.1.5
pip install -r requirements.txt
Django の初期ファイル群を作成
django-admin.exe startproject mysite .
mysite/settings.py
の内容を必要に応じて修正する
# DEBUG=Trueかつ以下が空の場合、自動的にlocalhost等が設定される
ALLOWED_HOSTS = []
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
マイグレーションを実行(デフォルトでは sqlite3 がすぐに使えるよう設定されている)
python manage.py migrate
サーバを起動
python manage.py runserver
モデル
blog
という名前のアプリケーションを作成
python manage.py startapp blog
mysite/settings.py
にアプリケーションを追加しておく
INSTALLED_APPS = [
# ....other apps
'blog',
]
blog/models.py
にモデルを定義する(フィールドタイプの一覧)
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
# 計算で算出したいプロパティは下記のように定義する
@property
def author_name_and_title(self):
return self.author.name + self.title
マイグレーションを行う
# モデルの変更を検出し、マイグレーションファイルを作成
python manage.py makemigrations blog
# マイグレーションファイルをDBに適用
python manage.py migrate blog
Django admin で Post を管理できるよう、blog/admin.py
に Post を追加する。
from .models import Post
admin.site.register(Post)
管理者を作成する
python manage.py createsuperuser
http://localhost:8000/admin
で管理画面にログインすると、ブラウザ上から DB を編集できる。
ルーティング
urls.py
は、ルーティングの設定ファイルである。ルートと、それに対応する処理(一般的には view)を設定する。
mysite/urls.py
で、/blog
に来たリクエストをblog/urls.py
に委任する
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls'))
]
blog/urls.py
では、全ての処理をview.post_list
に任せる。
from django.urls import path
from . import views
urlpatterns = [
path('', views.post_list, name='post_list'),
]
※ name
とは?
- ビューを識別するために使われる URL の名前
- ビューと同じ名前にすることもできるし、別の名前にすることもできる
- ユニークで覚えやすいものにしておくこと
ビュー
ややこしいが、
- Django の View は、MVC でいうところの Controller を指している。
- Django の template が、MVC でいうところの View を指している。
# blog/views.py
from django.shortcuts import render
def post_list(request):
return render(request, 'blog/post_list.html', {})
テンプレートは、アプリケーションフォルダ/templates/アプリケーション名
の中に配置すること。
アプリケーション名が重複しているのは、あとでより複雑な構成にする時に楽にするためらしい。
request
でリクエスト情報を取得できる。
blog/templates/blog/post_list.html
<div>hello world</div>
Django shell
Django shell の起動 (python shell に見えるが、Django も動いてるよ)
python manage.py shell
shell では、Model を使って様々な操作を行える。
Django ORM の基本
基本操作
一覧の取得
model.objects.all()
一覧(クエリセット)を取得できる。クエリセット=モデルのインスタンスのリストのこと。
from blog.models import Post
Post.objects.all()
# <QuerySet [<Post: Hello1>, <Post: Hello2>]> => これがクエリセット
条件を指定して 1 件取得
model.objects.get()
# django標準のユーザ管理機能から、Userモデルを呼び出す
from django.contrib.auth.models import User
me = User.objects.get(username='ola') # => 単品のオブジェクトが返る
条件にあうインスタンス(オブジェクト)が見つからなかったときは、ObjectDoesNotExist
を raise する。これは不都合 なことが多いので、get_object_or_404
関数と組み合わせて使うことも多い。
from django.shortcuts import get_object_or_404
get_object_or_404(Person, id=20)
DB から情報を再取得
model_instance.refresh_from_db()
作成
2 種類のインスタンス作成方法がある。
# こちらは`save()`は不要
MODEL_NAME.objects.create(kwargs)
# こちらは`save()`が必要
obj = MODEL_NAME(kwargs)
obj.save()
条件を指定して複数件取得
model.objects.filter()
Post.objects.filter(author=me) # => クエリセットが返る
「含む」などの条件を指定したい場合は、フィールド名__contains
等の形式で指定する。
Post.objects.filter(title__contains='sample')
Post.objects.filter(published_date__lte=timezone.now())
並べ替え
model.objects.order_by()
Post.objects.order_by('created_date')
Post.objects.order_by('-created_date')
チェーン
Post.objects.\
filter(title__contains='sample').\
order_by('created_date').\
all()
クエリの確認
queryset.query
で発行されるクエリを確認できる!
参照・逆参照先のデータの取得
後続の処理で何度もアクセスされるオブジェクトを先に取得しておきたいときには、select_related
やprefetch_related
を使う。
select_related
one 側のオブジェクト(Foreign Key など)を見に行くときにつかう。 INNER JOIN または LEFT OUTER JOIN されるのでクエリの回数を減らせる。
# many->one
BlogPost.objects.filter(pk=1).select_related('user')
prefetch_related
many 側のオブジェクト群を取得することができる(one 側のオブジェクト取得にも使えるが、あまり利用しない)。複数回のクエリを発行して Python 側で結合するので、select_related よりはクエリ回数は増える。
# one->many (reverse relationship)
User.objects.filter(pk=1).prefetch_related('blogposts')
# many->many
BlogPost.objects.filter(pk=1).prefetch_related('categories')
内部結合
filter
は内部結合(INNER JOIN)で動作する。
下記の例では、Blog(many)から Author(one)へ参照が設定されているものとする。Author から Blog を逆参照している。
# 少なくとも 1 つ以上の`hello`というタイトルのブログを持っている作者らの情報をクエリする
Author.objects\
.filter(blog__title='hello') \
.all()
外部結合
many 側のデータ「を」フィルタしたいときは、prefetch_related
とdjango.db.models.Prefetch
を組み合わせる。外部結合と同じような動作になる。
- 全てのユーザを取得
- 各ユーザに紐づく、タイトルが'hello'であるブログを取得(1 とは独立したクエリが発行され、Python 側でマージされる)
Author.objects \
.prefetch_related(
Prefetch(
"blog",
queryset=Blogposts.objects.filter(title='hello')
)
)
filter と Prefetch の違い
filter
は many 側のデータ「で」フィルタしているだけなので、後段のprefetch_related
には何ら影響を与えない点に注意する。
- 少なくとも 1 つ以上の
hello
というタイトルのブログを持っている作者らの情報をクエリする(INNER JOIN による) - その作者らの子となる全ての
blog
をクエリする(上記とは独立したクエリが発行され、Python 側でマージされる)
Author.objects\
.filter(blog__title='hello') \
.prefetch_related('blog') \
.all()
結論:many 側のデータ「で」フィルタしたいときはfilter
を、many 側のデータ「を」フィルタしたいときはPrefetch
を使う。
Aggregation, Annottion, Groupby
aggregate()
レコードセット全体に対して集計を行い、単一のデータとして返す。annotate()
レコードセットの各レコード単位で集計等を行い、複数のデータとして返す。values()
を組み合わせることで、Group By したうえで集計を行うこともできる。- 参考
- 条件つきで集計
Query Expressions
https://docs.djangoproject.com/en/2.2/ref/models/expressions/
F()
expression
- 列の値を使った様々な計算ができる。
filter
やannotate
の中で使える。インスタンスの値を更新するときにも使える。- Race Condition 時に値を失うことがない
from django.db.models import Count, F, Value
from django.db.models.functions import Length, Upper
# 椅子の数よりも従業員数が多い会社
Company.objects.filter(num_employees__gt=F('num_chairs'))
# 椅子の数よりも従業員数が2倍以上多い会社
Company.objects.filter(num_employees__gt=F('num_chairs') * 2)
Company.objects.filter(num_employees__gt=F('num_chairs') + F('num_chairs'))
# 全ての従業員が座るには椅子がいくつ必要なのかを、それぞれの会社ごとに算出
company = Company.objects \
.filter(num_employees__gt=F('num_chairs'))
.annotate(chairs_needed=F('num_employees') - F('num_chairs'))
# 個別更新
# - Bad メモリ効率が悪く、Race Condition時にデータを失う
some_instance.some_field += 1
# - Good メモリ効率がよく、Race Condition時にもデータを失わない
some_instance.some_field = F('stories_filed') + 1
# 一括更新
some_instance.update(some_field=F('stories_filed') + 1)
注意事項
重複して更新されるため、F()
を使った更新を設定したあとにsave()
を 2 回以上呼んではダメ。
2 回以上保存したい場合は、間で必ずrefresh_from_db()
すること。
some_instance.some_field = F('stories_filed') + 1
some_instance.save() # +1される
some_instance.save() # もう一度+1される