Djangoの学習メモ
新規プロジェクトの作成
$ django-admin startproject app
$ cd && tree
./app
├ /app
| ├ __init__.py
| ├ asgi.py
| ├ settings.py
| ├ urls.py
| └ wsgi.py
└ manage.py
設定ファイルの書き換え
設定ファイルは./app/app
の配下に存在するsetting.py
ファイル。言語設定とタイムゾーンを切り替える。
- LANGUAGE_CODE = "en-us"
+ LANGUAGE_CODE = "ja"
- TIME_ZONE = "UTC"
+ TIME_ZONE = "Asia/Tokyo"
マイグレーションコマンド実行
デフォルトではSQLite3を使うように設定されている。
実行するとapp配下にdb.sqlite3
ファイルが生成される。
$ python manage.py migrate
# スーパーユーザを作成
$ python manage.py createsuperuser
admin
admin@plactice.com
hogehoge
hogehoge
サーバー起動
$ python magane.py runsever
新しいアプリケーションを作る
./app
の配下に指定した名前のアプリケーションディレクトリが生成される。
アプリケーションは機能単位ごとで生成されていくようなイメージ。
$ python manage.py startapp placticeapp
├ /app
| └ /placticeapp
| ├ /migrations
| | └ __init__.py
| ├ __init__.py
| ├ admin.py
| ├ models.py
| ├ tests.py
| └ views.py
└ manage.py
アプリケーションの定義を設定に反映する
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
+ "placticeapp.apps.PlacticeappConfig",
]
新しいモデルを作る
モデル(Model)とは
MVTモデルの構成要素の一つであり、DBへ直接アクセスすることを防ぎ、DBのデータをクラスを使って表現、アクセス可能としたもの。
from django.db import models
class Person(models.Model):
first_name = models.CharField("名", max_length=30)
last_name = models.CharField("姓", max_length=30)
age = models.IntegerField("年齢")
nickname = models.CharField("ニックネーム", max_length=255, blank=True)
def __str__(self):
return f"{self.last_name} {self.first_name}"
class Organization(models.Model):
name = models.CharField("組織名", max_length=255)
person = models.ForeignKey(Person, verbose_name="人", related_name="organizations", on_delete=models.CASCADE)
def __str__(self):
return self.name
# マイグレーションファイル作成
$ python mangae.py makemigrations placticeapp
Migrations for 'placticeapp':
placticeapp/migrations/0001_initial.py
- Create model Person
- Create model Organization
# SQLを確認
$ python manage.py sqlmigrate placticeapp 0001
BEGIN;
--
-- Create model Person
--
CREATE TABLE "placticeapp_person" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL, "age" integer NOT NULL, "nickname" varchar(255) NOT NULL);
--
-- Create model Organization
--
CREATE TABLE "placticeapp_organization" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(255) NOT NULL, "person_id" bigint NOT NULL REFERENCES "placticeapp_person" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "placticeapp_organization_person_id_598c497e" ON "placticeapp_organization" ("person_id");
COMMIT;
# マイグレーション実行
$ python magane.py migrate placticeapp
Operations to perform:
Apply all migrations: placticeapp
Running migrations:
Applying placticeapp.0001_initial... OK
管理サイトでデータを編集する
Djangoの機能を使って管理ページからデータを閲覧・編集することができる。
その際、編集したいデータモデルを管理サイトに紐づけておく必要がある。
紐づけができたら、(http://localhost:8000/admin)にアクセスし、事前に作成したadminユーザーでログインすると、データが見れるようになっている。
from django.contrib import admin
+ from plactice.models import Person, Organization
+ class PersonAdmin(admin.ModelAdmin):
+ list_display = ("id", "first_name", "last_name", "age", "nickname")
+ list_display_links = "id"
+ admin.site.register(Person, PersonAdmin)
+ admin.site.register(Organization)
新しくビューを作る
ビュー(View)とは
MVTモデルの構成要素の一つであり、どのページを表示させるかをリクエストを元に決定する処理を担う。またModelから取得したデータを整形して、Templateに渡す。
Djangoでは関数やクラスを使って記述することができる。
from django.shortcuts import get_object_or_404, redirect, render
from django.http import HttpResponse
from placticeapp.models import Person
from placticeapp.forms import PersonForm
def person_list(request):
persons = Person.objects.all().order_by("id")
return render(request, "placticeapp/person_list.html", {"persons": persons})
def person_edit(request, person_id=None):
if person_id:
person = get_object_or_404(Person, pk=person_id)
else:
person = Person()
if request.method == "POST":
form = PersonForm(request.POST, instance=person)
if form.is_valid():
person = form.save(commit=False)
person.save()
return redirect("placticeapp:person_list")
else:
form = PersonForm(instance=person)
return render(
request, "placticeapp/person_edit.html", dict(form=form, person_id=person_id)
)
def person_delete(request, person_id):
person = get_object_or_404(Person, pk=person_id)
person.delete()
return redirect("placticeapp:person_list")
URLのルーティングを定義する
placticeappディレクトリに新たにurls.py
を作成する。
定義したルーティングをプロジェクト全体のurlsで読み込む。
from django.urls import path
from placticeapp import views
app_name = "placticeapp"
urlpatterns = [
path("person/", views.person_list, name="person_list"),
path("person/create/", views.person_edit, name="person_create"),
path("person/edit/<int:person_id>/", views.person_edit, name="person_edit"),
path("person/delete/<int:person_id>/", views.person_delete, name="person_delete"),
]
from django.contrib import admin
- from django.urls import path, include
+ from django.urls import path, include
urlpatterns = [
+ path("placticeapp/", include("placticeapp.urls"))
path("admin/", admin.site.urls),
]
入力フォームを定義する
placticeappディレクトリに新たにforms.py
を作成する。
from django.forms import ModelForm
from placticeapp.models import Person
class PersonForm(ModelForm):
class Meta:
model = Person
fields = ("first_name", "last_name", "age", "nickname,")
新しくテンプレートを作る
テンプレート(Template)とは
実際のページ(HTML)となりうるもの。Viewを通じて受け取ったデータをテンプレートに当てはめてユーザーに見せる。MVCモデルのViewにあたる。
人一覧のテンプレートを作成
placticeappディレクトリにtemplates/placticeapp
ディレクトリを作成し、そこにbase.html
とperson_list.html
ファイルを作成する。
{% load i18n static %}
<!DOCTYPE html>{% get_current_language as LANGUAGE_CODE %}
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}">
<head>
<meta charset="utf-8">
<meta name="view-port" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}Django Plactice App{% endblock %}</title>
</head>
<body>
<div class="container">
{% block content %}
{{ content }}
{% endblock %}
</div>
</body>
</html>
{% extends "placticeapp/base.html" %}
{% block title %}人の一覧{% endblock %}
{% block content %}
<h1>人の一覧</h1>
<a href="{% url "placticeapp:person_create" %}" class="btn">新規作成</a>
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">名前</th>
<th scope="col">年齢</th>
<th scope="col">ニックネーム</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
{% for person in persons %}
<tr>
<th scope="row">{{ person.id }}</th>
<td>{{ person.last_name }} {{ person.first_name }}</td>
<td>{{ person.age }}</td>
<td>{{ person.nickname }}</td>
<td>
<a href="{% url "placticeapp:person_edit" person_id=person.id %}" class="btn">編集</a>
<a href="{% url "placticeapp:person_delete" person_id=person.id %}" class="btn">削除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
(http://localhost:8000/placticeapp/persons)にアクセス。
人編集のテンプレートを追加
placticeapp/templates/placticeappディレクトリにperson_edit.html
ファイルを追加。
{% extends "placticeapp/base.html" %}
{% block title %}人の編集{% endblock %}
{% block content %}
<h4>人の編集</h4>
{% if person_id %}
<form action="{% url "placticeapp:person_edit" person_id=person_id %}" method="post">
{% else %}
<form action="{% url "placticeapp:person_create" %}" method="post">
{% endif %}
{% csrf_token %}
<div class="form-group">
<label for="last_name">姓</label>
<input type="text" class="form-control" id="last_name" name="last_name" value="{{ person.last_name }}">
<label for="first_name">名</label>
<input type="text" class="form-control" id="first_name" name="first_name" value="{{ person.first_name }}">
<label for="age">年齢</label>
<input type="number" class="form-control" id="age" name="age" value="{{ person.age }}">
<label for="nickname">ニックネーム</label>
<input type="text" class="form-control" id="nickname" name="nickname" value="{{ person.nickname }}">
</div>
<div class="form-group">
<button type="submit" class="btn">送信</button>
</div>
</form>
<a href="{% url "placticeapp:person_list" %}" class="btn">戻る</a>
{% endblock %}
APIサーバーとして実装する
JSON形式のレスポンスを返すためのAPIサーバーとしての実装を試す。
まずはこれまでと同様にアプリケーションを新規作成する。
$ python manage.py startapp placticeapi
Viewファイルを編集
import json
from collections import OrderedDict
from django.http import HttpResponse
from placticeapp.models import Person
# Create your views here.
def render_json_response(request, data, status=None):
json_str = json.dumps(data, ensure_ascii=False, indent=2)
callback = request.GET.get("callback")
if not callback:
callback = request.POST.get("callback")
if callback:
json_str = "%s{%s}" % (callback, json_str)
response = HttpResponse(
json_str,
content_type="application/javascript; charset=UTF-8",
status=status,
)
else:
response = HttpResponse(
json_str, content_type="application/json; charset=UTF-8", status=status
)
return response
def person_list(request):
persons = []
for person in Person.objects.all().order_by("id"):
organizations = []
for organization in person.organizations.order_by("id"):
organization_dict = OrderedDict(
[
("id", organization.id),
("name", organization.name),
]
)
organizations.append(organization_dict)
person_dict = OrderedDict(
[
("id", person.id),
("first_name", person.first_name),
("last_name", person.last_name),
("age", person.age),
("nickname", person.nickname),
("organizations", organizations),
]
)
persons.append(person_dict)
data = OrderedDict([("persons", persons)])
return render_json_response(request, data)
各ファイルにAPIサーバー用の設定を追記
placticeapiディレクトリにurls.py
ファイルを作成。
from django.urls import path
from placticeapi import views
app_name = "placticeapi"
urlpatterns = [
path("v1/persons/", views.person_list, name="person_list"),
]
urlpatterns = [
path("placticeapp/", include("placticeapp.urls")),
+ path("placticeapi/", include("placticeapi.urls")),
path("admin/", admin.site.urls),
]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"placticeapp.apps.PlacticeappConfig",
+ "placticeapi.apps.PlacticeapiConfig",
]
(http://localhost:8000/placticeapi/v1/persons/)にアクセス。
所感
APIの作成はFastAPIで実装したほうが楽だなあ
バッチ処理を実装する
Djangoにはバッチ処理をコマンド単位で実行できる機能が存在している。公式的にはカスタムコマンドと表現されている。
特徴
- 引数を受け取ることが可能
- コマンドは
management/commands/
配下に作成する - 1コマンドにつき1Pythonファイル
$ python manage.py startapp placticebatch
設定ファイルapp/settings.py
にアプリケーションを追加。
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"placticeapp.apps.PlacticeappConfig",
"placticeapi.apps.PlacticeapiConfig",
+ "placticebatch.apps.PlacticebatchConfig",
]
バッチ処理を認識させるためのファイルを作成
Djangoのバッチ処理はmanagement/commands
ディレクトリに存在するファイルをバッチ処理用のコマンドとして認識するため、必要なディレクトリとファイルを作成する。
$ cd ./placticebatch
$ mkdir -p management/commands
$ touch management/commands/__init__.py
$ touch management/commands/command_1.py
コマンドの書き方
Djangoがコマンドを認識するためにはBaseCommand
クラスを継承したクラスを定義し、クラス内でhandle
メソッドを定義する。
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "コマンドの説明"
def handle(self, *args, **options):
print("コマンドの処理")
コマンドの実行
# コマンドがDjangoに認識されているか確認
$ python manage.py help
# コマンド実行
$ python manage.py command_1
コマンドライン引数を定義
add_arguments
メソッドを定義することで、コマンドライン引数を実装することができる。
class Command(BaseCommand):
help = "コマンドの説明"
def handle(self, *args, **options):
print(f"渡された引数は{options['name']}")
+ def add_arguments(self, parser):
parser.add_arguments("--name", nargs="1", default="", type=str)
$ python manage.py command_1 --name テスト
渡された引数はテスト
ロギング設定
Djangoのロギング設定はsettings.py
ファイル内に記述する。
ロギングの基本的な仕様はloggingモジュールに準じており、Djangoだからといって特に特別な作り込みはされていないっぽい。
ログレベル
INFOのログレベルを持ったloggerやhandlerはINFO以上のログを出力するため、DEBUGやNOTSETで指定されたログは出力しない。
レベル | 概要 |
---|---|
NOTSET | 全てのログ |
DEBUG | デバッグ用のログ |
INFO | 正常に動作しているときのログ |
WARNING | 警告のログ |
ERROR | エラーなどの問題が発生したときのログ |
CRITICAL | 致命的な問題が発生したときのログ |
formatter
出力するログのフォーマットを指定する。
フォーマットにはそれぞれ名前を指定することが可能で、ここで作成したformatterを次に紹介するhandlersに渡す。
filter
ログ出力をフィルタリングするためのもの。
デバッグ設定のON/OFFによってログをフィルタリングさせるなどができる。
handler
ログの出力方式を指定する。
出力方式はコンソールやファイルとなり、ファイルの場合はファイル名もここで指定する。
また出力時のフォーマットは前述したformatterのから選択する。
logger
ログ出力を行うオブジェクトの設定。
どのhandlerを使用するかやログレベルを指定する。ここで設定したオブジェクトをgetLogger(logger名)
でインスタンス化して、実際にログ出力を行う。
ここの設定でpropagate: True
とするとログ出力が親のロガーにも伝搬する。
loggerの名前空間
loggerには名前空間を持つことができ、getLogger()
でインスタンス化しようとした際、対象のloggerがなかった場合にはより上位のloggerを取得する。
getLogger("aaa.bbb.ccc")とした際に、"aaa.bbb.ccc"loggerが定義されていない場合は、"aaa.bbb"logger→"aaa"logger→"root"loggerの順に取得を試みる。
Django定義済みのロガー
logger名 | 役割 |
---|---|
django | Djangoの内部処理ログ |
django.server | サーバーアクセスログ |
settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_false": {
"()": "django.utils.log.RequireDebugFalse",
},
"require_debug_true": {
"()": "django.utils.log.RequireDebugTrue",
},
},
"formatters": {
"django.server": {
"()": "django.utils.log.ServerFormatter",
"format": "[%(server_time)s] %(message)s",
},
"debug_formatter": {
"format": "[%(levelname)s]:%(asctime)s [%(pathname)s:%(lineno)d] %(message)s",
},
"default": {
"format": "[%(levelname)s]:%(asctime)s %(message)s",
},
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "debug_formatter",
},
"file": {
"level": "DEBUG",
"class": "logging.handlers.TimedRotatingFileHandler",
"filename": "./logs/debug.log",
"when": "D",
"interval": 1,
"backupCount": 7,
"formatter": "debug_formatter",
},
"django": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "default",
},
"django.server": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "django.server",
},
},
"loggers": {
"batch": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
"django": {
"handlers": ["django"],
"level": "INFO",
"propagate": False,
},
"django.server": {
"handlers": ["django.server"],
"level": "INFO",
"propagate": False,
},
},
}