Django♪ファイルがたくさんあって、データの流れが分からない

Djangoに慣れないうちは、ファイル間のデータの流れを理解するのが大変です。そこで、urls.py, views.py, forms.py, html, cssといったファイル名を使った図を使い、データの流れをマスターしたいと思います。

なお、この記事ではデータベース(モデル)の説明はしません。まずはデータベースを除いた部分のデータの流れをしっかりと覚える必要があるからです。

1.データの流れを図でつかもう

それでは、早速データの流れを整理してみましょう。なお、少し読みにくい場合は、先に「2. サンプルプログラム」から読んでみてください。

(1) 最初のアクセス時のデータの流れ

サイトに最初にアクセスしたときのデータの流れです。実際のデータのフローを厳密に表現したものではありませんが、こちらの方がファイル間のデータの参照が理解しやすいと思います。

最初のアクセス時のフロー図

クライアント側のwebブラウザに表示したいサイトのURL(例えば「http://localhost:8000/test_app/practice」)を入力し、実行した場合を考えてみます。

Djangoのurls.pyは仕事を割り振るのが役割ですから、urls.pyではリクエストされたURLより、実行するviews.pyの関数(クラス)を選びます。

views.pyは、データを受け取り、pythonのコードで処理し、表示する値をhtmlファイルに渡すのが役割です。また、htmlファイルに渡すデータは、辞書型データで作成します。

なお、forms.pyで記述したFormクラスのインスタンスも辞書の値としてhtmlファイルに渡すことができます。

辞書を受け取ったhtmlファイルでは、例えば{{form}}のように、辞書のキー名を{{}}で囲むことによって、辞書の値を表示することができます。

最後にDjangoはリクエストに対するレスポンスとして、「htmlファイル」「cssファイル」「views.pyで作成した辞書型データ」をクライアントのwebブラウザに返します。

すると、webブラウザにURLに対応した画面が表示されます。

なお、htmlファイルやcssファイルはフロントエンドなので、コードの内容はクライアントに公開されます。一方、views.pyやforms.pyはバックエンドであり、クライアントには公開されません。

ここで、覚えなければならないデータの流れと、表示方法はいかの通りです。

  • htmlファイル(テンプレート)に渡す値は、views.pyで辞書型データとして定義され、htmlファイルに渡される。
  • htmlファイルでは辞書のキーを使って辞書の値が表示される。例:{{辞書のキー}}
  • forms.pyで定義されるクラスのインスタンスにはhtmlファイルからは直接アクセスしない。

つまり、htmlファイルの{{●●}}の部分は、基本的に全てviews.pyで作成された「辞書のキー」なのです。

それをしっかり覚えていないと、「えっと、{{form}}のformってなんだっけ?どのファイルにあるんだっけ?」となります。

これは、フィールド名を定義したforms.pyのFormクラスについても同様です。Formクラスのインスタンスはhtmlファイルから直接呼び出されることはなく、views.py経由で辞書のとしてhtmlファイルに渡されます。

具体例としては、後述のサンプルプログラム(コード05)を見てください。htmlファイルからは全てviews.pyで定義した辞書のキー'message', 'aberage', 'form'にアクセスし、forms.pyで定義されたクラスのインスタンスTestForm()は辞書の値として渡されます。

#後述サンプルコード05(views.py)からの抜粋
def __init__(self):
    self.params = {
            'message': 'テストの点数を入力し、計算ボタンを押して下さい。',
            'average': '',
            'form': TestForm(),
        }

以上のように、htmlファイルからはviews.pyで定義した辞書のキーを参照するというルールを徹底させることでデータの流れを明快にしています。

(2) アクセス2回目以降のデータの流れ

以下、2回目以降のアクセス時のデータの流れを示す図です。具体的には1回目に開いたwebサイトのフィールド(入力フォーム等)にデータを入力し、更新ボタンを押すような場合のことです。

ここで重要なのは、サーバーにあったhtmlファイルは、1回目の閲覧で既にクライアントのPCに渡されており、クライアント側がwebページを更新するときは、このhtmlファイルを使って、サーバー側にリクエストを送信するということです。

つまり、ファイル間の参照は基本的にクライアント側のhtmlファイルと、サーバー側のurls.py、views.pyとのやりとりを記述することになります。

アクセス2回目以降のフロー図

例えばwebブラウザの画面上のテキストフィールドに名前や年齢を入力し、送信ボタンをクリックしたとします。

Webブラウザは、初回アクセス時にDjangoからhtmlファイルを受け取っているので、そのhtmlファイルを使って、更新に必要なデータをサーバー側のDjangoに送ります。

ここで、更新に必要なデータとは「更新後に表示するURL」と「webブラウザ上のフィールドに入力したデータ」です。

更新後に表示する「URL」は最初にアクセスしたURLと同じであるとは限りません。したがって、再びURLを指定する必要があるのです。このURLはhtmlファイルに記述しています。

また、「webブラウザ上のフィールドに入力したデータ」には、htmlファイルに記述した「フィールド名」をセットにして送ります。

つまり、更新に必要なデータは全てhtmlファイルに記述されているのです。

Django側では、先ほどと同様にurls.pyでは受け取ったURLより、実行するviews.pyの関数(クラス)を選び、その関数を実行します。関数には「フィールド名」と「フィールドに入力された値」が渡されます。

views.pyでは、受け取ったフィールドの値をrequest.POST('フィールド名’)のような書式で利用することができます。初回のアクセスと違うのは、フィールドに入力されたデータを使って計算できることです。

その後のフローは初回と同様であり、新しい辞書型データをhtmlファイルに渡し、レスポンスとしてwebブラウザにhtmlファイル、cssファイル、辞書型データを返し、webブラウザの表示が更新されます。

ここで、覚えなければならないデータの流れと、表示方法はいかの通りです。

  • views.py(ビュー)にはクライアントのwebブラウザから「フィールド名」と「フィールドの値」が渡される。
  • views.pyでは、フォールド名を使ってフィールドの値を利用する。例:request.POST('フィールド名’)
  • フィールド名はhtmlファイルで定義されるだけではなく、forms.pyでも定義される。

フィールド名をhtmlファイルで定義する場合は、「<input id="○○" type="text" name="フィールド名"」の書式でhtmlファイルに記述します。

フィールド名をforms.pyで定義する場合は「フィールド名=forms.CharField(label='○○')」の書式でforms.pyに記述します。

htmlファイルがveiws.pyのデータを参照するときは「辞書のキー」を使いましたが、views.pyがhtmlファイルのデータを参照するときは「フィールド名」を使います。

htmlファイルで使う辞書はveiws.pyで作成されましたが、views.pyで使うフィールド名は、htmlファイル内で定義されるだけではなく、forms.pyでも定義されるので注意が必要です。

フィールドはhtmlファイルに配置された入力フォーム等のことですから、本来はhtmlファイルで定義されるのが自然ですが、Djangoがフィールドの定義を肩代わりしているのです。

Djangoのviews.pyがhtmlファイルに対して「あなた(htmlファイル)が定義するフィールドは使いにくいから、私(views.py)が用意したフィールドの定義(forms.py)を使ってね!」と、強引に辞書データを渡すイメージです。

2.サンプルプログラム

サンプルプロジェクトを紹介します。なお、プロジェクト名、アプリケーション名、プロジェクトのディレクトリ構成は以下の通りです。

  • プロジェクト名 → test_pro
  • アプリケーション名 → test_app
プロジェクトの構成

(1) settings.py

前準備としてsettings.pyのINSTALLED_APPS[]には、'test_app',を追加します。

#コード01
#\test_pro\test_app\settings.py

        ・
        ・
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'test_app',
]
        ・
        ・

(2) urls.py

urls.pyは、クライアントからリクエストされたURLに対して実行するviews.pyの関数を指定します。urls.pyは「\test_pro\test_pro\urls.py」と「\test_pro\test_app\urls.py」の2カ所に存在しており、プロジェクト全体のurls.pyである「\test_pro\test_pro\urls.py」からアプリケーションのurls.pyである「\test_pro\test_app\urls.py」を呼出します。

コード02は「\test_pro\test_pro\urls.py」であり、webブラウザからURL「http://localhost:8000/test_app/・・・」をリクエストされたときに、「\test_pro\test_app\urls.py」のurls.pyを呼出します。

#コード02
#\test_pro\test_pro\urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('test_app/', include('test_app.urls')),
]

コード03のurls.pyではURL「http://localhost:8000/test_app/practice」に対して、クラスTestView()を実行するように記述されています。

5行目の「from .views import TestView」では、ディレクトリtest_app内のviews.pyから、自作のクラスTestView()を呼出しています。viewsの前の「.」はアプリtest_appのカレントディレクトリであることを示します。

なお、コード02の9行目のpath()やコード03の8行目のpath()の第1引数は、実行するファイルのパスを示しているわけではありません。webブラウザから呼出すURLの名称であり、自由に名称を決めることができます。

実行するファイルの位置は、5行目のimport文と8行目のurl()の第2引数から判断します。

urls.pyの役割は、第1引数で定めたURLをリクエストされた時に、第2引数の関数(クラス)を呼出すことなのです。

#コード03
#\test_pro\test_app\urls.py

from django.urls import path
from .views import TestView

urlpatterns = [
    path(r'practice', TestView.as_view(), name='testview'),
]

基本的に、urls.pyは求められたURLに対して、実行する関数を呼び出しますが、URLではなく8行目path()の第3引数「name='testview'」から呼出すこともできます。そして、htmlファイルからの呼び出しは主に第3引数を使います。

具体的には、「"http://localhost:8000/test_app/practice"」を「{% url 'testview' %}」と記述することが可能です。この表記方法を使えば、ドメインである「"http://localhost:8000/」を記述する必要がありませんし、URLとは独立して自由な名前をつけることができます。

webブラウザから呼出す名称と、htmlファイルから呼出す名称を分けて運用できるのです。

(3) forms.py

forms.pyに記述されるクラスは「django.forms.Form」を継承したクラスです。

web画面上へのフィールド(テキストフィールドなど)の配置はhtmlファイルで行いますので、流れとしてはフィールド名の定義もhtmlファイルで行った方が自然です。しかし、Djangoではforms.pyで高機能なフィールドのクラスを定義することができ、views.py経由でhtmlファイルにインスタンスを渡すことができます。

先ほど例えた通り、Djangoのviews.pyがhtmlファイルに対して「あなた(htmlファイル)が定義するフィールドは使いにくいから、私(views.py)が用意したフィールドの定義(forms.py)を使ってね!」と、強引に辞書データを渡すイメージです。

コード04では7~10行目がフィールドを定義した部分であり、「=」の左辺の「kokugo, sannsuu, rika, syakai」がフィールド名です。

webブラウザの画面上で入力された値をviews.pyで呼出す場合は、このフィールド名が使われます。

#コード04
#\test_pro\test_app\forms.py

from django import forms

class TestForm(forms.Form):
    kokugo = forms.IntegerField(label='国語')
    sannsuu = forms.IntegerField(label='算数')
    rika = forms.IntegerField(label='理科')
    syakai = forms.IntegerField(label='社会')

forms.pyの「クラスTestForm」は、views.pyにimportされ、そのインスタンスが辞書型データの値としてhtmlファイルに渡されます。そして、このforms.pyに記述されたクラスは、htmlファイルから直接呼び出されることはありません。

(4) views.py

views.pyでは、webブラウザ上のフィールドに入力された値を使って更新後のデータを作成し、辞書型のデータにまとめ、htmlファイルに渡します。

コード05の19, 37行目「return render(request, 'test_app/index.html', self.params)」の第2引数は、どのhtmlファイルにデータを渡すかを示します。また、第3引数はhtmlファイルに渡す辞書型データです。

#コード05
#\test_pro\test_app\views.py

from django import forms
from django.shortcuts import render
from django.views.generic import TemplateView
from .forms import TestForm

class TestView(TemplateView):

    def __init__(self):
        self.params = {
                'message': 'テストの点数を入力し、計算ボタンを押して下さい。',
                'average': '',
                'form': TestForm(),
            }

    def get(self, request):
        return render(request, 'test_app/index.html', self.params)

    def post(self, request):
        goal = request.POST['goal']
        ko_score = int(request.POST['kokugo'])
        sa_score = int(request.POST['sannsuu'])
        ri_score = int(request.POST['rika'])
        sya_score = int(request.POST['syakai'])
        ave = (ko_score+sa_score+ri_score+sya_score) / 4
        if goal == '':
            msg = '目標平均点が未入力です。'
        elif int(goal) > ave:
            msg =  '残念!目標平均点は(' + str(goal) + '点)です。'
        else:
            msg =  '目標達成!目標平均点は(' + str(goal) + '点)です。'
        self.params['average'] = ave
        self.params['message'] = msg
        self.params['form'] = TestForm(request.POST)
        return render(request, 'test_app/index.html', self.params)

webブラウザ上のフィールドに入力されたデータは、request.POST['フィールド名']の書式で呼出します。なお、フィールド名はforms.pyで定義されるだけではなく、htmlファイル内でも定義されます。

また、36行目のように「self.params['form'] = TestForm(request.POST)」とすると、現在のフィールド内のデータが更新後のフィールドに引き継がれます。

一番最初のアクセスではフィールド内にデータがありませんので、15行目の「'form': TestForm()」のように、request.POSTをTestForm()の引数として渡しません。

(5) index.html

htmlファイルで、他のファイルとつながっている箇所は以下の通りです。

  • 4, 10, 11行目 → style.css
  • 16行目の「'testview'」 → urls.pyの「url(r'practice', TestView.as_view(), name='testview')」
  • 20, 21, 25行目の「{{辞書paramsのキー}}」 → views.pyから渡された辞書paramsのキー

19行目のフィールドや、20行目のformで定義されたフィールド(forms.py参照)は、必ず「<form action="{% url 'testview' %}" method="post">」と「</form>」の間に記述します。そして、22行目「input type="submit" value="計算開始”」のボタンが押されると、この範囲のフィールドに入力されている値がクライアントのwebブラウザによりurls.py経由でviews.pyに渡されます。

なお、更新後に呼出すURLの指定は「action="{% url 'testview' %}"」の部分であり、「action="http://localhost:8000/test_app/practice"」のように直接URLを指定することも可能です。

19行目の「<input id="goal_id" type="number" name="goal">」は、フィールドの定義をforms.pyではなくhtmlファイルの中で行った例です。「name=」の後の「"goal"」がフィールド名です。

HTMLの機能として、18行目のlabelの「for="goal_id"」は、19行目のinputの「id="goal_id"」と関連付けるもので、例えばlabelをクリックすると、入力欄がアクティブになるといった効果があります。

なお、htlmファイルのタグでフィールドを定義した場合は、forms.pyのクラスのように更新前のデータを引き継ぐことができませんので、更新後は初期化され空白になります。

<!--  コード06  -->
<!--  \test_pro\test_app\templates\test_app\index.html  -->

{% load static %}
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>目標達成判定</title>
    <link rel = "stylesheet" type = "text/css"
        href = "{% static 'test_app/css/style.css' %}" />
</head>
<body>
    <h1>テストの目標達成判定プログラム</h1>
    <table>
    <form action="{% url 'testview' %}" method="post">
        {% csrf_token %}
        <tr><th><label for="goal_id">目標平均点:</label></th>
        <td><input id="goal_id" type="number" name="goal"></td></tr>
        {{form.as_table}}
        <tr><th>平均点:</th><td>{{average}}</td></tr>
        <tr><td></td><td><input type="submit" value="計算開始"></td></tr>
    </form>
    </table>
    <p>{{message}}</p>
</body>
</html>

フィールド名の定義が、htmlファイルで行われる場合とforms.pyで行われる場合があるために、少し複雑に感じますが、他のファイルとのデータのやりとりを正確に覚えましょう。

(6) style.css

cssファイルは、htmlファイルの書式を設定するファイルです。htmlファイルから呼出されます。特にDjangoだからといって内容は変わりません。

/*  コード07  */
/*  \test_pro\test_app\static\test_app\css\style.css  */

body {
    font-size:12pt;
}

h1 {
    color:blue;
    font-size:20pt;
}

p {
    color:red;
    font-size:14pt;
}

3.サンプルプログラムの操作画面

実行画面は以下の通りです。目標平均点はhtmlファイルのタグで定義したフィールドであるため、ボタンを押すたびに初期化されますが、forms.pyのクラスを用いたフィールドはフィールド内のデータを維持することができます。

(1) 起動画面

「http://ドメイン/test_app/practice」にアクセスした画面です。

サンプルの画面_初期画面

(2) フィールドに入力

目標平均点と各科目の点数を入力するフィールドに点数を入力しました。

サンプルの画面_入力

(3) 点数が目標よりも低い場合

データ入力後、計算開始ボタンを押した結果です。

目標平均66点に対して、平均の方が42.25点と低い場合は以下のような画面に更新されます。目標平均点はforms.pyのクラスを利用していないので、計算開始ボタンを押すと初期化され空白になります。

サンプルの画面_目標より低い場合

(4) 点数が目標よりも高い場合

目標点数を15点にして計算開始ボタンを押した結果です。下のメッセージの表示が変わりました。

サンプルの画面_目標より高い場合

(5) 目標点を入れずに計算開始した場合

目標点に未入力でボタンを押すと、下のメッセージで「目標平均点が未入力です。」と表示されます。

サンプルの画面_目標が未入力の場合

4.あとがき

Djangoの勉強を始めたとき、データの流れの全体像がつかめず暗闇の中を走っているような感覚だったので、この記事を書いてみました。

最初のハードルはデータの流れをつかむことだと思います。私も勉強中ですので、この記事がきっかけでDjango仲間が増えるとうれしいです。

私が実際にレンタルしたVPSサーバー

私が実際にレンタルしたVPSサーバーはConoHa VPSです。私は1GBのプランを申し込みました。VPSサーバーは一般のレンタルサーバーと異なりOSやアプリケーションを自由に設定できるので、Pythonで計算した結果をサイトに表示することもできます。

なお、ConoHa VPSの特長として、サーバーのディスクイメージを丸ごとバックアップできるイメージ保存機能を無料で使用することができます。コードを変更して元に戻せなくなった場合にも安心です。

また、ConoHa VPSは途中でプランをスケールアップできるだけでなく、スケールダウンすることもできます。つまり、2Gプランを1Gプランに変更することができます。ただし、512MBプランだけはスケールアップ・ダウン機能が使用できないので注意してください。

また、初期費用なしで3日だけ借り、3日分の費用だけ払うといったことも可能なので気軽に始められます。※時間課金(月の上限額は決まっています)

私からの友達紹介でクーポンをゲット

なお、以下のリンクから入ると、私からの友達紹介の扱いとなり1000円分のクーポンが支給されます。リンク先には「友達紹介」といった表記がないので「友達紹介」が適用されているのか不安になりますが大丈夫です。登録終了後にログインしたユーザーTop画面を確認すると支給されたクーポン1000円が表示されるはずです。

[ Conohaの友達紹介 ]

なお、友達紹介では個人情報が紹介者には開示されないので安心してご利用ください。

その他

Twitterへのリンクです。SNSもはじめました♪

液晶ペンタブレットを購入しました
 (1) モバイルディスプレイを買うつもりだったのに激安ペンタブレット購入

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?