PyramidでToDo管理サービスを作る 【ToDo登録画面作成】
前回は、ToDoを管理するモデルを作成したので、登録するための画面を作成する。
# 前回からだいぶ時間が経ってしまった…
実行環境
- Windows 8.1 (まだ10にしていない)
- Python 3.4.3
- Pyramid 1.5.7
セッションの設定
pyramidでセッションを使うには、 config
に使用するセッションを登録すれば良いらしい。
todomanager/__init__.py
にセッションを使うための設定をする。
セッションのシークレットキーは外部ファイルや環境変数辺りに設定するのが正しいのだろうけど、まだ作り途中であるため development.ini
から読み取るようにする。
import hashlib from pyramid.session import SignedCookieSessionFactory def main(global_config, **settings): secret = hashlib.sha256(str(settings['pyramid.session.secret']).encode('utf-8')).hexdigest() session_factory = SignedCookieSessionFactory(secret=secret) # 省略 config.set_session_factory(session_factory)
development.ini
にシークレットキーを設定する。
デプロイするときはランダム生成させた文字列にした方が良いと思われる。
pyramid.session.secret = development
テンプレートの設定
pyramidのデフォルトで設定されているテンプレートエンジンは Chameleon
となっている。
Djangoでは標準のテンプレートを使っていたことも有り、 jinja2
に変更する。
テンプレートエンジン用のモジュールをインストール
次のコマンドを入力してモジュールをインストールする。
(pyramid_python)> pip install pyramid_jinja2
レンダラーを変更
Jinja2
を利用して作成したテンプレートを表示するための準備を行う。
ToDoManager
を起動させる上で必要となるモジュール郡の中に、テンプレートエンジン用のモジュールが設定されているので、その部分を Jinja2
用に変える。
setup.py
に書かれている部分を次のように修正する。
requires = [ ... 'pyramid_chameleon', ... ] ↓ requires = [ ... 'pyramid_jinja2', ... ]
MANIFEST.in
に書かれている以下の部分に *.jinja2
を追記する。
recursive-include todomanager *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
^^^^^^^^
todomanager/__init__.py
に書かれている部分をつぎのように修正する。
def main(global_config, **settings): ... config.include('pyramid_chameleon') ... ↓ def main(global_config, **settings): ... config.include('pyramid_jinja2') ...
テンプレートの作成
テンプレートエンジンとして jinja2
を使用するが、タグや記述方法については公式を参照。
Welcome to Jinja2 — Jinja2 Documentation (2.8-dev)
Bootstrapの用意
こういうサービスを作るときに便利なのが Bootstrap
である。
Bootstrap · The world's most popular mobile-first and responsive front-end framework.
「Bootstrap CDN」を利用すると、必要なファイル一式をダウンロードしなくてもBootstrapを使うことができるらしい。
今回は、静的ファイルの使い方を練習する意味も兼ねてBootstrap一式をダウンロードすることにした。
ToDoManager/todomanager/static
に bootstrap
ディレクトリを作成して、展開したファイル一式をコピーする。
./ToDoManager/todomanager/static └─bootstrap ├─css │ bootstrap-theme.css │ bootstrap-theme.css.map │ bootstrap-theme.min.css │ bootstrap.css │ bootstrap.css.map │ bootstrap.min.css │ ├─fonts │ glyphicons-halflings-regular.eot │ glyphicons-halflings-regular.svg │ glyphicons-halflings-regular.ttf │ glyphicons-halflings-regular.woff │ glyphicons-halflings-regular.woff2 │ └─js bootstrap.js bootstrap.min.js npm.js
テンプレートの共通部分
それぞれのページで共通となる部分を base.jinja2
と定義して以下の様に記述する。
{%- set navigation_bar = [ (request.route_url('home'), 'home', 'Home'), (request.route_url('create'), 'create', 'Create'), ] -%} {%- set active_page = active_page|default('home') -%} <!DOCTYPE html> <html lang="{{ LANGUAGE_CODE|default('ja') }}"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ page_title|default('ToDo Manager') }}</title> <link href="./static/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="./static/bootstrap/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-inverse navbar-static-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="{{ request.route_url('home') }}">ToDo Manager</a> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> {%- for here, id, caption in navigation_bar %} <li{% if id == active_page %} class="active"{% endif %}><a href="{{ here|e }}">{{ caption|e }}</a></li> {%- endfor %} </ul> </div><!--/.nav-collapse --> </div> </nav> <div class="container-fluid"> {%- block container %}{%- endblock %} </div><!-- /.container-fluid --> </body> </html>
ToDoを登録するページを create.jinja2
として以下のように記述する。
{% extends 'base.jinja2' %} {%- block container %} <div class="row"> <form name="create" action="{{ request.route_url('create') }}" method="post" class="form-horizontal col-md-8 col-md-offset-2"> <input type="hidden" name="csrf_token" value="{{ request.session.get_csrf_token() }}" /> {%- if result != null %} <div class="alert{% if result['status'] == 'success' %} alert-success{% else %} alert-danger{% endif %}" id="result-alert" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden>×</span></button> {{ result['message']|e }} </div> {%- endif %} <div class="form-group"> <label class="control-label" for="id_summary">ToDo 内容</label> <textarea id="id_summary" name="summary" class="form-control" rows="10" cols="40" required="required"></textarea> </div> <div class="form-group"> <button class="btn btn-success" type="submit">登録</button> </div> </form> </div> {%- endblock %}
ビューの設定
ビューの処理を作成
scaffoldで作成された view.py
は、クラスは無く関数にURLのマッピングとレンダリング用のテンプレートが指定されている。
from pyramid.response import Response from pyramid.view import view_config @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): return Response("This page does not created yet.", content_type='text/plain', status_int=500)
機能毎にビューを分けられるようクラスを使用して、マッピングするURLとレンダリング用のテンプレートの設定などを行う。
ホームの処理については、画面デザインを決めあぐねているため仮置きとする。
from pyramid.response import Response from pyramid.session import check_csrf_token from pyramid.view import view_config from todomanager.models import Task, DBSession class TaskView(object): def __init__(self, request): self.request = request @view_config(route_name='home', renderer='templates/base.jinja2') def home(self): return Response( "This page does not created yet.", content_type='text/plain', status_int=500 ) @view_config(route_name='create', renderer='templates/create.jinja2') def create(self): parameter = { "page_title": "ToDo Manager - タスクの作成", "active_page": "create", } if self.request.method == 'POST': if check_csrf_token(self.request): summary = self.request.POST.get("summary") if summary is not None: task = Task(summary=summary) DBSession.add(task) result = { "status": "success", "message": "ToDoの登録に成功しました。", } else: result = { "status": "error", "message": "内容が入力されていません。", } parameter["result"] = result else: raise ValueError("CSRF token did not match.") return parameter
ビューを表示するところに、ToDoをDBに登録する処理があるのはイケてない感じがするものの、ひとまずはこれで登録できるようになった。
URLとマッピング
作成したビューの処理を呼び出すために todomanager/__init__.py
にルーティングを設定する。
今回新たに紐付けたURLは以下の1つ。
- ToDo作成画面(/create)
もともと設定されていた home
はそのまま使用する。
def main(global_config, **settings): ... config.add_route('home', '/') config.add_route('create', '/create') ...