Google app engine用汎用ビュー date_based

April 15, 2008 at 10:49 PM | View Comments

まだ作りかけですが、Google app engine用の汎用ビューdate_basedを作ってみました。

コードはCodeReposのコミット権をもらったので、そちらにあげておきました。

http://coderepos.org/share/browser/lang/python/gae_django/views/generic/date_based.py

template_nameを必ず渡す必要があるのと、object_detailができてません。

また、Google app engineのTIMEZONEはUTCなのですが、そのへんを考慮してないので日付の指定がズレてしまいます。そのうちsettings.pyのTIME_ZONEを見てその辺の修正をかけるようにする予定です。

テンプレートに表示するときはtemplatetagsで時刻を修正する予定です。

http://coderepos.org/share/browser/lang/python/gae_django/templatetags/datefilter.py

pytzが必要です。

categories: django_randomnote, python, googleappengine
Read and Post Comments

Django再入門 RandomNoteを作る vol.2 汎用ビュー(Genericview)

March 07, 2007 at 07:45 AM | View Comments

管理画面で本文を追加したり、消したりできるようになったので 今度は管理画面以外でもできるようにしましょう。 今日のポイントは汎用ビューを使うことです。 Railsのscalfoldが足場に対して汎用ビュー(GenericView)は完成品です。 Djangoの特徴的な機能なので是非使いこなせるようになりましょう。 (どちらが良いというわけではありません)

まずmyproject/urls.pyを

'myproject/urls.py'

from django.conf.urls.defaults import *

urlpatterns = patterns('',
     (r'^randomnote/', include('randomnote.urls')),
     (r'^admin/', include('django.contrib.admin.urls')),
)

と編集しましょう。 これでlocalhost:8000/randomnote/~~ とアクセスされたものはmyproject/randomnote/urls.pyを呼び出して処理するようにします。 プロジェクト直下のurls.pyですべてのアプリをさばいてもいいのですが、 通常はアプリごとにurls.pyを持つようにした方がいいようです。 adminもdjango.contrib.admin.urls.pyを呼び出して処理しています。

ではmyproject/randomnote/urls.pyは自分で作らないといけないので作りましょう。

通常はurls.pyでurlを正規表現で判別して、veiws.pyに書いた関数へ引数を渡すのですが、 Djangoには汎用ビュー(generic.view)というよくある処理を簡単にかける仕組みがあります。 汎用ビューは便利なので積極的に使いましょう。

'randomnote/urls.py'

from django.conf.urls.defaults import *
from randomnote.models import Page

info_dict = {
'queryset':Page.objects.all(),
}

urlpatterns = patterns('django.views.generic.list_detail',
(r'^$', 'object_list', info_dict),
(r'^detail/(?P<object_id>\d+)/$', 'object_detail', info_dict),
)

urlpatterns += patterns('django.views.generic.create_update',
(r'^create/$', 'create_object',
        {'model':Page, 'post_save_redirect':'/randomnote/'} ),
(r'^update/(?P<object_id>\d+)/$','update_object',
        {'model':Page}),
(r'^delete/(?P<object_id>\d+)/$','delete_object',
        {'model':Page, 'post_delete_redirect':'/randomnote/'} ),
)

Djangoは正規表現でurlを解釈してそれぞれの処理に渡します。 例えば localhost:8000/randomnote/detail/1 とブラウザで開いた場合 (r'^detail/(?P<object_id>d+)/$', 'object_detail', info_dict), にヒットするので django.views.generic.list_detail.object_detailという処理にinfo_dict(queryset=Page.objects.all())とobject_id=1が引数として渡されます。 何を引数に渡せるかはドキュメントに詳しく書かれてます。

汎用ビュー(django.views.generic)はいろいろな種類があって 今回は

処理内容: 使用するgeneric.view : 適用されるテンプレートファイル 一覧:django.views.generic.list_detail.object_list : アプリ名/モデル名_list.html 詳細: django.views.generic.list_detail.object_detail : アプリ名/モデル名_detail.html 新規入力: django.views.generic.create_update.create_object : アプリ名/モデル_form.html 追加入力: django.views.generic.create_update.update_object : アプリ名/モデル_form.html 消去: django.views.generic.create_update.delete_object : アプリ名/モデル名_confirm_delete.html

を使ってます。(他には日付ごとにアーカイブを分けてくれるものがあります)

汎用ビューを使う場合は views.pyに書かなくて良いので、urls.pyで正しい引数を渡して、対応するテンプレートファイルを作ればOKです。

ではテンプレートファイルを作っていきましょう。 settings.pyで指定したtemplatesの直下に作っていきます。

Djangoはテンプレートを継承できるので baseとなるテンプレートから

'myproject/templates/randomnote/base.html'

<html>
<h1>RandomNote</h1>
<a href="/randomnote/create/">Create</a><br/>
{% block content %}
    edit content
{% endblock %}
</html>

Djangoのテンプレートは継承するタイプです。 {% block content %} ここの部分を継承したテンプレートに任せる {% endblock %} ことになります。 継承するテンプレートは {% extends "randomnote/base.html" %} {% block content %} ここのそのテンプレート独自の処理を書く {% endblock %} を書くことになります。

ではgenericviewに対応するテンプレートファイルを書いていきましょう

まず、 一覧の汎用ビュー(list_detail.object_list)に対応するテンプレートです。 アプリ名/モデル名_list.htmlがデフォルトなので randomnote/page_list.htmlになります

object_listがモデルの一覧の配列みたいなものなので forを使って一つ一つ取り出して、それぞれの値(idとbody)を出力しています。

'myproject/templates/randomnote/page_list.html'

{% extends "randomnote/base.html" %}
{% block content %}
    {% for page in object_list %}
      <div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
        <div style="font-size: 80%; background-color: #fcc">
          <a href="/randomnote/detail/{{ page.id }}">Show</a>
          <a href="/randomnote/update/{{ page.id }}">Edit</a>
          <a href="/randomnote/delete/{{ page.id }}">Destroy</a>
        </div>
        {{ page.body }}
      </div>
    {% endfor %}
{% endblock %}

次に詳細の汎用ビュー(list_detail.object_detail) アプリ名/モデル名_detail.htmlなので randomnote/page_detail.htmlを作ります。 ここでテンプレートが受け取るのは object という特定のインスタンス urls.pyは (r'^detail/(?P<object_id>d+)/$', 'object_detail', {'queryset':Page}), なので http://localhost:8000/randomnote/detail/1/ というアクセスがあった場合、id=1, queryset=Page が汎用ビューに渡され 汎用ビューは Pageのidが1のインスタンスを object という名前でテンプレートに渡します。

object(=Pageのインスタンス)はidとbodyを持っているのでそれをテンプレートで表示させます。

'myproject/templates/randomnote/page_detail.html'

{% extends "randomnote/base.html" %}
{% block content %}
      <div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
        <div style="font-size: 80%; background-color: #fcc">
          <a href="/randomnote/">List</a>
          <a href="/randomnote/update/{{ object.id }}">Edit</a>
          <a href="/randomnote/delete/{{ object.id }}">Destroy</a>
        </div>
        {{ object.body }}
      </div>
{% endblock %}

つぎにcreateとupdateを扱う汎用ビュー(create_update.create_object とcreate_update.update_object )です。 この二つの汎用ビューは アプリ名/モデル名_form.htmlというひとつのテンプレートを扱います。

createは model=Page、post_save_redirect=/randomnote/ という引数を渡します

(r'^create/$', 'create_object',
    {'model':Page, 'post_save_redirect':'/randomnote/'} ),

Pageというモデルのインスタンスを作ってsaveしたら /randomnote/へリダイレクトしてね という意味です。 updateはobject_idとモデルがPageという引数を渡してます。 http://localhost:8000/randomnote/update/1/というurlでアクセスした場合

(r'^update/(?P<object_id>\d+)/$','update_object',
    {'model':Page}),

object_id=1, model=Pageという引数を渡します。 Pageモデルのidが1のインスタンスを表示、変更したらsaveするという意味です。デフォルトのリダイレクト先はmodel.pyのclass Pageのget_absolut_url()というメソッドになります。これを指定しないとエラーになるので models.pyのPageに

def get_absolute_url(self):
    return "/randomnote/detail/%s" % str(self.id)

を追加してください。

3~7行目で objectが存在してるかどうかでCreateかUpdateか判断してます。 8~14行目は 汎用ビューから受け取るformというインスタンスがエラーを持ってるかどうかを確認してあったら表示 15行目~ はインスタンスそれぞれの値を受け取ってエラーがあれば表示してます。 <label for="id_body">Body:</label>は決まった書き方です。 objectだったりformだったり、ちょっと紛らわしいけど、動かしてみればすぐに分かると思います。

'myproject/templates/randomnote/page_form.html'

{% extends "randomnote/base.html" %}
{% block content %}
{% if object %}
    <h1> Update </h1>
{% else %}
    <h1> Create </h1>
{% endif %}
{% if form.has_errors %}
    {% for field in form.fields %}
        {% if field.error %}
            {{ field.error }}
        {% endif %}
    {% endfor %}
{% endif %}
    <form action="." method="post" accept-charset="utf-8">
        <label for="id_body">Body:</label><br/>{{ form.body }}
        {% if form.body.errors %} *** {{ form.body.errors|join:", "}}{% endif %}<br/>
        <p><input type="submit" value="Continue →" /></p>
    </form>
{% endblock %}

削除の汎用ビュー(create_update.delete_object)はちょっと特殊で アプリ名/モデル名_comfirm_delete.htmlというテンプレートを作ります。 いきなり消さないで確認するためのページですね。 (ここってJavascriptじゃね!?という話も確かにあります)

http://localhost:8000/randomnote/update/1/ というアクセスがきた場合

(r'^delete/(?P<object_id>\d+)/$','delete_object',
     {'model':Page, 'post_delete_redirect':'/randomnote/'} ),

によって object_id=1, model=Page, post_delete_redirect=/randomnote/ という引数を渡します。 モデルがPageでidが1のインスタンスを消して 消したら /randomnote/へリダイレクトしてねということです。

'myproject/templates/randomnote/page_confirm_delete.html'

<form method="post" action=".">
<p>Are you sure?</p>
<input type="submit" />
</form>

この辺は型みたいなものなので他のアプリを作るときもサンプルを見ながら作ることになると思います。

これでとりあえず動くようになります。

http://farm1.static.flickr.com/115/278026275_47022ccee8.jpg

bodyに何も入れずに保存しようとすると Djangoは国際化対応してあるので、ちゃんと日本語でエラーメッセージが表示されます。

http://farm1.static.flickr.com/100/278026260_faee9f0ee4.jpg
categories: django_randomnote, django
Read and Post Comments

Django再入門 RandomNoteを作る vol.1 下準備

March 07, 2007 at 01:00 AM | View Comments

Djangoの勉強にあたって何かいい題材はないかと探していたのですが まちゅダイアリー - Rails に(再)挑戦してみようかと思います。 Railsがよくわからないので途中脱線しながらになると思いますが、がんばって完成させてみたいと思います。

Djangoのインストール、MySQLのインストールは済みの状態からスタートします。 プロジェクトを作りたいPATHにて

django-admin.py startproject myproject

でmyprojectというプロジェクトを作ります。

cd myproject

でmyporjectのディレクトリに移動すると以下のファイルができています。

__init__.py manage.py settings.py urls.py

djangoの場合はプロジェクト内に複数のアプリを持つことができるので、randomnoteというアプリを作りましょう

python manage.py startapp randomnote

randomnoteというディレクトリと配下に

randomnote/__init__.py randomnote/models.py randomnote/views.py

というファイルができます。

__init__.py は randomnote.models.__クラス名__ のようにディレクトリ.ファイル名 でPythonからアクセスできるにするためのものです。

manage.pycのようにpycという拡張子のファイルができるが、これはコンパイルされたファイル。 変更がないかぎり、pyじゃなくpycが自動的に使われます。 つまり特に意識しなくて良いし、subversionなどに含める必要がありません。

TextMateで開くとこのようになります。

http://farm1.static.flickr.com/84/277870544_6d11ee7185.jpg

では次にMySQLに使用するデータベースを作りましょう。 djangoはutf-8を扱うのでdefault character set utf8を付けます。

mysql> create database myproject default character set utf8;
Query OK, 1 row affected (0.50 sec)

次にsettings.pyの設定を行います。 settings.pyは設定ファイルですがPythonのプログラムそのものなので必要なモジュールをインポートできます。 ではいろいろ使うのでosモジュールをインポートしておきます

'settings.py'

#dbの設定

DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'myproject'
DATABASE_USER = 'your_username'  #あなたのユーザー名
DATABASE_PASSWORD = ''      #パスワード
DATABASE_HOST = ''
DATABASE_PORT = ''

#タイムゾーンと日本語化の対応

TIME_ZONE = 'Asia/Tokyo'
LANGUAGE_CODE = 'ja'

#テンプレートファイルを置くディレクトリの設定

TEMPLATE_DIRS = (
    os.path.join(os.getcwd(), "templates"),
)

# 管理画面とrandomnoteアプリの設定

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',
    'randomnote',   #'myproject.randomnote', より後々幸せになれるので
)

次にテンプレートファイルを置くディレクトリをmyprojectの下に作っておきましょう

mkdir templates

テンプレートファイルは TEMPLATE_DIRSで設定した場所ならどこでも探しにきてくれますが、 通常はプロジェクトの下が良いでしょう。

とりあえずの準備はこれで終了です。

models.pyの設定

randomnote/models.pyにモデルを作りましょう bodyだけで良いらしい。 __str__はインスタンスがprintされたときに表示される文字 class Adminを設定しておくと管理画面で編集できるようになります。

'randomnote/models.py'

from django.db import models

class Page(models.Model):
    body = models.TextField(blank=False)

    def __str__(self):
        return self.body[:20]

    class Admin:
        pass

#(注意: sqliteでself.body[:20]で日本語を無理やりスライスするとDBが壊れる症状がでます。


def __str__(self):
    return unicode(self.body, 'utf8')[:20].encode('utf8')

としてください。

つぎに

python manage.py syncdb

でデータベースに今作ったModelを反映させましょう プロジェクトの最初はいろいろなものが作られる&スーパーユーザーの設定を聞かれます

Creating table auth_message
Creating table auth_group
Creating table auth_user
Creating table auth_permission
Creating many-to-many tables for Group model
Creating many-to-many tables for User model
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use '____'): your superusername
E-mail address: your@email.adress
Password:
Password (again):
Superuser created successfully.
Adding permission 'message | Can add message'
Adding permission 'message | Can change message'
Adding permission 'message | Can delete message'
Adding permission 'group | Can add group'
Adding permission 'group | Can change group'
Adding permission 'group | Can delete group'
Adding permission 'user | Can add user'
Adding permission 'user | Can change user'
Adding permission 'user | Can delete user'
Adding permission 'permission | Can add permission'
Adding permission 'permission | Can change permission'
Adding permission 'permission | Can delete permission'
Adding permission 'content type | Can add content type'
Adding permission 'content type | Can change content type'
Adding permission 'content type | Can delete content type'
Adding permission 'session | Can add session'
Adding permission 'session | Can change session'
Adding permission 'session | Can delete session'
Creating example.com Site object
Adding permission 'site | Can add site'
Adding permission 'site | Can change site'
Adding permission 'site | Can delete site'
Adding permission 'log entry | Can add log entry'
Adding permission 'log entry | Can change log entry'
Adding permission 'log entry | Can delete log entry'
Creating table randomnote_page
Adding permission 'page | Can add page'
Adding permission 'page | Can change page'
Adding permission 'page | Can delete page'

と、さまざまなテーブルがデータベースにできます。 ちょっとみてみましょう

python manage.py dbshell

で対話式にMySQLを操作できます。履歴や補完が効くので通常のものより便利です

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

テーブルを確認

mysql> show tables;
+----------------------------+
| Tables_in_myproject        |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_message               |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_session             |
| django_site                |
| randomnote_page            |
+----------------------------+
12 rows in set (0.00 sec)

一番したのテーブルが今作ったモデルのテーブル アプリ名_モデル名のテーブルになります

カラムを確認

mysql> show columns from randomnote_page;
+-------+----------+------+-----+---------+----------------+
| Field | Type     | Null | Key | Default | Extra          |
+-------+----------+------+-----+---------+----------------+
| id    | int(11)  | NO   | PRI | NULL    | auto_increment |
| body  | longtext | NO   |     | NULL    |                |
+-------+----------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

と、bodyの他にidが自動的に作られてます。 この辺はおなじみですね。

ではテスト環境を動かしてみましょう

python manage.py runserver

Validating models...
0 errors found.

Django version 0.96-pre, using settings 'myproject.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

でブラウザでlocalhost:8000へアクセスしてみましょう。

http://farm1.static.flickr.com/88/277914769_933fe44b6f.jpg

このように動いていますね。

では管理ページを使えるようにしましょう。 urls.pyの # (r'^admin/', include('django.contrib.admin.urls')),

の #を消しましょう

'urls.py'

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    # Example:
    # (r'^myproject/', include('myproject.apps.foo.urls.foo')),

    # Uncomment this for admin:
     (r'^admin/', include('django.contrib.admin.urls')),
)

で、localhost:8000/adminをブラウザで開くと

http://farm1.static.flickr.com/108/277917725_04589016dc.jpg

管理画面が開きます。先ほどのsuperuserで設定したユーザー名とパスワードを入力すると

http://farm1.static.flickr.com/92/277919672_f781056707.jpg

一番下にrandomnote アプリの Pagesという欄ができてますね。 追加ボタンを押すと記入できるフォームが出来てます。 bodyはblank=Falseにしているので何も書かずに保存ボタンを押してみましょう。

ちゃんと怒られますね。

では適当な文章を入力して保存してみましょう。 私は「テストですはたしてどのようにこのページが保存されるのでしょうか??見物ですね」 と入力しました。

http://farm1.static.flickr.com/99/277952646_a9266d0702.jpg

ここで問題発生。 def __str__(self): でbody[:20] でbodyの頭から20文字を返すように指定したのですが、 unicode文字数で数えてしまっていますね。

これは今後の課題にして次へ行きましょう

とりあえずここまでで、Djangoの設定とModelの設定、管理画面での確認までできました。 Modelに blank=False と、設定するだけでバリデーションが効くのはDjangoの良いところじゃないでしょうか

categories: django_randomnote, django
Read and Post Comments

track feed ueblog