Notes

はじめに

  • これまで、HTMLとCSSを使って簡単なWebページを構築する方法、またGitとGitHubを使ってコードの変更を追跡し、他の人と共同作業する方法を説明してきました。また、Pythonプログラミング言語にも触れました。
  • 今日は、Pythonの Django フレームワークを使って動的なアプリケーションを作成する作業に取り掛かります。

Webアプリケーション

これまでのところ、私たちが作成したWebアプリケーションはすべて静的です。つまり、そのWebページを開くたびに、まったく同じに見えます。しかし、私たちが毎日訪れる多くのウェブサイトは、私たちが訪れるたびに変わります。例えば、New York TimesFacebookのウェブサイトを訪問すると、おそらく今日と明日では違ったものを見ることになるでしょう。このような大規模なサイトでは、変更が行われるたびに従業員が大きなHTMLファイルを手動で編集しなければならないのは無理がありますから、動的なWebサイトは非常に便利です。動的Webサイトは、プログラミング言語(Pythonなど)を利用してHTMLおよびCSSファイルを動的に生成するWebサイトです。この講義では、最初の動的アプリケーションの作成方法について学習します。

HTTP

HTTP (HyperText Transfer Protocol) は、インターネット上でメッセージを送受信するためのプロトコルとして広く受け入れられています。通常、オンライン情報はクライアント (ユーザ) とサーバの間で渡されます。

Client and Server

このプロトコルでは、以下のようにクライアントはサーバにリクエストを送信します。以下の例では、GET は単なる要求のタイプであり、このコースで説明する3つのうちの1つです。/ は通常、Webサイトのホームページを探していることを示し、3つのドットは、さらに多くの情報を渡すことができることを示します。

Request

要求を受信すると、サーバはHTTP応答を返します。この応答は次のようになります。このような応答には、HTTPバージョン、ステータスコード(200はOKを意味します)、コンテンツの説明、および追加情報が含まれます。

Response

200は多くのステータスコードのひとつで、以下のコードのいくつかは見たことがあるかもしれません。

Codes

Django

Django はPythonベースのWebフレームワークで、HTMLとCSSを動的に生成するPythonコードを作成できます。Djangoのようなフレームワークを使用する利点は、多くのコードがすでに作成されており、それを利用できることです。

  • まず、Djangoをインストールする必要があります。つまり、pipをまだインストールしていない場合は、pipもインストールする必要があります。
  • Pipをインストールしたら、ターミナルで pip3 install Django を実行してDjangoをインストールできます。

Djangoをインストールしたら、次のようにして新しいDjangoプロジェクトを作成します。

  1. django-admin startproject PROJECT_NAME を実行して、プロジェクトのスターターファイルをいくつか作成します。
  2. cd PROJECT_NAME を実行して、新しいプロジェクトのディレクトリに移動します。
  3. 目的のテキストエディタでディレクトリを開きます。いくつかのファイルが作成されています。現時点では、これらのほとんどを確認する必要はありませんが、最初から非常に重要な3つのファイルがあります。
    • manage.py は、ターミナルでコマンドを実行するために使用します。編集する必要はありませんが、頻繁に使用します。
    • settings.py には、新しいプロジェクト用の重要な構成設定がいくつか含まれています。デフォルトの設定もいくつかありますが、そのうちのいくつかは必要に応じて変更します。
    • urls.py には、特定のURLにアクセスした場合のルーティングに関する指示が含まれています。
  1.  python manage.py runserver を実行してプロジェクトを起動します。これにより、開発サーバーが起動します。このサーバーにアクセスするには、提供されているURLにアクセスします。この開発サーバーはローカルマシンで実行されているため、他のユーザはWebサイトにアクセスできません。デフォルトのランディングページが表示されます。
    Landing Page
  2. 次に、アプリケーションを作成します。Djangoプロジェクトは1つ以上のアプリケーションに分割されます。私たちのプロジェクトのほとんどは1つのアプリケーションしか必要としませんが、大規模なサイトはこの機能を利用して1つのサイトを複数のアプリケーションに分割することができます。アプリケーションを作成するには、python manage.py startapp APP_NAME を実行します。これにより、views.py を含む、すぐに役立つディレクトリとファイルが作成されます。
  3. 次に、新しいアプリケーションをインストールします。これを行うには、settings.pyに移動し、INSTALLED_APPS のリストまで下にスクロールして、このリストに新しいアプリケーションの名前を追加します。
installed apps

ルーティング

アプリケーションを開始するには、次の手順に従います。

  • 1. 次に、views.py に移動します。このファイルには、いくつかの異なるビューが含まれています。ここでは、ビューをユーザが見たいと思う1つのページと考えることができます。最初のビューを作成するために、requestを取り込む関数を作成します。ここでは、単純に HttpResponse  (応答コード200とWebブラウザに表示可能なテキスト文字列を含む非常に単純な応答) として「Hello, World」を返します。これを行うために、django.http import HttpResponse を含めました。ファイルは次のようになります。
from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def index(request):
    return HttpResponse("Hello, world!")
  • 2. 次に、作成したビューを特定のURLに関連付ける必要があります。これを行うには、views.py と同じディレクトリに urls.py という別のファイルを作成します。プロジェクト全体の urls.py ファイルはすでにありますが、アプリケーションごとに個別のファイルを用意することをお勧めします。
  • 3. 新しい urls.py の中に、ユーザが私たちのウェブサイトを使っている間に訪問するかもしれないURLパターンのリストを作ります。これを行うには、次の手順を実行します。
    1. いくつかのインポートを行わなければなりません: from django.urls import path によって、URLをリルートすることができます。from . import viewsはviews.py で作成した関数をインポートします。
    2. urlpatterns というリストを作成します。
    3. 必要なURLごとに、URLパスを表す文字列、そのURLにアクセスされたときに呼び出す views.py の関数、および (オプションで) path の名前を name="something" の形式で指定し、path 関数の呼び出しを含む urlpatterns リストに項目を追加します。例えば、私たちのシンプルなアプリが今どうなっているかを見てみましょう。
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index")
]
  • 4. ここで、この特定のアプリケーション用に urls.py を作成しました。次に、プロジェクト全体用に作成した urls.py を編集します。このファイルを開くと、admin というパスがすでに存在することがわかります。このパスについては、後の講義で説明します。新しいアプリに別のパスを追加したいので、urlpatterns リストに項目を追加します。これは、2番目の引数としてviews.pyから関数を追加するのではなく、urls.pyファイルからのすべてのパスをアプリケーションに含めることを除いて、以前のパスと同じパターンに従います。これを行うには、include("APP_NAME.urls") と記述します。ここで、include は、以下の urls.py に示すように、django.urls から include を読み込むことでアクセスできる関数です。
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/', include("hello.urls"))
]
  • 5. これにより、ユーザがこのサイトにアクセスし、検索バーでURLに /hello を追加すると、新しいアプリケーション内のパスにリダイレクトされることを指定しました。

python manage.py runserver を使ってアプリケーションを起動し、提供されたURLにアクセスすると、次のような画面が表示されます。wrong urlこれは、URL localhost:8000/hello のみを定義しているのに、URL localhost:8000 を定義せず、最後に何も追加していないためです。そこで、検索バーのURLに /hello を追加すると、Hello, world 

ある程度の成功を収めたので、これまでの経緯を見てみましょう。

  1. localhost:8000/hello/ というURLにアクセスすると、DjangoはベースURL ( localhost:8000/ ) の後にあるものを調べ、プロジェクトの urls.py ファイルにアクセスして、helloに一致するパターンを検索しました。
  2. 拡張子を定義したため、この拡張子が見つかったのです。そして、この拡張子を見つけるには、アプリケーション内から urls.py ファイルを include する必要があることがわかりました。
  3. その後、DjangoはURLの一部を無視し( localhost:8000/hello/ 、またはそのすべて)、他の urls.py ファイルの中で、URLの残りの部分と一致するパターンを探しました。
  4. これまでの唯一のパス ( "" ) がURLの残りの部分と一致していたため、そのパスに関連付けられている views.py から関数へとリダイレクトされました。
  5. 最後に、Djangoは views.py 内でその関数を実行し、結果( HttpResponse("Hello, world!") )をWebブラウザーに返しました。

これで、必要に応じて、views.py 内の index 関数を変更して必要なものを返すことができます。変数を追跡し、最終的に何かを返す前に関数内で計算を行うこともできます。

次に、アプリケーションに複数のビューを追加する方法について説明します。BrianとDavidに挨拶するページを作成するために、アプリケーション内の同じステップの多くに従うことができます。

views.py の内容: 

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def index(request):
    return HttpResponse("Hello, world!")

def brian(request):
    return HttpResponse("Hello, Brian!")

def david(request):
    return HttpResponse("Hello, David!")

urls.py の内容 (アプリケーション内)

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("brian", views.brian, name="brian"),
    path("david", views.david, name="david")
]

localhost:8000/hello にアクセスしてもサイトは変更されませんが、URLに brian または david を追加すると別のページが表示されます。

Brian
David

多くのサイトは、URLに含まれるアイテムによってパラメータ化されています。例えば、www.twitter.com/cs50にアクセスするとCS 50のツイートがすべて表示され、www.github.com/cs50にアクセスするとCS 50のGitHubページが表示されます。あなた自身の公開GitHubリポジトリを見つけるには、 www.github.com/YOUR_USERNAME にアクセスすれば大丈夫です。

これがどのように実装されているかを考えると、GitHubやTwitterのようなサイトがユーザごとに個別のURLパスを持つことは不可能に思えますから、もう少し柔軟なパスを作る方法を調べてみましょう。まず、greet という一般的な関数を views.py に追加します。

def greet(request, name):
    return HttpResponse(f"Hello, {name}!")

この関数は、要求だけでなく、ユーザ名の追加引数も受け取り、その名前に基づいてカスタムHTTP応答を返します。次に、urls.py でより柔軟なパスを作成する必要があります。これは次のようになります。

path("<str:name>", views.greet, name="greet")

これは新しい構文ですが、基本的には、URLで特定の単語や名前を検索するのではなく、ユーザが入力する文字列を検索することになります。さて、このサイトを他のいくつかのURLで試してみましょう。harry connor

文字列を大文字にするPythonのcapitalize関数を利用するようにgreet関数を拡張することで、これらをもう少しきれいに見せることもできます。

def greet(request, name):
    return HttpResponse(f"Hello, {name.capitalize()}!")
Harry
Connor

これは、Pythonにある機能がDjangoでどのように使用されてから返されるのかを示す良い例です。

テンプレート

これまでのところ、HTTPレスポンスはテキストのみでしたが、任意のHTML要素を含めることができます。例えば、index 関数内において、テキストだけではなく、青いヘッダーを返すようにすることもできます。

def index(request):
    return HttpResponse("<h1 style=\"color:blue\">Hello, world!</h1>")
Blue

views.py 内でHTMLページ全体を作成するのは非常に面倒です。また、プロジェクトの各部分を可能な限り個別のファイルに保存する必要があるため、不適切なデザインにつながります。

そこで今回は、HTMLとCSSを別々のファイルに記述し、それらのファイルをDjangoを使ってレンダリングできるDjangoのテンプレートを紹介します。テンプレートのレンダリングに使用する構文は、次のようになります。

def index(request):
    return render(request, "hello/index.html")

次に、そのテンプレートを作成します。これを行うには、アプリ内に templates というフォルダを作り、その中に hello  (アプリの名前が何であれ)というフォルダを作り、index.html というファイルを追加します。

Files

次に、新しいファイルに必要なものを追加します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>

アプリケーションのメインページにアクセスすると、ヘッダーとタイトルが更新されています。

template0

静的なHTMLページを作成するだけでなく、Djangoのテンプレート言語を使用して、アクセスしたURLに基づいてHTMLファイルのコンテンツを変更することもできます。先ほどの greet 関数を変更して試してみましょう。

def greet(request, name):
    return render(request, "hello/greet.html", {
        "name": name.capitalize()
    })

ここでは、3番目の引数を render 関数に渡しています。これはコンテキストと呼ばれます。このコンテキストでは、HTMLファイル内で使用可能にする情報を提供できます。このコンテキストはPython辞書型の形式をとります。次に、greet.html ファイルを作成します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Hello</title>
    </head>
    <body>
        <h1>Hello, {{ name }}!</h1>
    </body>
</html>

2重中括弧という新しい構文が使用されています。この構文を使用すると、context 引数で指定した変数にアクセスできます。試してみましょう。

Template 1
Template 2

ここまでは、提供するコンテキストに基づいてHTMLテンプレートを変更する方法について説明しました。しかし、Djangoテンプレート言語はそれよりも強力なので、役に立つ他のいくつかの方法を見てみましょう。

条件式

条件によって、ウェブサイトの表示を変更したい場合があります。たとえばwww.isitchristmas.comというサイトにアクセスすると、次のようなページが表示されます。

no

このウェブサイトはクリスマスの日に変更され、ウェブサイトはYESと表示されます。私たちでこのようなもの、つまり元旦であるかどうかをチェックするアプリケーションを作成してみましょう。そのために新しいアプリケーションを作ります。

  • 1. ターミナルで python manage.py startapp newyear を実行します。
  • 2. settings.py を編集し、INSTALLED_APPS の1つとして 「newyear」 を追加します。
  • 3. プロジェクトの urls.py ファイルを編集し、hello アプリケーション用に作成したものと同様のパスを含めます。
path('newyear/', include("newyear.urls"))
  • 4. 新しいアプリケーションのディレクトリ内に別の urls.py ファイルを作成し、hello 内のインデックスパスと同様のパスを含むように更新します。
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
]
  •  5. views.py に index関数を作りましょう。

新しいアプリの準備ができたので、元旦かどうかをチェックする方法を考えてみましょう。これを行うには、Pythonのdatetimeモジュールをインポートします。このモジュールがどのように動作するかを理解するために、ドキュメンテーションを調べ、Pythonインタプリタを使ってDjangoの外部でテストすることができます。

  • Pythonインタプリタは、Pythonコードの小さな塊をテストするために使用できるツールです。これを使用するには、ターミナルで python を実行すると、ターミナル内でPythonコードを入力して実行できるようになります。インタプリタの使用が終了したら、exit() を実行して終了します。interpreter
  • この知識を使用して、今日が新年の日: now.day == 1 および now.month == 1 の場合にのみTrueと評価されるブール式を作成できます。
  • これで、元旦かどうかを評価するための式ができたので、 views.py のindex関数を更新します。
def index(request):
    now = datetime.datetime.now()
    return render(request, "newyear/index.html", {
        "newyear": now.month == 1 and now.day == 1
    })

次に、index.html テンプレートを作成します。templates という名前の新しいフォルダ、newyear という名前のフォルダ、および index.html という名前のファイルを再度作成する必要があります。このファイルには、次のように記述します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Is it New Year's?</title>
    </head>
    <body>
        {% if newyear %}
            <h1>YES</h1>
        {% else %}
            <h1>NO</h1>
        {% endif %}
    </body>
</html>

上記のコードでは、HTMLファイルにロジックを含める場合、論理ステートメントの前後の開始タグと終了タグとして {% および %} を使用しています。また、Djangoのフォーマット言語では、if-else ブロックが終了したことを示す終了タグを含める必要があることにも注意してください。これで、ページを開いて以下を確認できます。

No

さて、この裏で何が起こっているのかを理解するために、このページの要素を見てみましょう。Source 

実際にWebブラウザーに送信されるHTMLにはNOヘッダーしか含まれていないことに注意してください。つまり、Djangoは作成したHTMLテンプレートを使って新しいHTMLファイルを作成し、それをWebブラウザーに送信します。少しごまかして、条件が常に真であることを確認すると、反対のケースが満たされていることがわかります。

def index(request):
    now = datetime.datetime.now()
    return render(request, "newyear/index.html", {
        "newyear": True
    })
Yes
Source 0

スタイリング

CSSファイル (変更されない静的ファイル) を追加する場合は、まず static というフォルダを作成し、その中に newyear フォルダを作成してから、styles.css ファイルを作成します。このファイルでは、最初のレッスンで行ったように、任意のスタイルを追加できます。

h1 {
    font-family: sans-serif;
    font-size: 90px;
    text-align: center;
}

今度は、このスタイル設定をHTMLファイルに組み込むために、HTMLテンプレートの先頭に {% load static %} という行を追加して、Djangoに static フォルダ内のファイルにアクセスしてもらいたいということを通知します。次に、以前のようにスタイルシートへのリンクをハードコーディングするのではなく、Django固有の構文を使用します。

<link rel="stylesheet" href="{% static 'newyear/styles.css' %}">

サーバを再起動すると、スタイル設定の変更が実際に行われたことがわかります。

big no

タスク

では、これまでに学んだことを、TODOリストの作成という小さなプロジェクトに適用してみましょう。新しいアプリを作ることから始めましょう。

  • 1. ターミナルで python manage.py startapp タスクを実行します。
  • 2. settings.py を編集し、INSTALLED_APPS の1つとして 「tasks」 を追加します。
  • 3. プロジェクトの urls.py ファイルを編集し、hello アプリケーション用に作成したものと同様のパスを含めます。
path('tasks/', include("tasks.urls"))
  • 4. 新しいアプリケーションのディレクトリ内に別の urls.py ファイルを作成し、hello 内のインデックスパスと同様のパスを含むように更新します。
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
]
  •  5. views.py にindex関数を作成します。

まず、タスクのリストを作成し、それをページに表示します。views.py の上部にタスクを保存するPythonリストを作成します。次に、index 関数を更新してテンプレートをレンダリングし、新しく作成したリストをコンテキストとして提供します。

from django.shortcuts import render

tasks = ["foo", "bar", "baz"]

# Create your views here.
def index(request):
    return render(request, "tasks/index.html", {
        "tasks": tasks
    })

次に、テンプレートHTMLファイルを作成します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Tasks</title>
    </head>
    <body>
        <ul>
            {% for task in tasks %}
                <li>{{ task }}</li>
            {% endfor %}
        </ul>
    </body>
</html>

ここでは、前述の条件式に似た構文、およびレッスン2のPythonループに似た構文を使用して、タスクをループすることができます。ここでタスクページに移動すると、リストがレンダリングされていることがわかります。

tasks0

フォーム

現在のすべてのタスクをリストとして表示できるようになったので、新しいタスクを追加できるようにします。そのために、フォームを使ってWebページを更新する方法を見ていきましょう。まず、views.py に別の関数を追加します。この関数は、新しいタスクを追加するためのフォームを持つページをレンダリングします。

# Add a new task:
def add(request):
    return render(request, "tasks/add.html")

次に、urls.py に別のパスを追加します。

path("add", views.add, name="add")

ここで、add.html ファイルを作成します。これは index.html によく似ていますが、本文にリストではなくフォームを含めます。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Tasks</title>
    </head>
    <body>
        <h1>Add Task:</h1>
        <form action="">
            <input type="text", name="task">
            <input type="submit">
        </form>
    </body>
</html>

しかし、2つの異なるファイルでHTMLの大部分を繰り返しただけなので、今行ったことは必ずしも最良の設計ではありません。Djangoのテンプレート言語は、この貧弱な設計を排除する方法、つまりテンプレート継承を提供してくれます。これにより、ページの一般的な構造を含む layout.html ファイルを作成できます。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Tasks</title>
    </head>
    <body>
        {% block body %}
        {% endblock %}
    </body>
</html>

ここでも {%...%} を使用して、HTML以外のロジックを表していることに注目してください。この例では、Djangoにこの 「ブロック」 を別のファイルのテキストで埋めるように指示しています。これで、他の2つのHTMLファイルを次のように変更できます。

index.html :

{% extends "tasks/layout.html" %}

{% block body %}
    <h1>Tasks:</h1>
    <ul>
        {% for task in tasks %}
            <li>{{ task }}</li>
        {% endfor %}
    </ul>
{% endblock %}

add.html :

{% extends "tasks/layout.html" %}

{% block body %}
    <h1>Add Task:</h1>
    <form action="">
        <input type="text", name="task">
        <input type="submit">
    </form>
{% endblock %}

レイアウトファイルを拡張することで、繰り返されるコードの大部分を取り除くことができます。これで、インデックスページは変更されず、追加ページも表示されます。

Add

次に、新しいタスクを追加するときにURLに 「/add」 と入力するのは理想的ではないので、ページ間にリンクを追加したいと思うでしょう。リンクをハードコーディングする代わりに、urls.py の各パスに割り当てた name 変数を使用して、次のようなリンクに作成できます。

<a href="{% url 'add' %}">Add a New Task</a>

「add」 はそのパスの名前です。add.html でも同様のことができます。

<a href="{% url 'index' %}">View Tasks</a>

しかし、私たちの様々なアプリには index という名前のいくつかのルートがあるので、これは問題を引き起こす可能性があります。これを解決するには、各アプリの urls.py ファイルに app_name変数を追加して、ファイルが次のようになるようにします。

from django.urls import path
from . import views

app_name = "tasks"
urlpatterns = [
    path("", views.index, name="index"),
    path("add", views.add, name="add")
]

リンクを単純な index と add から tasks:indextasks:add に変更します。

<a href="{% url 'tasks:index' %}">View Tasks</a>

<a href="{% url 'tasks:add' %}">Add a New Task</a>

次に、ユーザがフォームを送信したときに、フォームが実際に何かを実行するようにします。これを行うには、add.html で作成したフォームに action を追加します。

<form action="{% url 'tasks:add' %}" method="post">

これは、フォームが送信されると、add  URLに戻されることを意味します。ここでは、getメソッドではなくpostメソッドを使用することを指定しました。通常は、フォームによってWebページの状態が変更される可能性があるときに使用します。

Djangoはクロスサイトリクエストフォージェリ (CSRF) 攻撃を防ぐためにトークンを必要とするため、ここでこのフォームにもう少し追加する必要があります。これは、悪意のあるユーザがサイト以外の場所からサーバーに要求を送信しようとする攻撃です。これは、一部のWebサイトにとって非常に大きな問題になる可能性があります。たとえば、銀行のWebサイトに、あるユーザが別のユーザに送金するためのフォームがあるとします。誰かが銀行のウェブサイトの外から送金の命令を送信できたら大変なことになります!

この問題を解決するために、Djangoはテンプレートをレンダリングするレスポンスを送信する際に、サイト上の新しいセッションごとに固有のCSRFトークンも提供します。その後、リクエストが送信されると、Djangoはそのリクエストに関連付けられたCSRFトークンが、最近提供されたトークンと一致することを確認します。したがって、別のサイトの悪意のあるユーザが要求を送信しようとすると、CSRFトークンが無効であるためにブロックされます。このCSRF検証は、Djangoミドルウェアフレームワークに組み込まれています。Djangoミドルウェアフレームワークは、Djangoアプリケーションの要求・応答処理に介入できます。このコースではミドルウェアについて詳しく説明しませんが、必要に応じてドキュメント を参照してください。

この技術をコードに組み込むには、add.html のフォームに行を追加する必要があります。

<form action="{% url 'tasks:add' %}" method="post">
    {% csrf_token %}
    <input type="text", name="task">
    <input type="submit">
</form>

この行は、Djangoが提供するCSRFトークンを持つ非表示の入力フィールドを追加し、ページをリロードしても何も変更されていないように見えるようにします。ただし、要素を検査すると、新しい入力フィールドが追加されていることがわかります。

CSRF

Djangoフォーム

これまでのように直接HTMLを記述することでフォームを作成することもできますが、Djangoはユーザから情報を収集するためのさらに簡単な方法を提供しており、それはDjangoフォーム と呼ばれます。このメソッドを使用するには、次のコードをviews.pyの先頭に追加して、forms モジュールをインポートします。

from django import forms

ここで、NewTaskForm というPythonクラスを作成して、views.py 内に新しいフォームを作成します。

class NewTaskForm(forms.Form):
    task = forms.CharField(label="New Task")

では、このクラスで何が起こっているか見てみましょう。

  • NewTaskForm の後のかっこ内に、forms.Form があることがわかります。これは、新しいフォームが、forms モジュールに含まれるForm というクラスから継承するためです。Djangoのテンプレート言語やSassを使ったスタイリングで継承がどのように使われるかは既に見てきました。これは、継承がどのように使用されるかの一例であり、より一般的な記述( forms.Form クラス)を取得し、それを必要なもの(私たちの新しいフォーム)に絞り込むために使用されます。継承はオブジェクト指向プログラミングの重要な部分であり、このコースでは詳しく説明しませんが、このトピックについて学習できるオンラインリソースが多数あります。
  • このクラスでは、ユーザから収集する情報 (この場合はタスクの名前) を指定できます。
  • forms.CharField を記述することによって、これがテキスト入力であることを指定します。Djangoのフォーム・モジュールには、他にも選択可能な入力フィールドが多数含まれています。
  • この CharField 内で、ユーザがページをロードしたときに表示される label を指定します。label は、フォームフィールドに渡すことができる多くの引数の1つにすぎません。

NewTaskForm クラスを作成したので、add ページのレンダリング時にコンテキストに含めることができます。

# Add a new task:
def add(request):
    return render(request, "tasks/add.html", {
        "form": NewTaskForm()
    })

add.html 内で、入力フィールドを先ほど作成したフォームに置き換えることができます。

{% extends "tasks/layout.html" %}

{% block body %}
    <h1>Add Task:</h1>
    <form action="{% url 'tasks:add' %}" method="post">
        {% csrf_token %}
        {{ form }}
        <input type="submit">
    </form>
    <a href="{% url 'tasks:index' %}">View Tasks</a>
{% endblock %}

forms モジュールを使用すると、HTMLフォームを手動で作成するよりもいくつかの利点があります。

  • フォームに新しいフィールドを追加する場合は、views.py に追加するだけで、追加のHTMLを入力する必要はありません。
  • Djangoは、クライアント側の検証、つまりユーザのマシンに対してローカルな検証を自動的に実行します。つまり、フォームが不完全な場合、ユーザはフォームを送信できません。
  • Djangoは単純なサーバーサイドの検証、つまりフォームデータがサーバーに到達すると実行される検証を提供します。
  • 次のレッスンでは、モデルを使って情報を保存する方法を説明しますが、Djangoを使えば、モデルに基づくフォームを非常に簡単に作成することができます。

これでフォームの設定が完了したので、ユーザが送信ボタンをクリックしたときの動作について説明します。ユーザがリンクをクリックするか、URLを入力して追加ページに移動すると、GET リクエストがサーバに送信されます。ユーザがフォームを送信すると、サーバに POST 要求が送信されますが、現時点では add 関数では処理されません。POST メソッドを処理するには、関数に渡される引数、requestに基づいた条件を追加します。次のコードのコメントは、各行の目的を説明しています。

# Add a new task:
def add(request):

    # Check if method is POST
    if request.method == "POST":

        # Take in the data the user submitted and save it as form
        form = NewTaskForm(request.POST)

        # Check if form data is valid (server-side)
        if form.is_valid():

            # Isolate the task from the 'cleaned' version of form data
            task = form.cleaned_data["task"]

            # Add the new task to our list of tasks
            tasks.append(task)

            # Redirect user to list of tasks
            return HttpResponseRedirect(reverse("tasks:index"))

        else:

            # If the form is invalid, re-render the page with existing information.
            return render(request, "tasks/add.html", {
                "form": form
            })

    return render(request, "tasks/add.html", {
        "form": NewTaskForm()
    })

注意:送信が成功した後にユーザをリダイレクトするには、さらにいくつかインポートする必要があります。

from django.urls import reverse
from django.http import HttpResponseRedirect

セッション

この時点で、増え続けるリストにタスクを追加できるアプリケーションの構築に成功しました。ただし、これらのタスクをグローバル変数として保存すると、ページにアクセスするすべてのユーザがまったく同じリストを参照することになるため、問題が発生する可能性があります。この問題を解決するために、 セッションと呼ばれるツールを使用します。

セッションとは、Webサイトに新しくアクセスするたびに、サーバ側に一意のデータを保存する方法です。

アプリケーションでセッションを使用するには、まずグローバル tasks 変数を削除し、次に index 関数を変更します。最後に、変数 tasks を使用した他の場所を確認し、request.session["tasks"] に置き換えます。

def index(request):

    # Check if there already exists a "tasks" key in our session

    if "tasks" not in request.session:

        # If not, create a new list
        request.session["tasks"] = []

    return render(request, "tasks/index.html", {
        "tasks": request.session["tasks"]
    })

# Add a new task:
def add(request):
    if request.method == "POST":

        # Take in the data the user submitted and save it as form
        form = NewTaskForm(request.POST)

        # Check if form data is valid (server-side)
        if form.is_valid():

            # Isolate the task from the 'cleaned' version of form data
            task = form.cleaned_data["task"]

            # Add the new task to our list of tasks
            request.session["tasks"] += [task]

            # Redirect user to list of tasks
            return HttpResponseRedirect(reverse("tasks:index"))
        else:

            # If the form is invalid, re-render the page with existing information.
            return render(request, "tasks/add.html", {
                "form": form
            })

    return render(request, "tasks/add.html", {
        "form": NewTaskForm()
    })

最後に、Djangoがこのデータを保存できるようにするには、ターミナルで python manage.py migrate を実行する必要があります。来週は、移行 (マイグレーション) とは何かについて詳しく説明しますが、ここでは、上記のコマンドでセッションを保存できることだけを知っておいてください。

これでこのレッスンは終わりです!次回は、Djangoを使ってデータの保管、アクセス、操作を行うことにします。