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で取得できる)