SpineのtodoサンプルをSpine.appで書いてみた

SpineのtodoサンプルをSpine.appを使って書いてみた。これはBrunch(Backbone.js)みたいに、モデル、ビュー、コントローラーをバラバラに開発してbuildすると一つのjsファイル(cssも)にアセンブルしてくれるツールですね。テンプレートはecoを使っています。

index.coffeeの役割がわかりづらかったので理解するのにちょっと時間がかかった。

hem model task

とかやるといっしょにテストのひな形が作られるのとlocalhost:9294/testにアクセスすればjasmineのテストが走るようになっているので使いやすそう。

そして以下の素晴らしい三部作を読んでSinon.jsの便利さに目覚めたので積極的に使っていこうと感じた。

Spine.Model.AjaxつまりRESTのAPIはFlaskを使おうと思っているんだが、Flask-Restlessを利用するのが手っ取り早くていいかなぁと。

FlaskでOpenIDを使う

Flask-OpenIDのサンプルアプリを Flask-SQLAlchemyPyJadeを使って書きなおしてみた。

ディレクトリ構造

$ tree
.
├── app.py
└── templates
    ├── create_profile.jade
    ├── index.jade
    ├── layout.jade
    └── login.jade

app.py

 import os
from flask import Flask, request, session, g, \
    redirect, url_for, abort, render_template, flash, jsonify
from flaskext.openid import OpenID
from flaskext.sqlalchemy import SQLAlchemy

DEBUG = True
SECRET_KEY = 'openidsample'
databese_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'oidsample.db')
SQLALCHEMY_DATABASE_URI = 'sqlite:///%s' % databese_file

app = Flask(__name__)
app.config.from_object(__name__)
app.jinja_env.add_extension('pyjade.ext.jinja.PyJadeExtension')
db = SQLAlchemy(app)
oid = OpenID(app, os.path.join(os.path.dirname(__file__), 'openid'))

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60))
    email = db.Column(db.String(200))
    openid = db.Column(db.String(200))

    def __init__(self, name, email, openid):
        self.name = name
        self.email = email
        self.openid = openid

@app.before_request
def lookup_current_user():
    g.user = None
    if 'openid' in session:
        g.user = User.query.filter_by(openid=session['openid']).first()

@app.route('/')
def show_index():
    return render_template('index.jade')

@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
    if g.user is not None:
        return redirect(oid.get_next_url())
    if request.method == 'POST':
        openid = request.form.get('openid')
        if openid:
            return oid.try_login(openid, ask_for=['email', 'fullname', 'nickname'])
    return render_template('login.jade', next=oid.get_next_url(),
                           error=oid.fetch_error())

@oid.after_login
def create_or_login(resp):
    session['openid'] = resp.identity_url
    user = User.query.filter_by(openid=resp.identity_url).first()
    if user is not None:
        flash(u'Successfully signed in')
        g.user = user
        return redirect(oid.get_next_url())
    return redirect(url_for('create_profile', next=oid.get_next_url(),
                            name=resp.fullname or resp.nickname,
                            email=resp.email))

@app.route('/create-profile', methods=['GET', 'POST'])
def create_profile():
    if g.user is not None or 'openid' not in session:
        return redirect(url_for('index'))
    if request.method == 'POST':
        name = request.form['name']
        email = request.form['email']
        if not name:
            flash(u'Error: you have to provide a name')
        elif '@' not in email:
            flash(u'Error: you have to enter a valid email address')
        else:
            flash(u'Profile successfully created')
            db.session.add(User(name, email, session['openid']))
            db.session.commit()
            return redirect(oid.get_next_url())
    return render_template('create_profile.jade', next_url=oid.get_next_url())

@app.route('/logout')
def logout():
    session.pop('openid', None)
    flash(u'You were signed out')
    return redirect(oid.get_next_url())

if __name__ == '__main__':
    app.run()

layout.jade

bootstrap2をちょっと使いまわした。

!!! 5
html.no-js(lang='en')
  head
    title openid sample
  body
  .container
    block body

index.jade

extends "layout.jade"
block body
    if g.user
    h1 you are logged in as {{ g.user.name }}
    p
      a(href="{{ url_for('logout') }}") Logout
  else
    h1
      a(href="{{ url_for('login') }}") Login

login.jade

Flask-OpenIDのサンプルをhtml2jadeで変換した。

extends "layout.jade"
block body
  h2 Sign in
  form(action='', method='post')
    | {% if error %}
    p.error
      strong Error:
      | {{ error }}
    | {% endif %}
    p
      | OpenID:
      input(type='text', name='openid', size='30')
      input(type='submit', value='Sign in')
      input(type='hidden', name='next', value='{{ next }}')

create_profile.jade

Flask-OpenIDのサンプルをhtml2jadeで変換した。

extends "layout.jade"
block body
  h2 Create Profile
  p
    | Hey!  This is the first time you signed in on this website.  In
    | order to proceed we need a couple of more information from you:
    form(action='', method='post')
      dl
        dt
          | Name:
          dd
            input(type='text', name='name', size='30', value="{{ request.values.name }}")
            dt
              | E-Mail:
              dd
                input(type='text', name='email', size='30', value="{{ request.values.email }}")
      p
        input(type='submit', value='Create profile')
        input(type='hidden', name='next', value="{{ next }}")
    p If you don't want to proceed, you can a(href="{{ url_for('logout')}}") sign out again.

データベースを初期化するのに

>>> from app import db
>>> db.create_all()

と叩く必要がある。

FlaskのテストでAssertionError: Popped wrong request context.

Flask-Testingでテストを書いていて

@app.route('/json/tags')
@cache.cached(timeout=600)

みたいにキャッシュしているurlにgetすると

def test_get_json_tags(self):
    response = self.client.get("/json/tags")
    self.assert200(response)

タイトルのように

AssertionError: Popped wrong request context.

って怒られるんだが、どうすればいいのかわからない。

あと、テストブームが来たのでPython Testing: Beginner's Guideを注文してしまった。

ProductName Python Testing: Beginner's Guide
Daniel Arbuckle
Packt Publishing / 3220円 ( 2010-01-31 )


Flask-TestingのAssertion一覧

Flask-Testingを使おうと思ったんだけど、assertionの種類がドキュメントに載ってなかったのでソース読んだ。

assertTemplateUsed

指定されたテンプレートが使用されているかどうか調べる。

assertContext

コンテキスト中に指定された名前の変数が存在するかどうかと、その変数の値が想定通りかチェックする。

assertRedirects

リダイレクトされるかどうか。 status_codeが301か302であるかどうかを調べてる。

assertStatus

response statusが正しいか調べる。以下のメソッドも用意されている。

  • assert200
  • assert400
  • assert401
  • assert403
  • assert404
  • assert405

twill

twillっていうWeb browsing用のユーティリティも用意されているんだけどどういう時に使うのかよく分からなかった。codeとfindとnot findがあるのは分かるんだけど、テストにどう組み込んだらいいのかよくわからん。

flask-debugtoolbarが便利

うまく動かなかったのでflask-scriptとflask-sqlalchemyを入れたらちゃんと動くようになった。

Flaskのバージョン、応答時間、HTTPヘッダ、リクエスト変数、テンプレートやSQLAlchemyやロギング情報がツールバーに表示されるようになる。

flask-debugtoolbar

何もよりもツールバーが出ているとapp.DEBUGがTrueになっていることが明確なので本番稼働の際にfalseし忘れがない。

moin2の16の特徴の中から気になったもの

MoinMoin 2.0のビッグニュースに16個あげられていたんだが気になったものを

Sphinx-based docs

Sphinxベースのドキュメントになって読みやすい。でもmake pdflatexはこけた

Storage Layers: stores, backends, middlewares

ストレージにファイルだけではなくsqlalchemyで使えるRDBやKyoto Cabinetなんかも使える。

Tree based transformations

これに惹かれた

input -> converter -> DOM tree -> converter -> output

という変換経路をたどるのでフォーマットの変換ができる。つまりwikiのコンテンツを好きなフォーマットで取り出せるので、特にReSTで取り出してSphinxにもっていけるようになるのでデータの再利用がしやすそう。

GUI editor update and different approach

ckeditorが使える。WYSIWYGはイントラのサービスでは必須。

Themeing with Jinja2 templating engine

Flask-Themesを使っているので、自分用のテーマを作ってみる予定

Packaging

virtualenvのおかげで、環境を汚さずすむので便利。あとquickinstallスクリプトがインストールの面倒をほぼすべて見てくれるので超楽チン

moin2はFlaskベース

コミュニティーベースのコラボレーションツールを探している。

個人的にはSphinxのWeb Supportがいいんじゃないかと思っているんだが、うちのITリテラシーを考えるとちょっと無理すぎるかなぁと断念した。

論文が電子化されているけどpdfは単なる紙の模倣でつまんないがHTML化されてパラグラフ単位とかfigure単位でコメントしたり編集できたりすれば、レビューも作りやすいし、実験はいいけど解釈はクズみたいなトータルでの判断に悩むような論文も切り取れるし、レターみたいな内容が薄いんだか知識を入れ込めなかったんだか分からないような中途半端な文章も好き勝手に補足できる。つまり真のマッシュアップが待っているわけです。

単語に分解して相関を取るとかそういうのとは逆方向に、文脈をつなぎあわせてより抽象とか本質を目指すようなことがやりやすくなるわけですな。正直論文の最後に載っているリファレンスは、島根の場所が知りたいのに日本のあたりを指してるわけで残念な仕組みだよなと思ってるのでもう少し、位置情報を高精度化すれば面白いのにとずっと思っているんだけどなかなかそうはならないのはみんな著者のストーリーを読むのが好きなのかね?他人事なのでどうでもいいけど。

で、音でいうところのサンプリングというか、パラグラフ単位でコメントを入れられる仕組みっていうのは未来があると思っている、RWHで未来を見たというかちょっと感動した。まぁ、そういうのをイントラに構築できたら良かったんだけど現状は難しそうだ(ヨーヨーヨー)。

かといってwikiはなぁ、、、Sphinxっぽく文書出力できる良い感じのwikiが欲しいなぁと思いながら調べていたらmoin2がFlaskベースでフォーマットの変換がしっかりしてそう。ReSTでもOKなのでSphinx->pdfってのもやりやすそう。

moin2

あとは知っているフレームワークなので手を入れやすそうってのもある。化学構造のエディタとか組み込まないといけないし。それからmoin2のソースコード読むのも勉強になるし、モチベーション的にもよろしいです。

まぁそんな感じで今年は、wikiベースの創薬用コラボレーションツールを作ってみるかなと。

Flaskでhamlish-jinjaを使う

Express+Jadeで幾つか書いていたらJadeの読みやすさに慣れてしまった。

FlaskでもHamlっぽい記法を使いたいなぁとググッてみたらhaml and flaskというエントリをみつけたので、この通りに入れてみた。

-extends "layout.html"

-block title
  Page Not Found

-block body
  %h1 << Page Not Found
  %p
    %a href="{{ url_for('show_entries') }}" << go somewhere nice

  %p << or find by tag
  %div.tags
  %script type="text/javascript"
    |$.getJSON('{{ url_for('show_jsontags') }}', null, 
    |  function(json){
    |    for (i in json.tags.sort()){
    |      $('div.tags').append("<a href=\"/tag/" + json.tags[i] + "\">" + json.tags[i] + "</a> ");
    |    }
    |  }
    |);

かなり読みやすくなった。入れ子をインデントで表現するのでPythonistaにはありがたいですね。ちなみにこっちがもとのjinjaのテンプレート。

{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block body %}
  <h1>Page Not Found</h1>
  <p><a href="{{ url_for('show_entries') }}">go somewhere nice</a></p>
  <h1>or find by tag</h1>
  <div class=tags></div>
<script type="text/javascript">
$.getJSON('{{ url_for('show_jsontags') }}', null, 
function(json){
  for (i in json.tags.sort()){
   $('div.tags').append("<a href=\"/tag/" + json.tags[i] + "\">" + json.tags[i] + "</a> ");
  }
}
);
</script>
{% endblock %}

pipとかeasy_installで0.1を入れるとdiv#idとかdiv.classという記法が使えないので、GitHubの最新版を入れたほうがよいです。


ところで、HTML5においてセマンティクスが重視されると、デザインまわりはCSSに集約されていきHTMLタグとデザインっていうのは分離されていくと思うんですね(rdfとか昔っからあるし)。

セマンティクスってデザイナーというよりはプログラマー側が考えることだろうから、マークアップエンジニアとか呼ばれているヒト達はCSSエンジニアとかそういう名称になっていくの?

よくわからんので、今度誰かに聞いてみよう

ProductName HTML5&CSS3実践入門 最新Web標準を使いこなす (The Pragmatic Programmers)
ブライアンP.ホーガン
インプレスジャパン / 2940円 ( 2011-07-08 )


静岡(東部のあたり)ではHTML5の入門書の読書会をしています(宣伝)

Flask-Cacheを使ってみた

自作のブログシステムのなかにデータベースに頻繁にアクセスしすぎて困るAPIがあるので、Flask-Cacheを使ってキャッシュするようにした。

設定を読ませてからcacheの設定をしないといけない(まぁ当たり前か)のだけどapp.config.from_objectで読ませる前にCacheしてたらキャッシュが効かなくてちょっとはまった。

CACHE_TYPE = 'simple'

app = Flask(__name__)
app.config.from_object(__name__)
cache = Cache(app) # 設定読ませたあとに

ソースは読んでたらキャッシュのタイプはwerkzeugに任せているらしいので、そっちのドキュメントを読めば大丈夫な感じですね。

blohg - Mercurial+Flaskのブログシステム

blohgってのが面白そうだったのでコードを読みつつ手元のmacbookにインストールして触ってみた。

sudo easy_install-2.7 blohg
mkdir myblohg
cd myblohg/
blohg initrepo
hg commit -Am 'initial commit'
blohg runserver

エントリを追加する場合にはcontent/post/にあたらしくrstファイルを追加してaddしてコミットする。

blohg

で、コード読んでたら

from werkzeug.contrib.atom import AtomFeed, FeedEntry

ってあって、werkzeugにフィード生成用の仕組みが用意されていることを知った。自分のブログ(Flask製)ではfeedgeneratorを使っているのだけど、こっちでもいいかなと思った。

おまけ

fujinismもblohgベースに変えようかなぁ