サムネイル
FlaskはPythonで書かれたマイクロウェブフレームワークです。
特定のツールやライブラリを必要としないため、マイクロフレームワークに分類されます。
データベースの抽象化レイヤーやフォームの検証など、既存のサードパーティライブラリが共通機能を提供するコンポーネントは存在しません。
しかし、Flaskは拡張機能をサポートしており、Flask自体に実装されているかのようにアプリケーションの機能を追加することができます。
オブジェクトリレーショナルマッパー、フォームバリデーション、アップロード処理、様々なオープン認証技術、いくつかの一般的なフレームワーク関連ツールのための拡張機能が存在します。
Flaskのインストールにはいくつかの方法があります。
OS/パッケージマネージャが直接 Flask をサポートしている場合
FreeBSDがこれに当たります。
pip をサポートしている場合。
オペレーションシステムのパッケージマネージャから、Pythonのパッケージマネジャーである pip をインストールし、 pip を使って Flask をインストールします。
Flaskのパッケージがあるオペレーションシステムでは、pip ではなく、パッケージマネージャーからFlaskと依存パッケージをインストールしましょう。
混在すると、OSのパッケージマネージャーと pip の間で管理情報に矛盾が生じアップデートなどに支障をきたすようになっていまいます。
多くの BSD UNIX は ports/pkgsrc/pkg で Flask をサポートしています。
portsレクションに [https://www.freshports.org/www/py-flask www/py-flask] があります。
ソースコードからインストール
 # make -C /usr/ports/www/py-flask install clean
バイナリーパッケージからインストール
 # pkg install www/py-flask
Werkzeug や Jinja などの Flask が依存するパッケージも自動的にビルド/インストールされます。
また、静的なサイトを生成する [https://www.freshports.org/www/py-frozen-flask/ www/py-frozen-flask] もパッケージ化されています。
GNU/Linuxでは、ディストリビューションによってパッケージマネージャーや、パッケージアーカイブスが異なるので、お使いのパッケージマネージャーの利用方向に従ってインストールしてください。
sudo dnf update
sudo dnf search flask
を実行し、flask を名前に含むパッケージの一覧を表示し、その中から python3-flask を選びインストールします。
sudo dnf install python3-flask
GNU/Linuxの場合、インストールに管理者権限が必要なので、一般ユーザーのアカウントからは sudo を使って root 権限を行使します。
Fedora には、python3-flaskパッケージがあるので、 pip から flask をインストールすると、パッケージマネージャーで管理している依存パッケージ（Werkzeug や Jinja）と、 pip で管理している依存パッケージのバージョンが違ったりパスが違ったりする「キマイラ環境」ができてしまい、アップデートやアップグレードに支障を来たします。
ウェブアプリケーションを起動する前に、インストールが成功したか、またどのバージョンがインストールされたかを確認します。
[https://paiza.io/projects/hnqyJj-d47Kl52KBhcMy8Q?language=python3 flask-chk.py]:
import flask
import werkzeug
import jinja2
print(f"{flask.__version__=}")
print(f"{werkzeug.__version__=}")
print(f"{jinja2.__version__=}")
print("")
help(flask)
実行結果:
flask.__version__='2.2.2'
werkzeug.__version__='2.2.2'
jinja2.__version__='3.1.2'
Help on package flask:
NAME
    flask
PACKAGE CONTENTS
    __main__
    app
    blueprints
    cli
    config
    ctx
    debughelpers
    globals
    helpers
    json (package)
    logging
    scaffold
    sessions
    signals
    templating
    testing
    typing
    views
    wrappers
FUNCTIONS
    __getattr__(name)
    
    escape(...)
        Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in the string with HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML.
        
        If the object has an ``__html__`` method, it is called and the return value is assumed to already be safe for HTML.
        
        :param s: An object to be converted to a string and escaped.
        :return: A :class:`Markup` string with the escaped text.
DATA
    appcontext_popped = 
    g = 
    got_request_exception = 
    request_finished = 
    signals_available = True
    template_rendered = 
インストール先が /usr/local/lib/python3.8/dist-packages/flask/__init__.py であることがわかります。
個々のメソッドのヘルプも help(flask.app) のように参照できます。
"Hello World" だけを返すウェブサービスをつくってみます。
hello.py:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
    return "Hello World"
app.run()
起動してみましょう。
% python3.11 hello.py
 * Serving Flask app 'hello.py'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
http://127.0.0.1:5000 を開いてみます（127.0.0.0/8は、他のホストからはアクセスできないループバックインターフェースに割り当てられたアドレスです）。
Hello World
と表示されます。
Flaskを実行中の端末には
127.0.0.1 - - [22/Dec/2022 10:43:18] "GET / HTTP/1.0" 200 -
とcommon log format で表示されます。
http://127.0.0.1:5000/no-exist を開いてみましょう。
404 NOT FOUND
Flaskを実行中の端末には
127.0.0.1 - - [22/Dec/2022 10:43:30] "GET /no-exist HTTP/1.0" 404 -
と表示されます。
Flaskを実行中の端末でFlaskを強制終了します。
^C%
^Cと表示されますが、CTRL-C（CTRLキーを押しながら C を押す）です。
解説
from flask import FlaskflaskパッケージからFlaskクラスをインポートとしています。
app = Flask(__name__)変数 app をFlaskインスタンスで初期化し、以後appがウェブサービスの主体になります。__name__ には、実行しているプログラムのモジュール名が入ります。
return "Hello World"機能は、文字列 "Hello World" を返すだけ。これが、HTTPレスポンスのボディーになります。
app.run()サービスを起動します。具体的には、Flaskの内蔵ウェブサーバを localhost:5000 にバインドして実行します。
Flaskは、Jinjaテンプレートエンジンと連携し、テンプレートベースの動的なウェブページの生成が可能です
Flaskの既定のディレクトリーレイアウトでは、スクリプトのあるディレクトリーの下に、templates/ という名前のディレクトリーを掘り、そこにテンプレートを用意します。
２つのテンプレートファイルと１つのスクリプトを用意します。
templates/top.html:
Index
{% for who in ["world", "flask", "python"]%}
{% endfor %}
    
templates/echo.html:
Top
echo.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
    return render_template("top.html")
@app.route("/", methods=["GET", "POST"])
def echo(name):
    return render_template("echo.html", who=name)
if __name__ == "__main__":
    app.run(debug=True, host='127.0.0.1', port=5555, threaded=True)
hello.py の Flask に加え render_template も import しています。
@app.route() に新たに与えているキーワード引数 methods は、HTTPのメソッドを指定しています。ディフォルトは GET だけをルーティングします。
"/" は引数付きのURLで、この場合は変数 name に / 以降の引数が入ります。
if __name__ == "__main__": は、メインスクリプトとして呼ばれた場合だけ、つづくブロックを実行します。
Jinja は、Python で実装されたテンプレートエンジンで、Flaskウェブアプリケーションフレームワークの一部として提供されていますが、単体で使用することも可能です。
[https://paiza.io/projects/cdEud4CphuGiicYsf3Qz1A?language=python3 Example.html.jinja]:
{# Jinja の for 繰返し構文の例 #}
{% for item in items %}
{% endfor %}
{# Jinja の for 繰返し構文の例 #}
    
[https://paiza.io/projects/cdEud4CphuGiicYsf3Qz1A?language=python3 Main.py]:
from jinja2 import Template, Environment, FileSystemLoader
env = Environment(
    loader=FileSystemLoader('./', encoding='utf8'),
    line_statement_prefix = '#',
    line_comment_prefix = '##'
)
tmpl = env.get_template('Example.html.jinja')
print(tmpl.render(
    title = 'Simpla Template Example',
    items = range(0, 6)
))
実行結果(HTML):
Simpla Template Example
1
3
5
7
9
11
1
3
5
7
9
11
レンダリング例:
1
3
5
7
9
11
1
3
5
7
9
11
 でテンプレート中で変数（キーワード引数）や関数を参照できます。
{% for ループ変数 in コレクション %} から {% endfor %} の間で反復され、このブロックでループ変数を参照できます。
通常の Python のコードと異なり、{% for %} ループを抜けると、ループ変数は参照できません。このようにJinjaテンプレートはPythonと似た構文が使えますが、独立したサンドボックスで実行されており、Pythonとは別の言語であることに注意が必要です。
{#から#}までが、Jinjaテンプレートのコメントで、出力（この場合はHTML）には含まれません。
 でテンプレート中で変数（キーワード引数）や関数を参照できます。
{% set 変数 = 値 %} でテンプレート中で変数に値を代入できます。
{# コメント #}がJinjaテンプレートのコメントの構文で、出力には含まれません。
Jinjaには、条件分岐と反復の制御構造があります。
例:
{% if x > 0 %}
x > 0
{% elif x < 0 %}
x < 0
{% elif x == 0 %}
x == 0
{% else %}
{% endif %}
構文:
{% if 式１ %}
ブロック１
{% elif 式２ %}
ブロック２
      ︙
{% elif 式ｎ %}
ブロックｎ
{% else %}
ブロック
{% endif %}
elif 式 + ブロック と else + ブロック は省略可能です。
構文:
{% for ループ変数 in コレクション %}
ブロック
{% endfor %}
for のブロックの中では、特殊変数 loop を参照することでイテレーション回数を参照できます。
loop.first:最初の周回なら True、そうでなければ False を返す。
loop.last:最後の周回なら True, そうでなければ False を返す。
コレクションは、リスト、辞書、タプルなどで概ねPythonのforと同じ挙動ですが、ループ変数のスコープがループに閉じているところが違います。
 {% for item in range(1, 5) %}
 {% endfor %}
は
1
2
3
4
 {% for item in range(1, 5) -%}
 {% endfor %}
と %} を -%} とすることで
1
2
3
4
のように改行を抑止できます。
マクロ( ''macro'' )機能を使うと、繰返し使うフレーズをテンプレートの中で定義し、呼出しという形で再利用できます。
書式:
{% macro マクロ名(仮引数リスト)  %}
    処理内容
{% endmacro %}
マクロの呼出し:
例:
{% macro func(arg)  %}
 
{% endmacro %}
実行結果:
  おはよう 
レンダリング例
  おはよう 
Jinjaでは、Pythonのすべての組込み型を render_template() の引数として受取ることができ、反復構文を使うことで自由度の高いテンプレートを作ることができます。
[https://paiza.io/projects/xmTduPPU4msqgdLh1DQhSQ?language=python3 Example.html.jinja]:
jinja -- example
)
  
)
  
  
[https://paiza.io/projects/xmTduPPU4msqgdLh1DQhSQ?language=python3 Main.py]:
from jinja2 import Template, Environment, FileSystemLoader
env = Environment(
    loader=FileSystemLoader('./', encoding='utf8'),
    line_statement_prefix = '#',
    line_comment_prefix = '##'
)
tmpl = env.get_template('Example.html.jinja')
print(tmpl.render(args = (
    123,
    22/7,
    "abc",
    0 == 0,
    ['Hello', 'World', 9],
    ('Hello', 'World', 9),
    {'cat':'tama', 'dog':'pochi', 'year':1776},
    {'cat', 'dog', 'bird'},
    range(1,10)
)))
実行結果(HTML):
jinja -- example
123(int)
  3.142857142857143(float)
  abc(str)
  True(bool)
  ['Hello', 'World', 9](list)
  
Hello(str)
  World(str)
  9(int)
('Hello', 'World', 9)(tuple)
  
Hello(str)
  World(str)
  9(int)
{'cat': 'tama', 'dog': 'pochi', 'year': 1776}(dict)
  
cat(str)tama(str)
  dog(str)pochi(str)
  year(str)1776(int)
  
{'bird', 'cat', 'dog'}(set)
  
bird(str)
  cat(str)
  dog(str)
range(1, 10)(range)
  
1(int)
  2(int)
  3(int)
  4(int)
  5(int)
  6(int)
  7(int)
  8(int)
  9(int)
レンダリング例:
123(int)
  3.142857142857143(float)
  abc(str)
  True(bool)
  ['Hello', 'World', 9](list)
  
Hello(str)
  World(str)
  9(int)
('Hello', 'World', 9)(tuple)
  
Hello(str)
  World(str)
  9(int)
{'cat': 'tama', 'dog': 'pochi', 'year': 1776}(dict)
  
cat(str)tama(str)
  dog(str)pochi(str)
  year(str)1776(int)
  
{'bird', 'cat', 'dog'}(set)
  
bird(str)
  cat(str)
  dog(str)
range(1, 10)(range)
  
1(int)
  2(int)
  3(int)
  4(int)
  5(int)
  6(int)
  7(int)
  8(int)
  9(int)
キーワード引数（テンプレートの変数）args は Tuple で、Python の代表的な組込み型の値を要素にしました。
ここで定義している macro show(a) は、引数の値と型を表示し、引数がコレクションだったら再帰的に要素を表示します（マクロは再帰できます）。
この例では、line statment （行志向構文）を使っているので{% 文 %} の対応関係を目で追う必要なくなり可読性が高くなります。
Jinjaでは、テンプレートを別のテンプレートから雛形として継承する事ができます。
[https://paiza.io/projects/SAgURmsKBJzf71EKITv8jg?language=python3 Base.html.jinja]:
{% block title %}Jinja::Example{% endblock %}
  {% macro hr() %}
  
  {%endmacro %}
  {% block content %}
  {% endblock %}
  
[https://paiza.io/projects/SAgURmsKBJzf71EKITv8jg?language=python3 Example.html.jinja]:
{% extends "Base.html.jinja" %}
{% endblock %}
{% block content %}
ホーム
Example.html.jinja の block content
{% endblock %}
[https://paiza.io/projects/SAgURmsKBJzf71EKITv8jg?language=python3 Main.py]:
from jinja2 import Template, Environment, FileSystemLoader
env = Environment(
    loader=FileSystemLoader('./', encoding='utf8'),
    line_statement_prefix = '#',
    line_comment_prefix = '##'
)
tmpl = env.get_template('Example.html.jinja')
print(tmpl.render())
実行結果(HTML):
ホーム - Jinja::Example
ホーム
Example.html.jinja の block content
レンダリング例:
ホーム
Example.html.jinja の block content
解説
継承元のテンプレート Base.html.jinja では、開始タグ {% block ブロック名 %｝から終了タグ {% endblock %} で差替え対象を決めます。
継承先のテンプレート Example.html.jinja では、開始タグ {% block ブロック名 %｝から終了タグ {% endblock %} で差替える内容を気まます。
継承先のブロックの中では、関数 super() で、継承元の差替え対象を参照できます。
ブロックは、マクロと異なり継承元では定義を行うだけで、展開は継承先だけで行なえます。また継承元も継承先も同じ開始タグと終了タグを使うので、多段継承（孫テンプレートを作ること）はできません。定義を blockdef、展開を blockref のように別の名前にすれば構文的には多段継承できますが、その場合「super()が何を表すのか」「循環継承が起こったらどうするか？」など意味論・運用論的な問題を多数生んでしまうので、多段継承を妨げたのは設計上のロバストな選択です。
また、継承元で定義したマクロを、継承先で展開することはできます。
block と extends によるテンプレート継承が雛形だとすれば、include による包含は部品化です。
[TODO:コード例]
filter には、式指向のフィルターとブロック指向のフィルターとがあります。
[TODO:コード例]
Flaskに次ぐシェア２位のPythonベースのウェブアプリケーションフレームワークにDjangoがあります。
単独のページとして立項できる内容と信頼性を得るまで、Flaskの下におきました。
Djangoのインストールにはいくつかの方法があります。
OS/パッケージマネージャが直接 Django をサポートしている場合
FreeBSDがこれに当たります。
pip をサポートしている場合。
オペレーションシステムのパッケージマネージャから、Pythonのパッケージマネジャーである pip をインストールし、 pip を使って Flask をインストールします。
Djangoのパッケージがあるオペレーションシステムでは、pip ではなく、パッケージマネージャーからDjangoと依存パッケージをインストールしましょう。
混在すると、OSのパッケージマネージャーと pip の間で管理情報に矛盾が生じアップデートなどに支障をきたすようになっていまいます。
多くの BSD UNIX は ports/pkgsrc/pkg で Django をサポートしています。
portsレクションに [https://www.freshports.org/www/py-django www/py-django] があります。
ソースコードからインストール
 # make -C /usr/ports/www/py-django install clean
バイナリーパッケージからインストール
 # pkg install www/py-django
GNU/Linuxでは、ディストリビューションによってパッケージマネージャーや、パッケージアーカイブスが異なるので、お使いのパッケージマネージャーの利用方向に従ってインストールしてください。
sudo dnf update
sudo dnf search django
を実行し、django を名前に含むパッケージの一覧を表示し、その中から python3-django を選びインストールします。
sudo dnf install python3-django
GNU/Linuxの場合、インストールに管理者権限が必要なので、一般ユーザーのアカウントからは sudo を使って root 権限を行使します。
Djangoのバージョン確認の方法
python -m django --version
Django を使うには、コマンドラインを使う必要があります。
まず、プロジェクトのディレクトリ群を作らないといけないのですが、さすがにこれは自動生成してくれるコマンドがあります。
コマンド
 django-admin startproject プロジェクト名
で、まずプロジェクトを作ります。
上記コマンドを使えば、Django用のプロジェクトのディレクトリを自動生成してくれます。
たとえば、 「projtest」というプロジェクトを作るなら
 django-admin startproject projtest
になるわけです。
そして、このプロジェクト内でこれから作業するので、移動コマンドcdを使い、
 cd プロジェクト
で、さきほど作成したプロジェクトのディレクトリ内に移動します。
コマンド
 python manage.py runserver 
を使うと、Django の ウェブサーバーが立ち上がります。
ウェブブラウザで、
 http://127.0.0.1:8000/
でアクセスできます。
thumb|400px| Django の初期ページ
まだ何もユーザー側でHTMLを作っていないので、初期設定のHTMLページが表示されるので、表示ページには英文およびロゴマークでロケットの打ち上げ絵が書かれています。水色っぽいロケットの打ち上がってるイラストが出たら、とりあえず、ここまでの作業は成功です。
サーバ立ち上げのコマンドの冒頭は、  django-admin  ではなく  Python  ですので、間違えないようにしましょう。なんでここだけPythonなのか謎ですが、そういう仕様なので、従うしかありません。
なお、自動作成ずみのプロジェクト用ディレクトリ内にすでに manage.py というスクリプトがあり、それを上記コマンド  Python manage.py runserver  で実行している仕組みです。
なお、サーバを終了するには、CTRLキー を押しながら C を押します。（Flaskと同じ終了コマンド）
次回以降のサーバの立ち上げは
 cd プロジェクト名
 python manage.py runserver
になります。
では、これからホームページを作りましょう。Djangoではホームページは「アプリ」に分類されるので、コマンド
 Python manage.py startapp アプリ名
で、まずDjangoアプリを作ります。
このコマンドを実行する場所は、どこでもいいのですが、プロジェクト内でしたほうがラクなので、中で作成した場合、コマンドライン画面は
 [ユーザ名@localhost プロジェクト名]$  Python manage.py startapp アプリ名
のようになります。
今回、最終的に作りたいのはホームページなので、たとえば「hptest」というアプリ名を作ったとしましょう。
Django用ディレクトリ内に自動作成されたアプリ名ディレクトリの中に
views.py:
from django.shortcuts import render
というファイル名と上記のコード内容のファイルがありますので、それをマウスで右クリックして、普通に開き、これから編集することで、ホームページ作成する仕組みです。
これを編集し、下記のような関数を付け足します。関数名(func)とその引数(arg)は自由に設定できますが、「render」はそのままにしてください。
views.py 変更後:
from django.shortcuts import render
def func(arg):
    return render(arg, 'testpage.html')
書式はもちろん
def 関数名(引数名):
    return render(引数名, 'ウェブページの名前.html')
です。
次に、プロジェクト名と同名のディレクトリが、プロジェクトに入った階層にあるので、その同名ディレクトリに、マウスで移動します。
そして、その同名ディレクトリ内に、urls.py というファイルが存在し、↓のほうに、下記のようなコードが書かれています。
プロジェクト側 urls.py の抜粋
from django.contrib import admin
from django.urls import path
urlpatterns = [
    path('admin/', admin.site.urls),
]
このファイルのこのコード部分を下記のように書き換えます。
from django.contrib import admin
from django.urls import path, include   # 追加せよ include を
urlpatterns = [
    path('admin/', admin.site.urls),
    path('アプリ名/', include('アプリ名.urls') )   # 追加
    # たとえば path('hptest/', include('hptest.urls') )
]
次に、アプリ側に、urls.py という同名のファイルを作りますが（初期設定では用意されてないので、ファイルを新規作成してください）、中身は別です。下記のようなコードをアプリ側に書きます。
アプリ側 urls.py の抜粋
from django.urls import path
from . import views
urlpatterns = [
    path('templates/', views.func, name='func'),
]
そして、さらに、「templates」ディレクトリを作り、そこに最終的に表示したいHTMLを入れます。このディレクトリ名は、必ず「templates」でないといけません。（アプリ側 urls.py のコード中のpath('templates/',  といっしょに別ディレクトリ名に書き換えて違うディレクトリ名にしても、エラーになります。なので、必ず「templates」というファイル名にしてください。）
     path('templates/', views.func, name='func'),
の第一引数の部分は、ウェブブラウザなどで「templates」のメッセージが送られた場合に、テンプレートディレクトリにあるファイルで対応する事を言っています。
なので、たとえば、第一引数を別名にして
     path('tbetumei/', views.func, name='func'),
のようにしても、ウェブブラウザでのアクセス時にURL欄に
 http://localhost:8000/hptest/betumei/
と入力すると、ブラウザからtemplatesディレクトリ内のHTMLなどにアクセスできてしまいます。ですが、まぎらわしいので、第一引数は「templates」のままに、しておいてください。
このtemplatesディレクトリに入れるHTMLには、とりあえず、
Hello Django World!
と書いてあるとしましょう。
さらに、プロジェクト側にある setting.py に、INSTALLED_APPS という項目があり、最初は
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
と書かれていますが、これに私達の作成するアプリ名をつけたすことで、そのアプリが追加されたことを知らせます。
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'アプリ名', # たとえば 'hptest',
]
あとは、サーバを立ち上げて、ウェブブラウザで
 http://localhost:8000/アプリ名/templates/
にアクセスすれば、HTMLファイルの内容が表示されます。(上述のコード例の場合なら、 http://localhost:8000/hptest/templates/ にアクセスする事になります。  )
表示結果
大きな文字で
 Hello Django World!
と表示されれば成功です。
Django では、ログインページそのものを自動作成する機能もあり Auth といいますが、設定が必要です。
これとは別に、ログインページでよく使いそうな、Eメールアドレス入力フォームや、会員サイトでの入会時の生年月日の登録などで使いそうな年月日入力フォームなど、各種の入力フォームを、テンプレートで自動作成する機能があり、Formクラスと言います。
Eメール入力フォームなら、メールアドレス形式以外の文字列が入力されても、送信を禁止します。このように、入力内容を自動的に検査することを「バリデーション」といいます。Formクラスを使うと、バリデーションの機能を使えます。
また、その他の一般のDjangoの入力フォームでも、入力時の必須項目などを設定できます。たとえば、登録ページでユーザー名が未入力なのに「送信」ボタンの押された場合には、「この項目を入力してください」のような警告メッセージを、ウェブページ上で出させて、送信させないでユーザーに再入力させる事ができます。
Djangoなどのフルスタックウェブアプリケーションフレームワークを使う利点として、サーバー製作者は上述のように、入力欄のチェック機能を自作する手間が省けます。
いきなりログインページを作っても、複雑で理解が浅くなってしまうので、まずは、リンクで結ばれている複数のHTMLページをアップロードする方法を学びましょう。
templates にHTMLファイルが2つあるとして、
というファイルがあるとしましょう。
まず、これをアップロードするために、コードを下記のように書き換えます。
アプリ側 urls.py:
from django.urls import path
from . import views
urlpatterns = [
    path("testpage.html", views.func, name="func"),
    path("testpage2.html", views.func2, name="func2"),
]
なお、（アプリ側でなく）プロジェクト側 urls.py の抜粋はそのままにしておきます。
さて、views.pyに、2ページ目のHTMLのための関数を追加する必要があります。
views.py:
from django.shortcuts import render
def func(arg):
    return render(arg, "testpage.html")
def func2(arg):
    return render(arg, "testpage2.html")
そして、それぞれのHTMLファイルには
testpage.html
他ページにワープ 
testpage.html
ここは2ページ目です。
と書いてあるとしましょう。
説明の簡単化のため、まです、各種の Django テンプレートは使わないでおきましょう。
これだけすれば、とりあえず、ウェブブラウザで、
 http://localhost:8000/hptest/testpage.html
にアクセスすれば、1ページ目を表示できます。
そして、ブラウザに表示されたリンク文をクリックすれば、2ページ目に移動できます。
2ページ目に移動したとき、アドレス欄の末尾が、きちんと2ページ目のファイルである testpage2 に変わっている事を確認しましょう。つまり、
 http://localhost:8000/hptest/testpage2.html
というふうに、なっているハズです。
とりあえず、これで読者のアナタは、Djangoに複数のHTMLページをアップロードする事ができました。
では、今度はHTMLファイルのリンクを、Djangoテンプレートタグの一種である URLテンプレートタグ を使って書き換える方法を学びましょう。
単に、1ページ目のhrefタグの引数を、下記のようにurlテンプレートタグを使って書き換えるだけです。なお、前回との表示の区別のため、下記コードでは表示される文章を少々、追加してあります。
testpage.html
他ページにワープ. なお文章を書き換えてあります。 
参考のため、2ページ目のHTMLも再掲しておきます。
testpage.html:
ここは2ページ目です。書き換え後のワープ。
あとはもう、
 http://localhost:8000/hptest/testpage.html
にアクセスすれば、前回と同様のアクセス結果になります。