Djangoチュートリアルノート
Djangoとは、PythonのWebアプリケーションフレームワークです。
チュートリアルを一通り読み終え、使い方を再確認できるよう整理しました。
内容は入門レベルです。
公式ドキュメント
下の灰色のボタンで表示を絞り込むことができます。
ディレクトリの役割
(外側の)プロジェクト名/
ルートディレクトリ。
プロジェクトのためのコンテナ。
このディレクトリ名は好きな名前に変えることができる。
manage.py
プロジェクトを操作できるようにするコマンドラインユーティリティ。
(内側の)プロジェクト名/
プロジェクトのためのPythonパッケージ。
このディレクトリ名はその中の何かをインポートするために必要なPythonパッケージ名(例: mysite.urls)。
プロジェクト名/__init__.py
PythonにこのディレクトリがPythonパッケージと見なされることを伝える空のファイル。
プロジェクト名/settings.py
Djangoプロジェクトのための設定/構成。
Django settingsは、settingsがどのように機能するかについて教える。
プロジェクト名/urls.py
プロジェクトのURL宣言で、Djangoを使ったサイトの「目次」。
プロジェクト名/asgi.py
ASGI互換のWebサーバがプロジェクトにサービスを提供するためのエントリポイント。
プロジェクト名/wsgi.py
WSGI互換のウェブサーバがプロジェクトにサービスを提供するためのエントリポイント。
コマンドの使用例
インストールの確認
$ python -m django --version
開発用サーバの起動
$ python manage.py runserver
プロジェクトの作成
$ django-admin startproject mysite
アプリケーションの作成
$ python manage.py startapp アプリ名
マイグレーションの作成
$ python manage.py makemigrations アプリ名
モデル(models.py)変更のため
データベースに変更を適用
$ python manage.py migrate
モデルのテーブルをデータベースに作成する。
開発等の利便性のため意図的にマイグレーション作成と適用を分割している。
マイグレーションが実行するSQLの表示
$ python manage.py sqlmigrate アプリ名 0001
0001は適宜変更。
プロジェクトに問題がないか確認する
$ python manage.py check
管理ユーザー作成
$ python manage.py createsuperuser
設定
プロジェクト名/settings.py
INSTALLED_APPS = [
'アプリ名.apps.アプリ名Config',
'django.contrib.staticfiles',
略
]
STATIC_URL = 'static/'
# {% load static %}で相対パスを生成する。
# <img src="{% static 'アプリ名/ファイル名.jpg' %}">
STATICFILES_DIRS = [
BASE_DIR / "static",
'/var/www/static/',
]
STATIC_ROOT = "/var/www/example.com/static/"
TIME_ZONEを設定
# datetime.datetime.now()ではなく、timezone.now()を使う。
静的ファイルの配置
アプリ名/static/アプリ名/style.css
アプリ名/static/アプリ名/example.jpg
静的ファイルを本番環境で使う場合
各staticフォルダからSTATIC_ROOTのディレクトリにファイルをコピー
$ python manage.py collectstatic
STATIC_ROOTに置かれたファイルをSTATIC_URLから配信するようWebサーバで設定
viewの例
アプリ名/views
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils import timezone
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'アプリ名/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'アプリ名/detail.html'
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now())
class ResultsView(generic.DetailView):
model = Question
template_name = 'アプリ名/results.html'
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
# request.POSTの値は常に文字列
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'アプリ名/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('アプリ名:results', args=(question.id,)))
urls.pyの例
プロジェクト名/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('アプリ名/', include('アプリ名.urls')),
path('admin/', admin.site.urls),
]
アプリ名/urls.py
from django.urls import path
from . import views
app_name = 'アプリ名'
# DjangoがURLに対して作成するアプリビューを認識できるようにする
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
# /アプリ名/ nameは{% url %}で呼び出すための名前
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
# /アプリ名/5/
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
# /アプリ名/5/results/
path('<int:変数A>/vote/', views.vote, name='vote'),
# /アプリ名/5/vote/
]
# <>の部分: URLの一部を「キャプチャ」して、ビュー関数にキーワード引数として送信される。
モデルの例
アプリ名/models.py
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
# 各クラス変数はモデルのデータベースフィールドを表現し、列の名前に使われる。
# pub_dateのように最初の引数に分かりやすいフィールド名を指定できる。
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <=now
class Choice(models.Model):
# ForeinKeyで連携を定義する
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
テンプレート
変数
{{ variable }}
# context = {'variable': 'value'}
{{ value|default:"nothing" }}
# 変数がない場合のデフォルト値を設定
自動エスケープ
デフォルトではテンプレートの変数タグの出力は自動的にエスケープされる。
< > ' " &
自動エスケープを無効にするにはsafeフィルタを使う。
{{ data|safe }}
またはautoscapeタグを使う。
{% autoescape off %}
{{ data }}
{% endautoescape %}
辞書のキー
{{ my_dict.key }}
属性
{{ my_object.attribute }}
リスト
{{ my_list.0 }}
長さを出力
{{ my_list|length }}
条件式
{% if 条件 %}
処理
{% elif 条件 %}
処理
{% else %}
処理
{% endif %}
ループ
{% for i in リスト名 %}
{{ i }}
{% endfor %}
ループ(辞書)
{% for k, v in 辞書名.items %} # 辞書名の後ろに.itemsを付ける。
{{ k }}{{ v }}
{% endfor %}
ループ(空リストの処理)
{% for i in リスト名 %}
{{ i }}
{% empty %} # リストに中身がないとき。
処理
{% endfor %}
ループ(スライス)
{% for i in リスト名|slice:"2:5" %}
{{i}}
{% endfor %}
ループ(1回目のみ別処理)
{% for i in リスト名 %}
{% if forloop.first %} # ループ1回目のみTrueとなる
処理 {{ i }}
{% else %}
処理 {{ i }}
{% endif %}
{% endfor %}
ループ(最後のみ別処理)
{% for i in リスト名 %}
{% if forloop.last %} # 最後のループのみTrue
{{ i }}
{% else %}
{{ i }}
{% endif %}
{% endfor %}
ループカウント
{% for i in リスト名 %}
{{ i }}
{{ forloop.counter0 }} # 1回目のループは0、2回目は1
{{ forloop.counter }} # 1回目は1
{{ forloop.recounter0 }} # 1回目はn、最後は1
{{ forloop.recounter }} # 1回目はn-1、最後は0
{% endfor %}
ループ(数回おきの処理)
{% for i in リスト名 %}
{% if forloop.counter|divisibleby:'3' %} # 3で割り切れるときTrue
{{forloop.counter}}
{% else %}
{{i}}
{% endif %}
テンプレートの挿入
{% include "ファイル名.html" %}
ブロック化
{% extends 'ファイルの場所/ファイル名.html' %}
{% block ブロック名 %}
上書きする内容
{% endblock %}
記入例(アプリ名/templates/アプリ名/index.html)
{% load static %}
<link rel="stylesheet" href=" {% static 'アプリ名/style.css' %}">
{% if latest_question_list %}
{% for question in latest_question_list %}
<a href="{% url 'アプリ名:detail' question.id %}">{{ question.question_text }}</a>
{% endfor %}
{% else %}
No アプリ名 are available.
{% endif %}
アプリ名/templates/アプリ名/detail.html
<form action="{% url 'アプリ名:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend>{{ question.question_text }}</legend>
{% if error_message %}{{ error_message }}{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label>
{% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
継承
base.html
<head>
<link rel="stylesheet" href="style.css">
<title>{% block title %}タイトル{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<a href="/">Home</a>
<a href="/blog/">Blog</a>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
extends
{% extends "base.html" %}
{% block title %}タイトル{% endblock %}
{% block content %}
{% for entry in blog_entries %}
{{ entry.title }}
{{ entry.body }}
{% endfor %}
# 可読性のために{% endblock content %}も可
{% endblock %}
form
公式: FormsAPI
公式: Form fields
公式: Widgets
使用例(forms.py)
from django import forms
class クラス名(forms.Form):
変数名 = forms.BooleanField(label='ラベル名', initial=False)
変数名 = forms.CharField(max_length=100)
# デフォルトのtypeを変更する
変数名 = forms.CharField(widget=forms.Textarea)
ビューの例(viws.py)
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import クラス名
def 関数名(request):
# POSTリクエストの場合にフォームデータを処理する
if request.method == 'POST':
# フォームのインスタンスを作成し、リクエストからのデータを入力する
form = クラス名(request.POST)
# 有効なデータを含むかチェック(cleaned_data属性にデータを配置し、Trueを返す)
# form.cleaned_data辞書には、例えばIntegerFieldはintのデータに変換して格納される
if form.is_valid():
変数名 = form.cleaned_data['変数名']
# 新しいURLにリダイレクト
return HttpResponseRedirect('URL')
else:
# 空白のフォームを作成
form = クラス名()
return render(request, 'htmlのURL', {'form': form})
formのテンプレート(html)
<form action="URL" method="post">
{% csrf_token %}
<table>
<tr>
<th>変数名1</th>
<td>{{ form.変数名1 }}</td>
</tr>
<tr>
<th>変数名2</th>
<td>{{ form.変数名2 }}</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
adminフォーム
アプリ名/admin.py
from django.contrib import admin
from .models import Question
admin.site.register(Question)
リファレンス
テスト
公式リファレンス
モデルやビュー毎にTestClassを分割する。
テストしたい各条件に対して異なるテストメソッドを作る。
テストメソッドの名前で機能を説明する。
記入例(アプリ名/tests.py)
from django.test import TestCase
from django.urls import reverse
from .models import Question
class QuestionModelTests(TestCase):
テスト内容
テンプレートタグ
ディレクトリの準備
アプリのディレクトリの下にtemplatetagsディレクトリを作る。
(templatesディレクトリと同じ階層)
templatetagsの下に__init__.pyを作る。
(__init__.pyは中身が空のファイル)
pythonファイルを作る。
(今回の例ではキャッシュを無効にするcache_busting.pyを作成)
機能(templatetags/cache_busting.py)
from django import template
from django.templatetags.static import static
from datetime import datetime
register = template.Library()
@register.simple_tag
def static_cache(filepath):
# Django標準機能を使ってhtml埋め込み用のパスを取得
res_path = static(filepath)
# 年月日を取得
now = datetime.now()
ymd = f"{now.year}{now.month}{now.day}"
# パスに結合
res_path += '?v=' + ymd
return res_path
テンプレート(htmlファイル)
{% load cache_busting %}
# cssファイルを引数として、「機能」の項目で作ったstatic_cache関数を実行する。
<link rel="stylesheet" href="{% static_cache 'css/example.css' %}">
# <link rel="stylesheet" href="CSSファイルのある場所/example.css?v=20230824">のように変換される。
補足:
?の後ろはどんな文字列でもよく、前回読み込んだ時と違う文字になっていれば再読み込みされる。
この例は日付が変われば再読み込みするようにしている。
CSSファイルの更新日時を文字列にして、更新した時だけ再読み込みさせる方が実用的。
(更新時間は、ファイルパス.stat().st_mtimeで取得できる)