Pylonsでメモアプリ(4)

メモにタグ付けてタグクラウドを表示させてみます。

タグの登録は、[tag1][tag2]のようにはてなっぽく[]で囲ったものをタグとして登録するようにしたい。

メモの登録時の処理はこんな感じにしてみた。
・controllers/notes.py

from note.lib.tagcloud import *

# メモの作成
def create(self):
    note = Note()
    note.title = self.form_result['title']
    note.content = self.form_result['content']
    if self.form_result['tags'] != "":
      taglist = tag_parse(self.form_result['tags'])
      for t in taglist:
        tag = self.session.query(NoteTag).get_by(name=t) or None
        if tag is None:
          note.tags.append(NoteTag(t))
        else:
          note.tags.append(tag)
    note.user_id = 1
    self.session.save(note)
    self.session.flush()
    redirect_to('/notes')

・lib/tagcloud.py

def tag_parse(tags):
    import re
    p = re.compile(r'\[(.+?)\]')
    taglist = p.findall(tags)
    if len(taglist) == 0:
      taglist.append(u"未定義")
    return taglist

[]で囲まれた部分があればそれを返し、Noteモデルのtagsプロパティにappendしてやれば、SQLAlchemyの定義で関連付けしてあるのでsave()時に登録される。

もし入力されたタグが一度登録されたものだったら見つかったタグを追加、一度も登録されていなければ新しくモデルを作成して追加する。

新規登録が出来たので、変更もできるようにする。
・controllers/notes.py

# 編集フォームを表示
def edit(self, id, format='html'):
    c.note = self.session.query(Note).get_by(id=id)
    c.form_result = {
        'title': c.note.title,
        'content': c.note.content,
        'tags': tag_text(c.note.id, "note")}
    return render('_form')

# 変更を保存する
def update(self, id):
    note = self.session.query(Note).get_by(id=id)
    note.title = self.form_result['title']
    note.content = self.form_result['content']
    taglist = tag_parse(self.form_result['tags'])
    for t in taglist:
        tag = self.session.query(NoteTag).get_by(name=t) or None
        if tag is None:
            note.tags.append(NoteTag(t))
        else:
            note.tags.append(tag)
    self.session.flush()
    redirect_to('/notes')

・/lib/tagcloud.py

def tag_text(id, app):
    text = u""
    current = model.sac.session_context.current
    if app == "note":
      from memo.model import NoteTag
      taglist = current.query(NoteTag).select(id=id)
    for t in taglist:
      text += "[" + t.name + "]"
    return text

tag_text()でタグにを追加して返してます。
いろいろなタグを返すようにしようと思ったので、引数にアプリ名を取って判別することにしました。
表示時は
を追加して、DBに突っ込む時は[]を取り除く感じ。

そうそう、モデルのcreate時はsave(note)してflush()だけど、update時はflush()だけですね。


と、いう具合で登録と変更が出来るようになったので、いよいよタグの表示です。
タグの多さに応じてフォントのサイズが変わるのってどうやんだろ?と思って調べてみたところ、http://kjirou.sakura.ne.jp/mt/2007/09/post_57.html:こちらのブログがとても参考になりました。
というかそのまま実装しました。。。

表示時にやることとしては、

  • タグ名とその総数を取得
  • タグの総数に応じて重みを付けフォントサイズを決定
  • 決定したフォントサイズでタグ名を表示する

・controllers/notes.py

def __before__(self):
    self.session = model.sac.session_context.current
    self.session.clear()
    TagCloud()

def index(self, format='html'):
    if request.params.has_key('s') is False:
      start = 0
    else:
      start = int(request.params['s'])
    notes = self.session.query(Note).filter_by(user_id=1).all()
    Pager(notes, start, 20)
    return render('list')

・lib/tagcloud.py

class TagCloud(object):
  def __init__(self):
    sql = select([notetags.c.name, func.count(note_tag_relation.c.notetag_id), notes.c.user_id], and_(note_tag_relation.c.notetag_id == notetags.c.id, note_tag_relation.c.note_id == notes.c.id, notes.c.user_id == 1)).group_by(notetags.c.name)
    rows = sql.execute().fetchall()

    c.tag = set_fontsize(rows)

def set_fontsize(rows):
    import math
    rowlist = list()
    maxnum = math.ceil(math.sqrt(max([i[1] for i in rows])))
    minnum = math.ceil(math.sqrt(min([i[1] for i in rows])))
    mw = 24 / (maxnum - 1)
    for row in rows:
      level = (math.ceil(math.sqrt(row[1])) - minnum) * mw
      l = list(row)
      l[1] = level + 12
      rowlist.append(l)
    return rowlist

タグクラウドはサイドバーに常に表示するので、__before__()の中で作成してます。

タグ名とタグ総数をSQLAlchemyで取得するところは、何か上手いやり方思いつかなかった
ので、select関数を使ってやってみました。普通にSQL文書くのとあまり変わらないんじゃ・・というのはさておき。表示されるSQLは結構綺麗になったのでまあいいかと。

タグの重み付けはset_fontsize()に取得したタグリストを渡して、上のブログで紹介されてるやり方をまんま実装。
SQLAlchemyで返されるのはタプルで変更不可なので、リストオブジェクトに入れなおして返してます。

最後にテンプレートで表示すれば完成(?)
・templates/base.mak

<!-- tags -->
<div class="side_tags">
  <h3>Tags</h3>
  <ul class="tag_list">
  % for t in c.tag:
  <li><a href="/notes/tag/${str(t[0])}" style="font-size: ${t[1]}pt;">${str(t[0])}</a>
  </li>
  % endfor
  </ul>
</div>

でけたー!
何か動くと嬉しいですねやっぱり。


前回でページャ、今回でタグクラウドが表示できて基本的な機能は揃ってきたので、次回はバリデーションについてちゃんと調べてみようと思います。