テキストのエンコードを変換するUtility #python

pythonで文字コードの変換を行うときにちょっとだけ簡単になるよ。easy_install,pipでインストールできるよ。
easy_install textconverter
pip install textconverter

まぁ、これがやりたかっただけなんだけど。
from textconverter import convert

text = 'こんにちは'
text = convert.to_utf8(text)
text = convert.utf8_to_eucjp(text)

ソース:https://github.com/ukyo/textconverter
#coding: utf8

import types
import chardet

from method_missing import MethodMissing as MM


class TextConverter(MM):
def method_missing(self, name, *args, **kw):
if len(args) == 1:
return self.convert(name, args[0])
else:
return (self.convert(text) for text in args)

def convert(self, name, text):
if name.startswith('to_'):
encode_to = name[3:]
if type(text) == types.UnicodeType:
return text.encode(encode_to)
else:
encode_from = chardet.detect(text)['encoding']
return unicode(text, encode_from).encode(encode_to)
elif '_to_' in name:
encode_from, encode_to = name.split('_to_')
return unicode(text, encode_from).encode(encode_to)
else:
raise AttributeError, name

convert = TextConverter()
posted by 右京 | Python

method missingを利用したtwitter apiラッパー #python

ちょっと前に作ったmethod missingのpython実装。

こやつは下のように使う。
class Api(MethodMissing):
def __init__(self, auth=None, convert_to_dict=True):
self._auth = auth or NoAuth()
self.convert = CONVERT if convert_to_dict else NOT_CONVERT

def method_missing(self, name, *args, **kw):
if 'POST' in args:
method = 'POST'
elif 'PUT' in args:
method = 'PUT'
elif 'DELETE' in args:
method = 'DELETE'
elif kw.has_key('method'):
method = kw['method']
del kw['method']
else:
method = 'GET'
url = 'http://api.twitter.com/1/' + name.replace('__','/') + '.json'
return self.fetch(url, method, **kw)

def raw_response(self, url, method, **params):
return urllib2.urlopen(self._auth.generate_request(url, method, params))

def fetch(self, url, method, **params):
return self.convert(self.raw_response(url, method, **params).read())

で、下のように使えば色々できる。
import tinytwitter as tw

api = tw.Api()

#http://api.twitter.com/1/search.json?q=twitterにリクエストを送る
api.search(q='twitter')
#http://api.twitter.com/1/statuses/public_timeline.jsonにリクエストを送る
#'__'が'/'に変換される
api.statuses__public_timeline()

まぁ、補完が全くきかないという問題はあります。

コード:
https://github.com/ukyo/tinytwitter
posted by 右京 | Python

pythonでmethod missingできたぞ

百聞は一見にしかず。以下サイトでコピペ実行すべし。
Online Python Tutor: Write Python code online and single-step through its execution
class A(object):
def __getattr__(self, name):
try:
return self.__getattribute__(name)
except AttributeError:
def hoge():
print name
self.__dict__[name] = hoge
return self.__dict__[name]

a = A()

a.hoge()
a.fuga()
a.hoge()

追記:
一般化してみた。
class MethodMissing(object):
def __getattr__(self, name):
try:
return self.__getattribute__(name)
except AttributeError:
def method(*args, **kw):
return self.method_missing(name, *args, **kw)
return method

def method_missing(self, name, *args, **kw):
raise AttributeError

class A(MethodMissing):
def method_missing(self, name, *args, **kw):
print 'called %s' % name

a = A()
a.hogehoge()
a.unco()
a.buhiiiiiii()
posted by 右京 | Python

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdbの対処法

結論から書きます。
$ sudo apt-get install python-mysqldb


環境:ubuntu 11.04

エラー:
$ python manage.py syncdb
Traceback (most recent call last):
File "manage.py", line 14, in <module>
execute_manager(settings)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 438, in execute_manager
utility.execute()
File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 379, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 261, in fetch_command
klass = load_command_class(app_name, subcommand)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 67, in load_command_class
module = import_module('%s.management.commands.%s' % (app_name, name))
File "/usr/local/lib/python2.7/dist-packages/django/utils/importlib.py", line 35, in import_module
__import__(name)
File "/usr/local/lib/python2.7/dist-packages/django/core/management/commands/syncdb.py", line 7, in <module>
from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal
File "/usr/local/lib/python2.7/dist-packages/django/core/management/sql.py", line 6, in <module>
from django.db import models
File "/usr/local/lib/python2.7/dist-packages/django/db/__init__.py", line 78, in <module>
connection = connections[DEFAULT_DB_ALIAS]
File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 93, in __getitem__
backend = load_backend(db['ENGINE'])
File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 33, in load_backend
return import_module('.base', backend_name)
File "/usr/local/lib/python2.7/dist-packages/django/utils/importlib.py", line 35, in import_module
__import__(name)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/mysql/base.py", line 14, in <module>
raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb
posted by 右京 | Python

django+mysqlでdotcloudを始める #dotcloud #git #django

githubに置いてあるのでそこからどうぞ
https://github.com/ukyo/dotcloud-django

githubにのほうにも書いてあるんですが下のようにすればdjangoのアプリができるはず。
dotcloud create your-app-name
git clone git://github.com/ukyo/dotcloud-django.git your-dotcloud-project
cd your-dotcloud-project
dotcloud push your-app-name
dotcloud run your-app-name.www python current/mysite/manage.py syncdb

それでもってdotcloudはカレントディレクトリに.gitがあるとgitレポジトリ的な動きをするので、なんか変更したときは下のような感じでアップロードできる。
git add .
git commit -m 'message'
dotcloud push your-app-name

ただしpullとかはできないっぽい。できたら便利なんだけどな。

仕組み:
mysql
dotcloudでは環境設定的な情報は/home/dotcloud/environment.jsonに入っているのでそこから下のような感じで読み込んでsetting.pyを動的に編集する。
import json
dotcloud_env = json.load(open('/home/dotcloud/environment.json'))

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysql',
'USER': dotcloud_env['DOTCLOUD_DB_MYSQL_LOGIN'],
'PASSWORD': dotcloud_env['DOTCLOUD_DB_MYSQL_PASSWORD'],
'HOST': dotcloud_env['DOTCLOUD_DB_MYSQL_HOST'],
'PORT': dotcloud_env['DOTCLOUD_DB_MYSQL_PORT'],
}
}

template置き場
/home/dotcloud/currentが色々アップロードしたファイルどものルートなので/home/dotcloud/current/django_templates/を作ってそこにテンプレート置くようにする。もうちょっとうまいやり方がある気がするけど、とりあえずこれで。
TEMPLATE_DIRS = (
'/home/dotcloud/current/django_templates',
)

今のところの問題点としてはこのままだとローカル環境で動かないので修正する必要あり。多分すぐ直せるかな。

追記:
開発環境用のsettings.pyを書いた。修正箇所は以下。
import os
import json

#If local DEBUG is True.
DEBUG = os.environ['USER'] != 'dotcloud'

#...

#load mysql environment.
if DEBUG:
env_path = os.path.abspath('../local_environment.json')
else:
env_path = '/home/dotcloud/environment.json'

dotcloud_env = json.load(open(env_path))

#...

TEMPLATE_DIRS = (
os.path.abspath('../django_templates'),
)

mysqlの設定はルートにあるlocal_environment.jsonに書けばOK。あと、settings.pyのSECRET_KEYは適宜書き換えてね。
posted by 右京 | Python

[backbone.js][kay framework]RESTfulなmodelとview

Backborn.jsを使いたかったんです。kay frameworkで。ついでに動的にモデルも作ってみたかったんです。動的につくることができたらサーバ側でやることが殆ど無くなってうれしいでしょ。で、試しにBackbone Demo: TodosのlocalStorageの部分をurl:'/todo'に変えてみたら動きました。いやっほー、未来は明るいね!(なんだ、このテンション)

models.py
# -*- coding: utf-8 -*-
# myapp.models

from google.appengine.ext import db
import datetime
import kay.db

# Create your models here.

accepted_model = set([
'todo'
])

class NotAcceptedModelError(Exception):
pass


class Expando(db.Expando):
user = kay.db.OwnerProperty()
created = db.DateTimeProperty(auto_now_add=True)
updated = db.DateTimeProperty(auto_now=True)

@classmethod
def create(cls, model, dct, id=None):
props = dir(cls)
ins = model.get_by_id(id) if id else model()
for k, v in dct.iteritems():
if k not in props:
setattr(ins, k, v)
ins.put()
return ins.to_dict()

@classmethod
def read(cls, model, dct, id=None):
return model.get_by_id(id).to_dict() if id else [ins.to_dict() for ins in model.all()]

@classmethod
def destroy(cls, model, dct, id=None):
model.get_by_id(id).delete()
return {'delete' : id}

def to_dict(self):
dct = {}
for k, v in self.__dict__['_entity'].iteritems():
if not k.startswith('_'):
dct[k] = v
dct['user'] = self.user.email.split('@')[0]
dct['id'] = self.key().id()
dct['created'] = self.created.strftime('%Y/%m/%d %H:%M:%S')
dct['updated'] = self.updated.strftime('%Y/%m/%d %H:%M:%S')
return dct


def define_model(name):
'''modelを生成する。

Example:
model = define_model('message')
entity = model(text='Hello World!')
entity.put()
'''
if name not in accepted_model:
raise NotAcceptedModelError('Model Name %s is not Accepted.' % name)
return type(str(name).capitalize(), (Expando,), {})

views.py
# -*- coding: utf-8 -*-
"""
myapp.views
"""

from kay.utils import render_to_response
from django.utils import simplejson
from kay.auth.decorators import login_required

from werkzeug import (
Response,
)

from models import define_model

# Create your views here.

@login_required
def index(request):
return render_to_response('myapp/index.html')


@login_required
def restful_api(request, model_name, id=None):
model = define_model(model_name)
try:
dct = simplejson.loads(request.data)
except:
dct = {}
method = request.method

if method == 'GET':
result = model.read(model, dct, id)
elif method == 'POST' or method == 'PUT':
result = model.create(model, dct, id)
elif method == 'DELETE':
result = model.destroy(model, dct, id)

return Response(mimetype='application/json', response=simplejson.dumps(result))
posted by 右京 | Python

kay frameworkのFormWidget.render()の挙動を変更する

例えばブログの記事を新規作成するとき、ModelFormを使うと以下のように書けると思います。
class Entry(db.Model):
title = db.StringProperty(required=True)
tags = db.StringListProperty()
text = db.TextProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)

class EntryForm(forms.ModelForm):
class Meta:
model = Entry

def create_entry(request):
form = EntryForm()
if request.method == 'POST':
if form.validate(request.form):
form.save()
return redirect('/')
#create.htmlで{{ form()|safe }}している
return render_to_response('app/entry/create.html', {'form': form.as_widget()})

自動で生成されるフォームをカスタマイズしたいとします。kayだとformをテンプレート側でカスタマイズできるんですが、同じようなフォームがたくさんあるときなどは、全部のテンプレートを修正しなきゃいけないので結構面倒です。そこで以下のようなコードを使うとテンプレートとかviews.pyとかはそのままでよくて、修正箇所がforms.py(いくつかフォームを定義しているモジュール)だけになるので楽できます。
class FieldRendererHook(object):
key = ''

@classmethod
def make_renderer(cls, widget):
return widget[cls.key].render


class TagFieldRendererHook(FieldRendererHook):
key = 'tags'

@classmethod
def make_renderer(cls, widget):
def render():
#...
return render


class CustomForm(ModelForm):
hooks = []

#override
def as_widget(self):
widget = super(CustomForm, self).as_widget()
for hook in self.hooks:
widget[hook.key].render = hook.make_renderer(widget)
return widget

class EntryForm(CustomForm):
hooks = (TagFieldRendererHook,)

class Meta:
model = Entry

具体的な例としてタグ入力欄をスタイリッシュなタグ入力欄のjQueryUIプラグイン Tag-it :右京webにかえるコードを書いておきます。
class TagFieldRendererHook(FieldRendererHook):
key = 'tags'

@classmethod
def make_renderer(cls, widget):
values = []

for value in widget[cls.key].value.replace('\r\n', '\n').split('\n'):
if value != '':
values.append(value)

def render():
field = '''
<ul id="tags-field"></ul>
<script>
$(function(){
$("#tags-field").tagit({
inputName: "%s",
defaultValues: %s
});
});
</script>
''' % (cls.key, simplejson.dumps(values))
return field
return render

追記:こういうメソッドを動的に書き換えてしまう行為をモンキーパッチというらしい。使うときはhook→patchにして。
posted by 右京 | Python

pythonでmimetypeを判定する

Pythonにはデフォルトでmimetypeを判定するmimetypesモジュールというのがある(というのをさっき知った)。

>>>import mimetypes

>>>mimetypes.guess_type('hoge.zip')
('application/zip', None)

>>>mimetypes.guess_type('hoge.png')
('image/png', None)

>>>mimetypes.guess_type('hoge.ts')
(None, None)

>>>mimetypes.guess_type('hoge.pdf')
('application/pdf', None)

>>>mimetypes.guess_type('hoge.ps')
('application/postscript', None)

>>>mimetypes.guess_type('hoge.c')
('text/plain', None)

>>>mimetypes.guess_type('hoge.py')
('text/x-python', None)

>>>mimetypes.guess_type('hoge.gif')
('image/gif', None)

なかなかやりおる。というか変なファイルじゃなければなんでもいけるね。
posted by 右京 | Python

kay frameworkでSNS - アップローダー

今回はアップローダーを実装しようと思う。他に比べるとけっこうハマりポイント多めかな。

ソース:
ukyo/kaysns at b4cd3c3d256fbdb1345950155a99dd0447c18c38 - GitHub

そういえば、実はGoogle App Engine 1.5.0になってからリクエストとレスポンスの制限が10MBから32MBに緩和されたらしい。さっき24MB位のファイルをアップロードしてみて成功したので多分あってる。ただし、一発で数パーセントほどQuota(帯域)を消費するので、あまり無理はできないかな。

app/urls.py
view_groups = [
ViewGroup(
#...
Rule('/uploader', endpoint='uploader', view='app.views.uploader'),
Rule('/uploader/download/<int:id>/<filename>', endpoint='uploader/download', view='app.views.uploader_download'),
Rule('/uploader/delete/check/<int:id>', endpoint='uploader/delete/check', view='app.views.uploader_check_delete'),
Rule('/uploader/delete/<int:id>', endpoint='uploader/delete', view='app.views.uploader_delete'),
)
]

Google App Engineでは一つのエンティティにつき1MBまでしか保存できない。保存するときは1MBずつ分割する必要がある(参考:GAE Datastoreの制限と回避方法 | Go for it!)。なのでファイル名やらを入れるFileとファイル本体の塊であるChunkというモデルをつくる。ただ、ファイルが1MB未満限定だったとしても、この方法で保存しておくと、データストアからファイル一覧を呼び出したときに軽くなるという利点もあったりする。

app/models.py
class File(db.Model, DateTimeMixin):
user = kay.db.OwnerProperty()
name = db.StringProperty()
comment = db.StringProperty()
tags = db.StringListProperty()
created = db.DateTimeProperty(auto_now_add=True)

class Chunk(db.Model):
file = db.ReferenceProperty(File, collection_name='chunks')
data = db.BlobProperty()
index = db.IntegerProperty()

今までみたいに直接保存できないので、ModelFormは使わず普通のkay.utils.formsを使う。あと、forms.Formを継承したときにdataというプロパティを作ると怒られる。

app/forms.py
from kay.utils import forms

#...

class FileForm(forms.Form):
file = forms.FileField('File', required=True)
comment = forms.TextField('Comment')
tags = forms.LineSeparated(forms.TextField(), label='Tags')

viewsにはけっこうなハマりポイントが存在する。特にuploader関数のあたり。ファイルをアップロードするにあたってファイル名は取得したくて、どうやってとってくるかというと、request.files['hoge'].filenameを見ればいいんだけど、ここにハマりポイントがある。validateした後に、request.fiels['hoge'].read()とかやってもデータが0byteとかになってしまう。よくよく考えてみるとvalidateしたときにファイル読み込むはずだから、form['hoge']とかに入ってるだろってわかるんだけど、1時間くらい悩んで放置してたら気づいた。

app/views.py
from app.models import (
MyUser, BbsThread, BbsComment, BlogEntry, Icon,
File, Chunk
)
from app.forms import (
UserForm, BbsThreadForm, BbsCommentForm, BlogEntryForm,
BlogCommentForm, IconForm, FileForm
)

#...

def uploader(request):
form = FileForm()
if request.method == 'POST':
if form.validate(request.form, request.files):
file = File(comment=form['comment'],
tags=form['tags'],
name=request.files['file'].filename)
file.put()
data = form['file']
chunk_size = 1000000
for i in xrange(int(len(data)/chunk_size)+1):
chunk = Chunk(file=file, data=data[i*chunk_size:(i+1)*chunk_size], index=i)
chunk.put()
return redirect(url_for('app/uploader'))

query = File.all().order('-created')
files = create_paginator_page(request, query)
return render_to_response('app/uploader/index.html', {'files': files,
'paginator': render_paginator(files),
'form': form.as_widget()})


def uploader_download(request, id, filename):
file = File.get_by_id(id)
data = ''.join(chunk.data for chunk in file.chunks.order('index'))
return Response(mimetype='', response=data)


def uploader_check_delete(request, id):
return render_to_response('app/uploader/delete.html', {'file': File.get_by_id(id)})


def uploader_delete(request, id):
file = File.get_by_id(id)
if request.user == file.user:
db.delete(file.chunks)
db.delete(file)
return redirect(url_for('app/uploader'))

あとはテンプレート。
app/templates/uploader/index.html
{% extends "app/base.html" %}
{% block title %}Uploader{% endblock %}
{% block content %}
<h1>Uploader</h1>
<div>
{{ form()|safe }}
</div>
<table>
<tr>
<th>Filename</th>
<th>Comment</th>
<th>Author</th>
<th>Tags</th>
<th>Created</th>
<th>Delete</th>
</tr>
{% for file in files.object_list %}
<tr>
<td><a href="/uploader/download/{{ file.key().id() }}/{{ file.name }}">{{ file.name }}</a<</td>
<td>{{ file.comment }}</td>
<td>{{ file.user }}</td>
<td>
{% for tag in file.tags %}
<a href="/tag/{{ tag }}">{{ tag }}</a>
{% endfor %}
</td>
<td>{{ file.view_datetime('created') }}</td>
<td>
{% if request.user == file.user or request.user.is_admin %}
<a href="/uploader/delete/check/{{ file.key().id() }}">Delete</a>
{% else %}
-
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{{ paginator|safe }}
{% endblock %}

app/templates/uploader/delete.html
{% extends "app/base.html" %}
{% block title %}Uploader/Delete{% endblock %}
{% block content %}
<h1>Uploader/Delete</h1>
<div>
Do you delete the {{ file.name }}?
<ul>
<li><a href="/uploader/delete/{{ file.key().id() }}">Yes</a>
<li><a href="/uploader">No</a>
</ul>
</div>
{% endblock %}
posted by 右京 | Python

kay frameworkでSNS - アイコンの設定と表示

今回はアイコンの設定と表示だけ。PILは入れてくださいね。

ソース:
ukyo/kaysns at 109c541ee6e2761ad73c6c71c824b4045f770163 - GitHub

アイコンの表示にはちょっと工夫がしてあって、横48px、縦48pxの画像を表示させたい場合は/your-appid/username/icon_48x48.pngみたいにとれるようにしてみた。
こうしておけばサイズごとにmodelにサムネイルを作らなくても済む。ただし、その都度画像を生成するとなると表示速度とか負荷的な問題が出るので最適化(memcacheに入れておくとか)が必要だと思う。今回は特に負荷対策は考えずに実装しておく。

注意点として、もしも設定したアイコンがなければデフォルトのアイコンを表示することになってるんだけど、特に何も設定しないとkayディレクトリ内に置かなければ読み込めない。import sysしてパスを追加すれば解決するとは思います、とだけ書いとく。

app/url.py
view_groups = [
ViewGroup(
#...
Rule('/setting/icon', endpoint='setting/icon', view='app.views.setting_icon'),
Rule('/<user_name>/icon_<int:width>x<int:height>.png', endpoint='icon', view='app.views.icon'),
)
]

app/models.py
#...

class Icon(db.Model):
user = kay.db.OwnerProperty()
image = db.BlobProperty(required=True)

app/forms.py
from app.models import (
MyUser, BbsThread, BbsComment,
BlogEntry, BlogComment, Icon
)

#...

class IconForm(ModelForm):
class Meta:
model = Icon
exclude = ('user', )

app/views.py
from google.appengine.api import images

from app.models import (
MyUser, BbsThread, BbsComment, BlogEntry, Icon
)
from app.forms import (
UserForm, BbsThreadForm, BbsCommentForm, BlogEntryForm,
BlogCommentForm, IconForm
)

#...

def setting_icon(request):
form = IconForm()
if request.method == 'POST':
if form.validate(request.form, request.files):
form.save()
return redirect(url_for('app/index'))
return render_to_response('app/setting/icon.html', {'form': form.as_widget()})


def icon(request, user_name, width, height):
user = MyUser.all().filter('user_name', user_name).get()
try:
icon = Icon.all().filter('user', user).get().image
except:
icon = open('default_user_icon.png').read()
return Response(mimetype='image/png', response=images.resize(icon, width, height))

app/templates/setting/icon.html
{% extends "app/base.html" %}
{% block title %}Setting/Icon{% endblock %}
{% block content %}
<h1>Setting/Icon</h1>
<div>
<img src="/{{ request.user }}/icon_48x48.png"/>
</div>
<div>
{{ form()|safe }}
</div>
{% endblock %}

特に難しいことはないね。http://localhost:8080/setting/iconで確認しておしまい。
posted by 右京 | Python

pyahooapis0.2.3リリース

UTF-8以外の文字コードでの入出力をできるようにしました。
Serviceのインスタンス作成時にencodingに'sjis'とか入れればShift_JISで入出力できます。

Python Package Index : pyahooapis 0.2.3
posted by 右京 | Python

kay frameworkでSNS - Blog機能

ここまでのソース:
ukyo/kaysns at a18d6c9b1ac524fd10ccd1802235160efb613d07 - GitHub

今回はブログ機能。先に全体像だけ書いとこう。

app/views.py
from google.appengine.ext import db

#...

from app.models import (
MyUser, BbsThread, BbsComment, BlogEntry
)
from app.forms import (
UserForm, BbsThreadForm, BbsCommentForm, BlogEntryForm,
BlogCommentForm,
)

#...

def blog(request, user_name):
#blogのトップページ

def blog_entry(request, user_name, id):
#blogの記事ページ

def blog_manage(request):
#blog管理画面

def blog_create_entry_base(request, form, template):
#blog記事作成の元の関数

def blog_create_entry(request):
#blog記事新規作成

def blog_update_entry(request, id):
#blog記事更新

def blog_check_delete_entry(request, id):
#blog記事削除確認画面

def blog_delete_entry(request, id):
#blog記事削除


app/urls.py
#...

view_groups = [
ViewGroup(
#...以下を追加
Rule('/blog/manage', endpoint='blog/manage', view='app.views.blog_manage'),
Rule('/blog/create', endpoint='blog/create', view='app.views.blog_create_entry'),
Rule('/blog/update/<int:id>', endpoint='blog/update', view='app.views.blog_update_entry'),
Rule('/blog/delete/check/<int:id>', endpoint='blog/delete/check', view='app.views.blog_check_delete_entry'),
Rule('/blog/delete/<int:id>', endpoint='blog/delete', view='app.views.blog_delete_entry'),
Rule('/<user_name>/blog', endpoint='blog/index', view='app.views.blog'),
Rule('/<user_name>/blog/<int:id>', endpoint='blog/entry', view='app.views.blog_entry'),
)
]

・モデルとフォームの作成。

app/models.py
class BlogEntry(Thread):
pass

class BlogComment(Comment):
entry = db.ReferenceProperty(BlogEntry, collection_name='comments')

app/forms.py
class BlogEntryForm(ModelForm):
class Meta:
model = BlogEntry
exclude = ('user', )


class BlogCommentForm(ModelForm):
class Meta:
model = BlogComment
exclude = ('user', 'entry')

・ブログ記事新規作成機能とその画面の作成。

記事新規作成と記事更新の処理はほぼ同じなので、
blog_create_entry_base関数という共通で使う関数を用意する。

app/views.py
def blog_create_entry_base(request, form, template):
if request.method == 'POST':
if form.validate(request.form):
form.save()
return redirect(url_for('app/blog/manage'))
return render_to_response(template, {'form': form.as_widget()})


def blog_create_entry(request):
return blog_create_entry_base(request, BlogEntryForm(), 'app/blog/create.html')

app/templates/blog/create.html
{% extends "app/base.html" %}
{% block title %}Create a Blog Entry - app{% endblock %}
{% block content %}
<h1>Create a Blog Entry</h1>
<div>
{{ form()|safe }}
</div>
{% endblock %}

http://localhost:8080/blog/createで確認。

・ブログ記事管理画面の作成。

app/views.py
def blog_manage(request):
query = BlogEntry.all().filter('user', request.user).order('-created')
entries = create_paginator_page(request, query)
return render_to_response('app/blog/manage.html', {'entries': entries,
'paginator': render_paginator(entries)})

app/templates/blog/manage.py
{% extends "app/base.html" %}
{% block title %}Manage Blog Entries - app{% endblock %}
{% block content %}
<h1>Manage Blog Entries</h1>
<div>
<p>List of Entries</p>
<div>
{% for entry in entries.object_list %}
<ul>
<li><a href="/{{ request.user.user_name }}/blog/{{ entry.key().id() }}">{{ entry.title }}</a>
<li><a href="/blog/update/{{ entry.key().id() }}">Update</a>
<li><a href="/blog/delete/check/{{ entry.key().id() }}">Delete</a>
</ul>
{% endfor %}
</div>
{{ paginator|safe }}
</div>
{% endblock %}

ブログ記事新規作成画面から記事を作成した後にhttp://localhost:8080/blog/manageで確認。

・ブログ記事更新機能とその画面の作成。

app/views.py
def blog_update_entry(request, id):
return blog_create_entry_base(request, BlogEntryForm(BlogEntry.get_by_id(id)), 'app/blog/update.html')

app/templates/blog/update.html
{% extends "app/base.html" %}
{% block title %}Update a Blog Entry - app{% endblock %}
{% block content %}
<h1>Update a Blog Entry</h1>
<div>
{{ form()|safe }}
</div>
{% endblock %}

http://localhost:8080/blog/manageのUpdateから確認。

・ブログ記事削除確認画面とブログ記事削除機能の作成。

app/views.py
def blog_check_delete_entry(request, id):
return render_to_response('app/blog/delete.html', {'entry': BlogEntry.get_by_id(id)})


def blog_delete_entry(request, id):
entry = BlogEntry.get_by_id(id)
db.delete(entry.comments)
db.delete(entry)
return redirect(url_for('app/blog/manage'))

app/templates/delete.html
{% extends "app/base.html" %}
{% block title %}Delete a Blog Entry - app{% endblock %}
{% block content %}
<h1>Delete a Blog Entry</h1>
<div id="check-delete">
<dl>
<dt>Title
<dd>{{ entry.title }}
<dt>Text
<dd>{{ entry.text|safe }}
</dl>
</div>
<div>
<p>Do you delete this Entry ?</p>
<ul>
<li><a href="/blog/delete/{{ entry.key().id() }}">Yes</a>
<li><a href="/blog/manage">No</a>
</ul>
</div>
{% endblock %}

http://localhost:8080/blog/manageのUpdateから確認。

・ブログ記事一覧画面の作成

app/views.py
def blog(request, user_name):
user = MyUser.all().filter('user_name', user_name).get()
query = BlogEntry.all().filter('user', user).order('-created')
entries = create_paginator_page(request, query)
return render_to_response('app/blog/index.html', {'user_name': user_name,
'entries': entries,
'paginator': render_paginator(entries)})

app/templates/blog/index.html
{% extends "app/base.html" %}
{% block title %}{{ user_name }} - Blog Page - app{% endblock %}
{% block content %}
<h1><a href="/{{ user_name }}/blog">{{ user_name }} - Blog Page</a></h1>
<div id="blog">
{% for entry in entries.object_list %}
<div class="blog-entry">
<div class="blog-title">
<a href="/{{ user_name }}/blog/{{ entry.key().id() }}">{{ entry.title }}</a>
</div>
<div class="blog-created">{{ entry.created }}</div>
<div class="blog-text">{{ entry.view_text()|safe }}</div>
<div class="blog-tags">
Tags:
{% for tag in entry.tags %}
<a href="/tag/{{ tag }}">{{ tag }}</a>
{% endfor %}
</div>
<div class="blog-comment">
<a href="/{{ user_name }}/blog/{{ entry.key().id() }}#comment">Comment({{ entry.comments.count() }})</a>
</div>
</div>
{% endfor %}
{{ paginator|safe }}
</div>
{% endblock %}

http://localhost:8080/your-user-name/blogから確認。your-user-nameには設定したユーザ名を入れる。

・ブログ記事画面の作成。

app/views.py
def blog_entry(request, user_name, id):
form = BlogCommentForm()
entry = BlogEntry.get_by_id(id)
if request.method == 'POST':
if form.validate(request.form):
form.save(entry=entry)
return redirect('/%s/blog/%d' % (user_name, id))
return render_to_response('app/blog/entry.html', {'user_name': user_name,
'entry': entry,
'form': form.as_widget()})

app/templates/blog/entry.html
{% extends "app/base.html" %}
{% block title %}{{ entry.title }} - {{ user_name }} - Blog Page - app{% endblock %}
{% block content %}
<h1><a href="/{{ user_name }}/blog">{{ user_name }} - Blog Page</a></h1>
<div class="blog-title">{{ entry.title }}</div>
<div class="blog-created">{{ entry.created }}</div>
<div class="blog-text">{{ entry.view_text()|safe }}</div>
<div class="blog-tags">
Tags:
{% for tag in entry.tags %}
<a href="/tag/{{ tag }}">{{ tag }}</a>
{% endfor %}
</div>
<div class="blog-comment">
<a href="/{{ user_name }}/blog/{{ entry.key().id() }}#comment">Comment({{ entry.comments.count() }})</a>
</div>
<a name="comment"></a>
<div id="blog-entry-comments">
{% for comment in entry.comments %}
<div class="blog-entry-comment">
<div class="blog-entry-comment-title">{{ comment.title }}</div>
<div class="blog-entry-comment-created">{{ comment.created }}</div>
<div class="blog-entry-comment-text">{{ comment.view_text()|safe }}</div>
<div class="blog-entry-comment-author">by {{ comment.user }}</div>
</div>
{% endfor %}
</div>
<div>
{{ form()|safe }}
</div>
{% endblock %}

http://localhost:8080/your-user-name/blogの各記事のタイトルがリンクになっているのでそれをクリックして確認。あと、コメントも入力できる。

これでブログ機能は(ほぼ)完成。あと、app/templates/base.htmlをちょっと書きなおしたので書いとく。

app/templates/base.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

{% from "auth/macros.html" import render_loginbox with context %}

<html>
<head>
{% block head %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
{% if request.user.is_anonymous() %}
{{ render_loginbox() }}
{% else %}
<div id="menu">
{% block menu %}
<ul>
<li><a href="/">My Page</a>
<li><a href="/bbs">BBS</a>
<li>Blog
<ul>
<li><a href="/{{ request.user }}/blog">My Blog</a>
<li><a href="/blog/manage">Manage Entries</a>
<li><a href="/blog/create">Create a Entry</a>
</ul>
<li><a href="{{ create_logout_url() }}">logout</a>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}

{% endblock %}
</div>
{% endif %}
</body>
</html>
posted by 右京 | Python

kay frameworkでSNS - jinia2のextends機能

jinja2ではextendsとかいう便利な機能があったのでテンプレートを書きなおしてみる。まずは、ベースとなるテンプレートを作成する。

app/base.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

{% from "auth/macros.html" import render_loginbox with context %}

<html>
<head>
{% block head %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
{% if request.user.is_anonymous() %}
{{ render_loginbox() }}
{% else %}
<div id="menu">
{% block menu %}
<ul>
<li><a href="/">My Page</a>
<li><a href="/bbs">BBS</a>
<li><a href="/blog/manage">Blog</a>
<ul>
<li><a href="/blog/manage">Manage Entries</a>
<li><a href="/blog/create">Create a Entry</a>
</ul>
<li><a href="{{ create_logout_url() }}">logout</a>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}

{% endblock %}
</div>
{% endif %}
</body>
</html>

次に、これを継承するように今まで作ってきたテンプレートを修正する。

app/index.html
{% extends "app/base.html" %}
{% block title %}Top Page - app{% endblock %}
{% block content %}Hello {{ request.user }}!{% endblock %}


app/manage-profile.html
{% extends "app/base.html" %}
{% block title %}Profile Page - app{% endblock %}
{% block content %}
<div>
{{ form()|safe }}
</div>
{% if validate %}
<div>
{{ validate }}
</div>
{% endif %}
{% endblock %}

app/bbs/index.html
{% extends "app/base.html" %}
{% block title %}BBS Page - app{% endblock %}
{% block content %}
<div>
{{ form()|safe }}
</div>
<table>
<tr><th>Auther</th><th>Title</th><th>Updated</th><th>Created</th></tr>
{% for thread in threads %}
<tr>
<td>{{ thread.user.user_name }}</td>
<td><a href="/bbs/{{ thread.key().id() }}">{{ thread.title }}</a></td>
<td>{{ thread.updated }}</td>
<td>{{ thread.created }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}

app/bbs/thread.html
{% extends "app/base.html" %}
{% block title %}{{ thread.title }} - BBS Thread Page - app{% endblock %}
{% block content %}
<h1>{{ thread.title }}</h1>
<div>
<dl class="info">
<dt>Auther</dt><dd>{{ thread.user.user_name }}</dd>
<dt>Created</dt><dd>{{ thread.created }}</dd>
</dl>
<div class="text">{{ thread.view_text()|safe }}</div>
</div>
{% for comment in thread.comments %}
<div>
<dl class="info">
<dt>Auther</dt><dd>{{ comment.user.user_name }}</dd>
<dt>Created</dt><dd>{{ comment.created }}</dd>
</dl>
<div class="text">{{ comment.view_text()|safe }}</div>
{% endfor %}
<div>
{{ form()|safe }}
</div>
{% endblock %}

ちょっとすっきりしたかな。
posted by 右京 | Python

kay frameworkでSNS - 掲示板を作る

今回は掲示板を実装しよう。一応ブログ機能を作成するときに応用できるように実装していく。

ソース:
ukyo/kaysns at bb32e10530492b26e5a2e44c3aa70292225401ee - GitHub

最初にスレッド投稿フォーム、スレッド一覧ページを作成する。

まずapp/models.pyを編集する。kay.dbをインポートしてThreadクラスとBbsThreadクラスを追加する。kay.db.OwnerPropertyはログインしているユーザのkeyを自動で格納するためのプロパティ(とkayチュートリアルに書いてある)。Threadクラスはブログ機能作成するときに流用する。
import kay.db

#...(中略)...

class Thread(db.Model):
user = kay.db.OwnerProperty()
title = db.StringProperty(required=True)
tags = db.StringListProperty()
text = db.TextProperty(required=True)
newline_to_br = db.BooleanProperty()
created = db.DateTimeProperty(auto_now_add=True)

class BbsThread(Thread):
updated = db.DateTimeProperty(auto_now=True)

次にapp/forms.pyを編集する。BbsThreadFormクラスを作成する。BbsThreadクラスのインポートを忘れないように。
from kay.utils.forms.modelform import ModelForm
from app.models import (
MyUser, BbsThread
)
class UserForm(ModelForm):
class Meta:
model = MyUser
exclude = ('is_admin', 'activated', 'user_name', 'password')

class BbsThreadForm(ModelForm):
class Meta:
model = BbsThread
exclude = ('user', )

次にapp/templates/bbs/index.htmlを作成する。フォームがあって、その下にスレッド一覧を表示する感じ。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>BBS Page - app</title>
</head>
<body>
<div><a href="/">TOP</a></div>
<div>
{{ form()|safe }}
</div>
<ul>
<table>
<tr><th>Auther</th><th>Title</th><th>Updated</th><th>Created</th></tr>
{% for thread in threads %}
<tr>
<td>{{ thread.user.user_name }}</td>
<td><a href="/bbs/{{ thread.key().id() }}">{{ thread.title }}</a></td>
<td>{{ thread.updated }}</td>
<td>{{ thread.created }}</td>
</tr>
{% endfor %}
</table>
</ul>
</body>
</html>

次にapp/urls.pyを編集する。view_groupsに新しいRuleを追加する。
view_groups = [
ViewGroup(
Rule('/', endpoint='index', view='app.views.index'),
Rule('/manage-profile', endpoint='manage-profile', view='app.views.manage_profile'),
Rule('/bbs', endpoint='bbs/index', view='app.views.bbs'),#これ
)
]

次にapp/views.pyを編集する。bbs関数を作成する。url_for, BbsThread, BbsThreadFormのインポートを忘れないように。
from werkzeug import redirect

from kay.utils import (
render_to_response, url_for
)
from app.models import (
MyUser, BbsThread,
)
from app.forms import (
UserForm, BbsThreadForm,
)
from kay.auth.decorators import login_required

#...(中略)...

@login_required
def bbs(request):
form = BbsThreadForm()
threads = BbsThread.all().order('-created')
if request.method == 'POST':
if form.validate(request.form):
form.save()
return redirect(url_for('app/bbs/index'))
return render_to_response('app/bbs/index.html', {'form': form.as_widget(),
'threads': threads})

そうしたらhttp://localhost:8080/bbsを表示して確認してみる。

こんな感じになったかな(投稿したあと)。
CropperCapture[33].png
まぁ、投稿先のリンクをクリックしてもエラーになるので、次はスレッド単体のページを作成する。

app/models.pyを編集する。CommentクラスとBbsCommentクラスを追加する。Commentクラスはやっぱりブログ機能作成するときに流用する。
class Comment(db.Model):
user = kay.db.OwnerProperty()
title = db.StringProperty(required=True)
text = db.TextProperty(required=True)
newline_to_br = db.BooleanProperty()
created = db.DateTimeProperty(auto_now_add=True)

class BbsComment(Comment):
thread = db.ReferenceProperty(BbsThread, collection_name='comments')

app/forms.pyを編集する。BbsCommentFormを追加する。BbsCommentをインポートするのを忘れないように。
from kay.utils.forms.modelform import ModelForm
from app.models import (
MyUser, BbsThread, BbsComment,
)

#...(中略)...

class BbsCommentForm(ModelForm):
class Meta:
model = BbsComment
exclude = ('user', 'thread')

app/templates/bbs/thread.htmlを作成する。スレ主のコメントがあって、他の人のコメントがあって、その下にコメントフォームがある感じ。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>{{ thread.title }} - BBS Thread Page - app</title>
</head>
<body>
<div><a href="/">TOP</a> | <a href="/bbs">BBS</a></div>
<h1>{{ thread.title }}</h1>
<div>
<dl class="info">
<dt>Auther</dt><dd>{{ thread.user.user_name }}</dd>
<dt>Created</dt><dd>{{ thread.created }}</dd>
</dl>
<div class="text">{{ thread.text|safe }}</div>
</div>
{% for comment in thread.comments %}
<div>
<dl class="info">
<dt>Auther</dt><dd>{{ comment.user.user_name }}</dd>
<dt>Created</dt><dd>{{ comment.created }}</dd>
</dl>
<div class="text">{{ comment.text|safe }}</div>
{% endfor %}
<div>
{{ form()|safe }}
</div>
</body>
</html>

app/urls.pyを編集する。view_groupsに新しいRuleを追加する。
view_groups = [
ViewGroup(
Rule('/', endpoint='index', view='app.views.index'),
Rule('/manage-profile', endpoint='manage-profile', view='app.views.manage_profile'),
Rule('/bbs', endpoint='bbs/index', view='app.views.bbs'),
Rule('/bbs/<int:id>', endpoint='bbs/thread', view='app.views.bbs_thread'),#これ
)
]

app/views.pyを編集する。BbsComment, BbsCommentFormをインポートするのを忘れないように。
from werkzeug import redirect

from kay.utils import (
render_to_response, url_for
)
from app.models import (
MyUser, BbsThread, BbsComment
)
from app.forms import (
UserForm, BbsThreadForm, BbsCommentForm
)
from kay.auth.decorators import login_required

#...(中略)...

@login_required
def bbs_thread(request, id):
form = BbsCommentForm()
thread = BbsThread.get_by_id(id)
if request.method == 'POST':
if form.validate(request.form):
form.save(thread=thread)
thread.put()
return redirect('/bbs/%d' % id)
return render_to_response('app/bbs/thread.html', {'form': form.as_widget(),
'thread': thread})

これでとりあえずスレッドの表示と投稿ができるようになった。
CropperCapture[34].png

ただ、これだと改行しても反映されないので、ちゃんと改行するようにしてみる。そのためにnewline_to_brとか用意したわけだし。

app/models.pyを編集する。Threadクラスの上の辺りにBrMixinクラスを追加し、ThreadクラスとCommentクラスはそれを継承するようにする。BrMixinクラスは改行指定したときは改行するようにするview_text関数を提供する。
class BrMixin(object):
def view_text(self):
if self.newline_to_br:
return self.text.replace('\r\n', '\n').replace('\n', '<br/>')
else:
return self.text

class Thread(db.Model, BrMixin):
#...

class Comment(db.Model, BrMixin):
#...

app/templates/bbs/thread.htmlを編集する。thread.text→thread.view_text(), comment.text→comment.view_text()に置換する。
<div>
<dl class="info">
<dt>Auther</dt><dd>{{ thread.user.user_name }}</dd>
<dt>Created</dt><dd>{{ thread.created }}</dd>
</dl>
<div class="text">{{ thread.view_text()|safe }}</div>
</div>
{% for comment in thread.comments %}
<div>
<dl class="info">
<dt>Auther</dt><dd>{{ comment.user.user_name }}</dd>
<dt>Created</dt><dd>{{ comment.created }}</dd>
</dl>
<div class="text">{{ comment.view_text()|safe }}</div>
{% endfor %}
<div>

そうしたらめでたく改行表示できたでしょ(app/view.pyを編集する必要がないから楽だよね)。
CropperCapture[35].png

今日はここまで。
posted by 右京 | Python

kay frameworkでSNS - とりあえずログイン、ユーザ設定

Kay frameworkを使用してSNSを作ってみる。今日はとりあえずログイン回りとユーザ設定の実装をしようかと。

ソース:
ukyo/kaysns at - GitHub

※Kay frameworkとは?
Kay 1.1.0 ドキュメントへようこそ − Kay v1.1.0 ドキュメント

まずはプロジェクトの作成。
$ python manage.py startproject kaysns
$ cd kaysns

アプリケーションの作成。
$ python manage.py startapp app

動かす。
$ python manage.py runserver

setting.pyの編集(参考:11. 認証の設定 − Kay v1.1.0 ドキュメント)。
# -*- coding: utf-8 -*-

"""
A sample of kay settings.

:Copyright: (c) 2009 Accense Technology, Inc.
Takashi Matsuo ,
All rights reserved.
:license: BSD, see LICENSE for more details.
"""

DEFAULT_TIMEZONE = 'Asia/Tokyo'
DEBUG = True
PROFILE = False
SECRET_KEY = 'ReplaceItWithSecretString'
SESSION_PREFIX = 'gaesess:'
COOKIE_AGE = 1209600 # 2 weeks
COOKIE_NAME = 'KAY_SESSION'

ADD_APP_PREFIX_TO_KIND = True

ADMINS = (
)

TEMPLATE_DIRS = (
)

USE_I18N = False
DEFAULT_LANG = 'en'

INSTALLED_APPS = (
'kay.auth',
'app',
)

APP_MOUNT_POINTS = {
'app': '/',
}

MIDDLEWARE_CLASSES = (
'kay.sessions.middleware.SessionMiddleware',
'kay.auth.middleware.AuthenticationMiddleware',
)
AUTH_USER_BACKEND = 'kay.auth.backends.datastore.DatastoreBackend'
AUTH_USER_MODEL = 'app.models.MyUser'

# You can remove following settings if unnecessary.
CONTEXT_PROCESSORS = (
'kay.context_processors.request',
'kay.context_processors.url_functions',
'kay.context_processors.media_url',
'kay.auth.context_processors.login_box',
)

app/models.pyの編集。kay.auth.models.DatastoreUserを拡張したMyUserってのを作ってみる。ここではtags、descriptionというプロパティを追加。
# -*- coding: utf-8 -*-
# app.models

from google.appengine.ext import db
from kay.auth.models import DatastoreUser

# Create your models here.

class MyUser(DatastoreUser):
tags = db.StringListProperty()
description = db.TextProperty()

app/views.pyとapp/templates/index.htmlの編集。ログインボックスを出して、ログインしたらhello user!する。
# -*- coding: utf-8 -*-
"""
app.views
"""

from kay.utils import render_to_response

# Create your views here.

def index(request):
return render_to_response('app/index.html', {'request': request})

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - app</title>
</head>
<body>
{% from "auth/macros.html" import render_loginbox with context %}

{% if request.user.is_anonymous() %}
{{ render_loginbox() }}
{% else %}
Hello {{ request.user }}! <a href="{{ create_logout_url() }}">logout</a>
{% endif %}
</body>
</html>

ユーザを作成する。なんかハッカーぽくね?
$ manage.py create_user -h localhost:8080 --no-secu
re ukyo
Running on Kay-1.1.1
Please input a password for new user:
Username:ukyo
Password:
A new user: ukyo successfully created.

アクセスしてみる。http://localhost:8080
CropperCapture[30].png

ユーザ設定できるようにする。app/forms.pyを作成する。kay.utils.forms.modelform.ModelFormを使うとモデルから自動的にフォームを作ってくれるので便利。excludeはフォームに表示しないやつを列挙したもの。
# -*- coding: utf-8 -*-

from kay.utils.forms.modelform import ModelForm
from app.models import MyUser

class UserForm(ModelForm):
class Meta:
model = MyUser
exclude = ('is_admin', 'activated', 'user_name', 'password')

app/urls.pyにユーザ設定するページのurlを設定する。ここでは/manage-profileとかにしておく。
view_groups = [
ViewGroup(
Rule('/', endpoint='index', view='app.views.index'),
Rule('/manage-profile', endpoint='manage-profile', view='app.views.manage_profile'),
)
]

app/views.pyにmanage_profile関数を追加する。※import文より下を表示。
from kay.utils import render_to_response
from app.models import MyUser
from app.forms import UserForm
from kay.auth.decorators import login_required

# Create your views here.

def index(request):
return render_to_response('app/index.html', {'request': request})

@login_required
def manage_profile(request):
form = UserForm(request.user)
data = {}
if request.method == 'POST':
if form.validate(request.form):
form.save()
data['validate'] = u'成功しました。'
else:
data['validate'] = u'失敗しました。'
data['form'] = form.as_widget()
return render_to_response('app/manage-profile.html', data)

app/templates/manage-profile.htmlを作成。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - app</title>
</head>
<body>
<div><a href="/">TOP</a></div>
<div>
{{ form()|safe }}
</div>
{% if validate %}
<div>
{{ validate }}
</div>
{% endif %}
</body>
</html>

トップページからアクセスできるようにする。app/templates/index.htmlのbodyの中身を以下のように変更。
{% from "auth/macros.html" import render_loginbox with context %}

{% if request.user.is_anonymous() %}
{{ render_loginbox() }}
{% else %}
Hello {{ request.user }}! <a href="{{ create_logout_url() }}">logout</a>
<div><a href="/manage-profile">manage profile</a></div>
{% endif %}

トップページを更新してみる。
CropperCapture[31].png

/manage-profileにアクセスしていろいろ入力してみる。
CropperCapture[32].png

送信したらどんなデータが入っているか確認してみる。http://localhost:8080/_ah/admin

できたかな。今日はここまで。
posted by 右京 | Python

pyahooapisのドキュメント作成中です

Welcome to Pyahooapis’s documentation! − Pyahooapis v0.2.1 documentation

このドキュメントはsphinxによって作成されてます。これはとても素晴らしいツールです。Dropboxと合わせて使うととても幸せになれます。是非使ってみましょう。

とか、ドキュメント風な口調で書いてみたり。
posted by 右京 | Python
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。