ゴミ箱の中のメモ帳

まだ見ぬ息子たちへ綴る手記

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 (PERFECT SERIES 5)


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>&copy; 2013 mon0.jp.</p>
      </div>

    </div>
  </body>
</html>


パーフェクトPython (PERFECT SERIES 5)

パーフェクトPython (PERFECT SERIES 5)

初めてのPython 第3版

初めてのPython 第3版

エキスパートPythonプログラミング

エキスパートPythonプログラミング

Pythonプロフェッショナルプログラミング

Pythonプロフェッショナルプログラミング