BottleとPeeweeで掲示板を作ってみた
PythonモジュールのBottleとPeeweeのドキュメントの翻訳を考えていると前の記事「PythonのBottleとPeeweeの日本語訳」に書いたが、BottleはまだしもPeeweeは知名度が低いと感じたので私なりのサンプルを作ってみた。
サンプルは簡単な掲示板。動作確認は一通りしたが、入力チェックなどは行なっていないので期待通りの入力をしないとエラーになるかと思う。
GitHubに「bottle.peewee.sample」としてコードを上げているので、詳しくはそちらを参考にして欲しい。BottleとPeeweeモジュールを添付しているので、index.pyを実行するとそのままローカルホストでサーバが立ち上がる。それほどお手軽に動かせるのも魅力。
Python 2.7.3で動作を確認している。
パーフェクトPython (PERFECT SERIES 5)
- 作者: Pythonサポーターズ,露木誠,ルイス・イアン,石本敦夫,小田切篤,保坂翔馬,大谷弘喜
- 出版社/メーカー: 技術評論社
- 発売日: 2013/03/05
- メディア: 大型本
- 購入: 1人 クリック: 65回
- この商品を含むブログ (30件) を見る
Peeweeを使ったアプリケーションは現在製作中だが、完成させたのはこれが初めてになる。初めてでもPeeweeドキュメントのクイックスタートを読み、あとはドキュメントを検索しながら読めば簡単に作れた。
Bottleは今までに数個のアプリケーションを作ったことがあるが、ルーティングとテンプレートしか使ってないのでまともに理解できていないかと思う。だからこそドキュメントを翻訳して理解力を深めたい。
このサンプルはテンプレートも含めて2時間くらいで制作できた。Bottleの素晴らしさは以前から感じていたが、一度アプリケーションを制作するとPeeweeの素晴らしさも実感できる。そしてさらに、BottleとPeeweeが相まみえたときそれらは相互に強力に補完し合い、強力な環境が出来上がる。
今までは何でもかんでもDjangoを使おうとしていた部分があったが、この二つがあればDjangoを使うまでもない小さなアプリケーションや、Djangoがインストールできないような環境では強力に使える。BottleもPeeweeも単ファイルのモジュールなのでファイルをコピーするだけで使える。
マジで素晴らしい。
このコードについて、もっと効率のいい方法があったり、問題があれば是非とも指摘いただきたいです。
GitHubにもコードを上げているがこちらのブログにもコードを貼りつけておく。
index.py
# coding: UTF-8 #!/usr/bin/python from lib.bottle import get, post, request, run, template, static_file, redirect, html_escape from lib import peewee import os import sys import datetime page_per_entry = 5 master_password = "password" BASE_DIR = os.path.dirname( os.path.abspath( __file__)) STATIC_DIR = os.path.join( BASE_DIR, 'static') db_filename = "./db.sqlite" db = peewee.SqliteDatabase( db_filename) class BaseModel( peewee.Model): class Meta: database = db class EntryModel( BaseModel): title = peewee.CharField() body = peewee.TextField() name = peewee.CharField() password = peewee.CharField() created_at = peewee.DateTimeField() class Meta(): order_by = ( "-created_at",) def create_db(): EntryModel.create_table() def load_entries( page=1): entries = EntryModel.select().paginate( page, page_per_entry) return entries def load_entry( entry_id): entry = EntryModel.select().where( EntryModel.id==entry_id) return entry def save_entry( title, body, name, password="", entry_id=None): if entry_id is not None: entry = load_entry( entry_id) entry.title = title entry.body = body entry.name = name entry.save() else: EntryModel.create( title=title, body=body, name=name, password=password, created_at=datetime.datetime.now()) def delete_entry( entry): entry.delete_instance() def render( template_name="index", entries=[], page=1): entry_count = EntryModel.select().count() pages = int( float( entry_count) / page_per_entry + 0.5) return template( template_name, entries=entries, pages=pages, current_page=page) @get( "") @get( "/") def index_view(): entries = load_entries() return render( entries=entries, page=1) @get( "/page/<paginate:int>/") def page_view( paginate): entries = load_entries( paginate) return render( entries=entries, page=paginate) @get( "/entry/<entryid:int>/") def entry_view( entryid): entries = load_entry( entryid) return render( entries=entries, page=1) @post( "/new/") def new_view(): title = request.forms.title body = request.forms.body name = request.forms.name password = request.forms.password save_entry( title, body, name, password) return redirect( "/") @post( "/delete/") def delete_view(): entry_id = request.forms.entry_id password = request.forms.password entry = load_entry( entry_id) if entry[0].password == password or master_password == password: delete_entry( entry[0]) return redirect( "/") @get( "/static/<filename:path>") def static_view( filename): return static_file( filename, root=STATIC_DIR) if __name__ == "__main__": if not os.path.exists( db_filename): create_db() run( host="localhost")
index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Bottle Peewee BBS</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap-theme.min.css"> <link type="text/css" rel="stylesheet" href="/static/css/common.css"> </head> <body> <div class="container"> <div class="header"> <ul class="nav nav-pills pull-right"> <li><a href="/">Home</a></li> <li><a href="#new">New</a></li> <li><a href="#remove">Remove</a></li> </ul> <h3 class="text-muted">Sample BBS</h3> </div> <div class="row marketing"> <div style="text-align: center;"> <ul class="pagination pagination-lg"> % for page in range( pages): % if page + 1 == current_page: <li class="active"> % else: <li> % end <a href="/page/{{page + 1}}/">{{page + 1}}</a> </li> % end </ul> </div> % for entry in entries: <h4><a href="/entry/{{entry.id}}/">{{entry.id}}. {{entry.title}}</a></h4> <h5 style="text-align: right;">{{entry.created_at.strftime( "%Y.%m.%d %H:%M")}} By. {{entry.name}}</h5> <p style="white-space: pre; margin: 1.4em;">{{entry.body}}</p> <hr> % end <div style="text-align: center;"> <ul class="pagination pagination-lg"> % for page in range( pages): % if page + 1 == current_page: <li class="active"> % else: <li> % end <a href="/page/{{page + 1}}/">{{page + 1}}</a> </li> % end </ul> </div> </div> <div class="jumbotron" id="new"> <h1>New Entry</h1> <form method="POST" action="/new/" role="form"> <div class="form-group"> <label for="inputTitle">Title</label> <input type="text" class="form-control" id="inputTitle" placeholder="Enter Title" name="title"> </div> <div class="form-group"> <label for="inputName">Name</label> <input type="text" class="form-control" id="inputName" placeholder="Enter Your Name" name="name"> </div> <div class="form-group"> <label for="inputPassword">Password</label> <input type="password" class="form-control" id="inputPassword" placeholder="Enter Remove Password" name="password"> </div> <div class="form-group"> <label for="inputTitle">Body</label> <textarea name="body" class="form-control" id="inputBody" rows="5"></textarea> </div> <div class="form-group"> <input type="submit" value="Submit" class="btn btn-lg btn-primary btn-block"> </div> </form> </div> <div class="jumbotron" id="remove"> <h1>Remove Entry</h1> <form method="POST" action="/delete/" role="form"> <div class="form-group"> <label for="inputEntry">Remove Entry Id</label> <select name="entry_id" class="form-control" id="inputEnt"> % for entry in entries: <option value="{{entry.id}}">{{entry.id}}</option> % end </select> </div> <div class="form-group"> <label for="inputPassword">Password</label> <input type="password" class="form-control" id="inputPassword" placeholder="Enter Remove Password" name="password"> </div> <div class="form-group"> <input type="submit" value="Remove" class="btn btn-lg btn-warning btn-block"> </div> </form> </div> <div class="footer"> <p>© 2013 mon0.jp.</p> </div> </div> </body> </html>
パーフェクトPython (PERFECT SERIES 5)
- 作者: Pythonサポーターズ,露木誠,ルイス・イアン,石本敦夫,小田切篤,保坂翔馬,大谷弘喜
- 出版社/メーカー: 技術評論社
- 発売日: 2013/03/05
- メディア: 大型本
- 購入: 1人 クリック: 65回
- この商品を含むブログ (30件) を見る
- 作者: Mark Lutz,夏目大
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/02/26
- メディア: 大型本
- 購入: 12人 クリック: 423回
- この商品を含むブログ (133件) を見る
- 作者: Tarek Ziade,稲田直哉,渋川よしき,清水川貴之,森本哲也
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2010/05/28
- メディア: 大型本
- 購入: 33人 クリック: 791回
- この商品を含むブログ (90件) を見る
- 作者: ビープラウド
- 出版社/メーカー: 秀和システム
- 発売日: 2012/03/26
- メディア: 単行本
- 購入: 6人 クリック: 765回
- この商品を含むブログ (27件) を見る