2014年1月28日火曜日

6. Python Pyramid + MySQL ついでに Mako から Jinja2

Djangoじゃないのをやってみようということで、Pyramidをさわってみる。
そのときの記録。
久しぶり。
今回は、Pyramid Tutorial を MySQL 化してみます。
Pyramid Tutorial の中の pyramid_blogr Tutorial をやってみます。このチュートリアルはテーブルをひとつではなく、複数使うのでこれを選びました。複数といってもふたつですが。

  • 環境
  • MacOS 10.9.1
    Python 2.7.6 (MacPortsでインストールしたものを使用する)
    MySQL 5.5.33 (MacPortsでインストールしたものを使用する)
    Apache 2.2.24 (標準でインストールされているもの)
    (mod_wsgi 3.4 はソースを落としてきてインストール)
    Eclipse 4.3.1
    Pydev 3.2.0

最初に、pyramid_blogr のソースをそのまま実行してみます。
pyramid_blogr をこつこつとつくっていくのは日を改めて。

  1. virtualenv を作成して、pyramid の環境を作る
    env_pyramid_blogr という名前で環境を作りました。
    $ virtualenv --no-site-packages env_pyramid_blogr

    つくったディレクトリに、pyramid をインストールします。
    $ cd env_pyramid_blogr/
    $ bin/pip install pyramid

  2. ソースのダウンロード
    git で clone します。
    $ git clone https://github.com/Pylons/pyramid_blogr

  3. sqlite で、まず実行
    $ cd pyramid_blogr/
    $ ../bin/python setup.py develop
    $ ../bin/pip install -e .
    $ ../bin/initialize_pyramid_blogr_db development.ini
    $ ../bin/pserve --reload development.ini
    

    localhost:6543 を見に行くと pyramid_blogr が動作しています。
    Users を admin、Password も admin で、Sign in できます。

    最初は空っぽなので、entry をつくってみます。

    Create a new blog entry をクリックすると、入力できるようになります。

    最初の画面に、entry title が表示されます。


  4. 日本語入れるとこける
    タイトルに日本語を入力するとこけます。

    この状態で保存すると「pyramid_mako.MakoRenderingException」と言われます。

    なんだかよくわかりませんが、ここんとこを修正すると直りました。
    models.py の slug() を修正します。「encode('utf-8')」を追加しました。
    -- OLD --
        @property
        def slug(self):
            return urlify(self.title)
    

    -- NEW --
        @property
        def slug(self):
            return urlify(self.title.encode('utf-8'))
    





で、これを MySQL にするには、ここを修正します。
  1. development.ini と production.ini のデータベースの接続設定を修正
    sqlite を mysql にする。

    -- OLD --
    sqlalchemy.url = sqlite:///%(here)s/pyramid_blogr.sqlite

    -- NEW --
    sqlalchemy.url = mysql://pyramid_user:password@localhost:3306/pyramid_blogr?charset=utf8&use_unicode=1
    sqlalchemy.pool_recycle = 3600

  2. pyramid_blogr/models.py にある型の import をしているところを修正
    Text, Unicode, UnicodeText をやめて、String にする。
    -- OLD --
    from sqlalchemy import (
         Column,
         Integer,
         Text,
         Unicode,     #<- will provide unicode field,
         UnicodeText, #<- will provide unicode text field,
         DateTime     #<- time abstraction field,
         )
    

    -- NEW --
    from sqlalchemy import (
         Column,
         Integer,
         String,
         DateTime     #<- time abstraction field,
         )
    

    そして、class User と class Entry にある項目の「Unicode(255)」や「UnicodeText」を「String(255)」に変更する。unique も取り除く。
    例えば、こう。
    -- OLD --
    title = Column(Unicode(255), unique=True, nullable=False)
    body = Column(UnicodeText, default=u'')

    -- NEW --
    title = Column(String(255), nullable=False)
    body = Column(String(255), default=u'')

  3. setup.py にある requires = [ に mysql-python を追加
    requires = [
        'pyramid',
        'pyramid_chameleon',
        'pyramid_debugtoolbar',
        'pyramid_tm',
        'SQLAlchemy',
        'transaction',
        'zope.sqlalchemy',
        'waitress',
        'wtforms',
        'webhelpers',
        'mysql-python',
        ]
    

これで修正は終了です。
動かしてみる前に、MySQL にデータベース「pyramid_blogr」を作成し、そのデータベースのためのユーザーを追加します。

MySQLに入る。
$ mysql -u root -p mysql

データベースの作成。
mysql> CREATE DATABASE pyramid_blogr CHARACTER SET utf8;

そのデータベースのユーザーの作成。
mysql> GRANT ALL ON pyramid_blogr.* TO pyramid_user@localhost IDENTIFIED by "password";


念のために、sqliete のデータファイル「pyramid_blogr.sqlite」を削除しておきます。
MySQL を使っていると言いつつも、このファイルができているときは sqlite をまだ使っているかも。
「development.ini」のあるディレクトリに「pyramid_blogr.sqlite」があります。
これからの作業をするために、そのディレクトリに移っておきます。

setup.py からもう一度。
$ ../bin/python setup.py develop
$ ../bin/pip install -e .
$ ../bin/initialize_pyramid_blogr_db development.ini
$ ../bin/pserve --reload development.ini

localhost:6543 を表示させると、sqlite の時と同じ Pyramid の画面が表示されます。



最後に、wsgi で動かせるようにします。localhost:6543 ではなく localhost/blogr で表示できます。
プロジェクト pyramid_blogr が置かれているディレクトリ、env_pyramid_blogr/ に pyramid.wsgi をつくります。
その内容はこう。このなかにある /Users/foo/env_pyramid_blogr の foo はあなたの名前(?)にしてください。
from pyramid.paster import get_app, setup_logging
ini_path = '/Users/foo/env_pyramid_blogr/pyramid_blogr/production.ini'
setup_logging(ini_path)
application = get_app(ini_path, 'main')

そして、mac os x 10.9 なら /etc/apache2/other/ にあるmodwsgi.conf を修正します。
env_first_app は、すでに動いているウェブアプリです。そんなものが無い人は、ここのところをばっさり切り取ってください。最初の「WSGIApplicationGroup %{GLOBAL}」の2行はそのままです。
それに、env_pyramid_blogr を追加します。
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On

# env_first_app
WSGIDaemonProcess pyramid_env_app user=foo group=staff threads=4 \
   python-path=/Users/foo/env_app/lib/python2.7/site-packages
WSGIScriptAlias /app /Users/foo/env_app/pyramid.wsgi
<Directory /Users/foo/env_app>
  WSGIProcessGroup pyramid_env_app
  Order allow,deny
  Allow from all
</Directory>

# env_blogr
WSGIDaemonProcess pyramid_env_blogr user=foo group=staff threads=4 \
   python-path=/Users/foo/env_pyramid_blogr/lib/python2.7/site-packages
WSGIScriptAlias /blogr /Users/foo/env_pyramid_blogr/pyramid.wsgi
<Directory /Users/foo/env_pyramid_blogr >
  WSGIProcessGroup pyramid_env_blogr
  Order allow,deny
  Allow from all
</Directory>

これで、localhost/blogr を見に行くと・・・
こけます。
mako の rendering エラーです。
What's New In Pyramid 1.5 をみると、mako の扱いが変わってしまったようです。
なぜターミナルからなら動くのか不明ですが・・・。パスが通っているからか?
修正します。
setup.py はこう。「'pyramid_mako',」を requires に追加します。「pyramid_chameleon」はあるので、これを使え、ということか。
requires = [
    'pyramid',
    'pyramid_mako', # <--- !!!
    'pyramid_chameleon',

pyramid_blogr/__init__.py の main() に「config.include('pyramid_mako')」を追加します。
    config = Configurator(settings=settings,
                      authentication_policy=authentication_policy,
                      authorization_policy=authorization_policy
                      )
    config.include('pyramid_mako') # <--- !!!
    config.add_static_view('static', 'static', cache_max_age=3600)

これで動くようになりました。



おまけ。
development.ini と production.ini にこれを追加すると
mako.directories = pyramid_blogr:templates

views.py やテンプレートで、こうだったのが、
renderer='pyramid_blogr:templates/index.mako'
こう書けるようになります。
renderer='index.mako'

テンプレートではこう。
<%inherit file="pyramid_blogr:templates/layout.mako"/>
が、
<%inherit file="layout.mako"/>
となります。


ついでに、Jinja2 を使う場合の修正について。
Mako は Python 風でいいかんじなんだけれど、やっぱり Jinja2 だ、という場合はこうします。Django ぽいですからね。

☆ development.ini
pyramid.includes = に「pyramid_jinja2」を追加します。
「mako.directories = pyramid_blogr:templates」を使っている場合は、その代わりに「jinja2.directories = pyramid_blogr:templates」とします。

☆ pyramid_blogr/__init__.py
config = のところに「config.include('pyramid_jinja2')」を追加します。
ついでに「config.add_renderer(".html", "pyramid_jinja2.renderer_factory")」をしておくと、テンプレートの拡張子を .html にすることができます。

テンプレートの修正です。テンプレートの拡張子は、.mako から .html にしてあります。
Mako から Jinja2 にするので修正が必要です。
「<% ... %>」は「{% ... %}」になります。そのほか、いろいろ。
☆ pyramid_blogr/templates/edit_blog.html
inherit は extends になります。
--- OLD
<%inherit file="layout.mako"/>

--- NEW
{% extends "layout.html" %}
{% block content_body %}

って、いちいちやろうかと思いましたが、たくさんあるので、全部載せます。
extends と block content_body は layout.html の修正にも関係します。
{% extends "layout.html" %}
{% block content_body %}
<form action="{{ request.route_url('blog_action',action=action) }}" method="post">
{% if action == 'edit' %}
{{ form.id() }}
{% endif %}

{% for error in form.title.errors %}
    <div class="error">{{ error }}</div>
{% endfor %}

<div><label>{{ form.title.label }}</label>{{ form.title() }}</div>

{% for error in form.body.errors %}
<div class="error">{{ error }}</div>
{% endfor %}

<div><label>{{ form.body.label }}</label>{{ form.body() }}</div>
<div><input type="submit" value="Submit"></div>
</form>
<p><a href="{{ request.route_url('home') }}">Go Back</a></p>

<style type="text/css">
form{
    text-align: right;
}
label{
    min-width: 150px;
    vertical-align: top;
    text-align: right;
    display: inline-block;
}
input[type=text]{
    min-width: 505px;
}
textarea{
    color: #222;
    border: 1px solid #CCC;
    font-family: sans-serif;
    font-size: 12px;
    line-height: 16px;
    min-width: 505px;
    min-height: 100px;
}
.error{
    font-weight: bold;
    color: red;
}
</style>
{% endblock content_body %}

☆ index.html
{% extends "layout.html" %}
{% block content_body %}
{% if user_id %}
    Welcome <strong>{{ user_id }}</strong> ::
    <a href="{{ request.route_url('auth', action='out') }}">Sign Out</a>
{% else %}
    <form action="{{ request.route_url('auth', action='in') }}" method="post">
    <label>User</label><input type="text" name="username">
    <label>Password</label><input type="password" name="password">
    <input type="submit" value="Sign in">
    </form>
{% endif %}

{% if paginator.items %}

    {{ paginator.pager() }}

    <h2>Blog entries</h2>

    <ul>
    {% for entry in paginator.items %}
    <li>
    <a href="{{ request.route_url('blog', id=entry.id, slug=entry.slug) }}">
    {{ entry.title }}</a>
    </li>
    {% endfor %}
    </ul>

    {{ paginator.pager() }}

{% else %}

<p>No blog entries found.</p>

{% endif %}

<p><a href="{{ request.route_url('blog_action',action='create') }}">
Create a new blog entry</a></p>
{% endblock content_body %}

☆ layout.html
「${ ... }」を「{{ ... }}」にします。これは何カ所かあります。
--- OLD
${request.static_url('pyramid_blogr:static/pylons.css')}
--- NEW
{{ request.static_url('pyramid_blogr:static/pylons.css') }}

ここは重要!!
「{% extends "layout.html" %} {% block content_body %}」に対応するところです。
--- OLD
${next.body()}
--- NEW
{% block content_body %}Content Body{% end block content_body %}


☆ view_blog.html
{% extends "layout.html" %}
{% block content_body %}
<h1>{{ entry.title }}</h1>
<hr/>
<p>{{ entry.body }}</p>
<hr/>
<p>Created <strong title="{{ entry.created }}">
{{ entry.created_in_words }}</strong> ago</p>

<p><a href="{{ request.route_url('home') }}">Go Back</a> ::
<a href="{{ request.route_url('blog_action', action='edit',
_query=(('id',entry.id),)) }}">Edit Entry</a>

</p>
{% endblock content_body %}


index.mako にある、これなんですが・・・
<% from pyramid.security import authenticated_userid
user_id = authenticated_userid(request)
%>
Mako にはできるんですが、Jinja2 には無いみたいなので、views.py をいじります。
☆ views.py
「from pyramid.security import authenticated_userid」を追加。
from pyramid.security import remember, forget, authenticated_userid

「@view_config(route_name='home', renderer='index.mako')」のように、テンプレートファイル名の拡張子を .mako から .html に変更。
「@view_config(route_name='home', renderer='index.html')」となります。

def index_page(request): で「user_id = authenticated_userid(request)」の行を追加して、return で返します。「return {'paginator':paginator, 'user_id':user_id}」
@view_config(route_name='home', renderer='index.html')
def index_page(request):
    user_id = authenticated_userid(request)
    page = int(request.params.get('page', 1))
    paginator = Entry.get_paginator(request, page)
    return {'paginator':paginator, 'user_id':user_id}

これで、Jinja2 化できたはず。

0 件のコメント:

コメントを投稿