Tag/Django

モデルのsaveが変わったよ。

Djangotrunkqueryset-refactor がマージされたタイミングで、 モデルの save メソッドにも改良が加えられてたのでメモメモ。

マージ前(r7476)save メソッド自体に実際の処理が書かれてた。

# django/db/models/base.py@213行目あたり
def save(self, raw=False):
    dispatcher.send(signal=signals.pre_save, sender=self.__class__,
                    instance=self, raw=raw)
    # 中略...

    dispatcher.send(signal=signals.post_save, sender=self.__class__,
                    instance=self, created=(not record_exists), raw=raw)

ので、データ登録の際に何か処理を挟みたい場合、 オーバーライドして処理を追加してあげて、 良いところで親の save をコールしてあげる事で実装してた(と思う)。

class Hige(models.Model):
    """
    HIGEなモデル
    """
    def save(self, raw=False):
        # なにか追加したい処理が入る。
        super(Hige, self).save(raw=raw)

でも、これだとダイレクトにデータを保存したい場合に、 適当な名前でもうひとつ save メソッドを作ってやる必要があった。

class Hige(models.Model):
    """
    HIGEなモデル
    """
    def save(self, raw=False):
        """
        adminとかでは処理を追加したいので、
        オーバーライドせざるを得ないんだ。
        """
        # なにか追加したい処理が入る。
        super(Hige, self).save(raw=raw)

    def direct_save(self, raw=False):
        """
        たまーに処理を追加しないのも必要。
        """
        super(Hige, self).save(raw=raw)

こういう状況になると、ものっすごい残念な気持ちになってた訳なのですが、 これが マージ後(r7477) でちょっと素敵に改良されてるよ。

# django/db/models/base.py@293行目あたり
def save(self):
    """
    Save the current instance. Override this in a subclass if you want to
    control the saving process.
    """
    self.save_base()

def save_base(self, raw=False, cls=None):
    """
    Does the heavy-lifting involved in saving. Subclasses shouldn't need to
    override this method. It's separate from save() in order to hide the
    need for overrides of save() to pass around internal-only parameters
    ('raw' and 'cls').
    """
    # 省略...

実際のデータ登録の処理は全部 save_base メソッドで実装しておいて、 save メソッドはそのフックになってるよ。 これだと、ダイレクトにデータを登録する必要が出て来た場合でも、 save_base をコールすれば良いだけ。 なので、特にモデルに変更を加える必要は無い。

class Hige(models.Model):
    """
    HIGEなモデル
    """
    def save(self):
        """
        処理を追加したいのでオーバーライド。
        raw=Falseは無くなってるよ。
        """
        # なにか追加したい処理が入る。

        # self.save_base()ってしてないのはなんとなく。
        super(Hige, self).save()

これでもう残念な気持ちにならなくて良いね。 また一つ、 Django が好きになりましたとさ :-)

Posted at: 
2008/05/02 00:29:35
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/05/02/changed-models-save-on-django/trackback/

DjangoのPaginatorが変わってるよ。

Djangor7306ObjectPaginator がdeprecated扱いになったので、 どんな風に変わったのか調べてみたよ。

テストを走らせてみると、こんな感じのWarningを思いっきり吐かれた。

DeprecationWarning: The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.

なるほど、 Paginator に変わったらしい。 実際にどう変わったのかな?って確認しようと、 django/core/paginator.py をちょっと覗いてみると、 QuerySetPaginator って派生クラスもある。 どこが違うかと言うと、 _get_count なメソッドだけがオーバーライドされてる。

まずは Paginator_get_count を見てみるとこうなってる。

def _get_count(self):
    "Returns the total number of objects, across all pages."
    if self._count is None:
        self._count = len(self.object_list) # <-- lenでカウントを取得
    return self._count
count = property(_get_count)

_countなプロパティがセットされて無ければ、 lenを使ってカウントを取得してる。

今度は QuerySetPaginator_get_count を見てみる。

def _get_count(self):
    if self._count is None:
        self._count = self.object_list.count() # <-- QuerySetのcountをコール
    return self._count
count = property(_get_count)

_countなプロパティがセットされて無ければ、 QuerySet のcountをコールして、COUNTなSQLを発行してカウントを取得してる。なるほどー。 って事は、 Paginator を使うよりも、 QuerySetPaginator を使う方が良いね。 今までの ObjectPaginatorQuerySet しか扱えなかったんだから、 WarningでもQuerySetPaginator insteadって出して欲しいなぁ。ってか出すべきな気がする。

念のために汎用ビューを見てみると、 やっぱり QuerySetPaginator を使ってるよ。

もう一つ、今回の変更で加わったのが Page なクラス。 今まで ObjectPaginatorget_page をコールすると、 返り値は QuerySet だった。

try:
    # 返り値のobject_listはQuerySet
    object_list = paginator.get_page(page - 1)
except InvalidPage:
    raise Http404

それが Paginatorpage をコールした時の返り値は Page になる。

try:
    # 返り値のpage_objはPage
    # マイナス1しなくて良くなった!
    page_obj = paginator.page(page)
except InvalidPage:
    raise Http404

# Pageの持つobject_listプロパティにQuerySetが入ってる
object_list = page_obj.object_list

この Page なクラスはページネイション関連のデータを保持する実装になっていて、 汎用ビューで今まで使えてたページネイション関連の変数は、 全部この Page がメソッドとして実装してる。 結構な変更がかかってるけども、 明らかに使いやすくなってると思う。 今までのがデータの引き回しとかが多くて、微妙に使いづらかったってのもあるけども :-P

ObjectPaginatorQuerySetPaginator にリプレイスする時は、 django/views/generic/list_detail.py を見れば分かりやすいと思うよ :-)

Posted at: 
2008/03/26 03:31:44
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/03/26/change-paginator-on-django/trackback/

Djangoのpre_saveとpost_saveがちょっと便利に

Django のちょっとしたコネタ。

モデルの save メソッドには、 raw って引数があるのですが、 r7054 から pre_savepost_save にもコレが入ってくるようになっております。

なので、 pre_savepost_save でコレを見てやる事で、 処理を分岐させる事が出来るようになりました。

rawTrue の場合、 余計な作業は省いてね。って事なので、 このブログではフックの最初で見てあげるようにしております。

def post_save_entry(instance, created, raw):
    """
    エントリーのセーブ後のフック
    """
    if raw or not created:
        # raw=Trueか新規登録じゃ無い場合はココでおしまい。
        return
    # 実際やりたい処理がつづく。

loaddata してデータを取り込む時なんかも rawTrue で入ってくるので、 こんな感じにしておくと余計な処理を省けるので便利。

Posted at: 
2008/02/17 00:39:07
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/17/more-useful-save-hook-on-django/trackback/

Djangoでパーマネントリダイレクト

前のブログからの変更でURLが結構変わったので、 どうしようかな?って思ってたんですが、 Django の汎用ビューにある redirect_to を使ってみる事に。

中を見てみると、必須な引数は url のみで、 残りの引数で置換してリダイレクトする。って仕組み。 また urlNone にすると、 ステータスコード410なレスポンスを返してくれる。

今回は /weblog/ はリダイレクトしてあげて、 /bookmark/ に来たのは410を返すようにしたかったので、 こんな感じにしてみたよ。

# urls.py
urpatterns = patterns('django.views.generic.simple',
    #   weblogに来たリクエストをリダイレクト。
    (r'^weblog/(?P<path>.*)$', 'redirect_to', {'url': '/%(path)s'}),
    #   bookmarkに来たリクエストは410を返す。
    (r'^bookmark/(?P<path>.*)$', 'redirect_to', {'url: None'}),
)

410の場合、ブラウザで見ると何も表示されないのが残念だけども、 気になるようならコピペして弄ってあげれば良いと思う。

こういうちょっとしたのが既に実装されてるってのは、 地味に嬉しかったりする。

Posted at: 
2008/02/10 01:35:20
0 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/10/permanent-redirect-on-django/trackback/

ブログ乗り換えました!

新しく作り直したブログに乗り換えました! て言っても、まだまだ安定してないと思われます。 ココからじゃんじゃんバグを直して行くよ!

Django のソースを眺めながら作った(読めてるかどうかは別)ので、 また少し Django と仲良くなれたような気がする。

URLが変わってしまったトコロはリダイレクトさせてる(つもり)なのですが、 リンク切れを発見された方は、教えて頂けるとありがたいです。 無くなっちゃったURLに関しては...。 まぁ、それはご愛嬌と言う事で。

ソースは ここらへん に転がっております。 ので、ツッコミとかも頂けると両手をあげて喜びます!

Djangor7054 以前で動かしてしまって、 ちょっとしたハプニングに見舞われたのは秘密です :-)

Posted at: 
2008/02/09 03:30:08
6 Comments
1 TrackBack
Tags: 
Django
memo
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/09/change-blog/trackback/

Djangoのflatpagesは凄いよ!

今作ってるブログ の静的ページをどうしようか迷ってる時に、 常山さん に刺激されて flatpages を使ってみたよ。

使い方はめちゃめちゃ簡単。 settings.pyINSTALLED_APPSMIDDLEWARE_CLASSES に追記するだけ。

#   一番最後に追加する。
MIDDLEWARE_CLASSES = (
    ...
    'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
)

INSTALLED_APPS = (
    ...
    'django.contrib.flatpages',
)

デフォルトだと flatpages/default.html なテンプレートを探しにくるので、 先に用意しておいてあげる。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
    "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
    <head>
        <title>{{ flatpage.title }}</title>
    </head>
    <body>
    {{ flatpage.content }}
    </body>
</html>

後は管理画面からゴリゴリ登録しまくってあげるだけ。 urls.py の編集はしなくても大丈夫! 登録したURLにアクセスすると、 ちゃんと表示されるはず!

コレ凄いよ!めちゃめちゃ便利! ステータスコード404に反応して該当するページを探してくれるよ! 簡単なサイトなら、コレだけで出来ちゃうかもよ!(言い過ぎ)

今欲しい機能としてはちょっとオーバースペックだけど、 これはかなり良いなぁ :-)

アイデアだけもらうかなぁ? それともコレ使っちゃうかなぁ? かなり迷うトコロでありますです。

flatpages の詳しい説明は flatpages アプリケーション : Django オンラインドキュメント和訳 でどうぞ。

Posted at: 
2008/02/07 05:42:55
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/07/django-flatpages-is-very-useful/trackback/

Djangoでモデルのsaveをフックする

Django の子ネタ。

Django のモデルの save メソッドには、 pre_savepost_save なフックポイントがあって、 これ使うとちょっとした処理を差し込めて便利。

# myapp/models.py

from django.db import models

#   必要なものをインポート。
from django.dispatch import dispatcher
from djanog.db.models import signals

class MyModel(models.Model):
    #   省略

#   セーブ前に呼ばれる関数
def pre_save_mymodel(instance, **kwargs):
    #   セーブ前にやりたい事を書く。

#   セーブ後に呼ばれる関数
def post_save_mymodel(instance, **kwargs):
    #   セーブ後にやりたい事を書く。

#   関数を登録。
dispatcher.connect(receiver=pre_save_mymodel,
                   signal=signals.pre_save,
                   sender=MyModel)
dispatcher.connect(receiver=post_save_mymodel,
                   signal=signals.post_save,
                   sender=MyModel)

こうやっておくと、 MyModelsave が呼ばれたときにコールされる。 post_save な関数には created ってブール値が入ってきてるので、 新規登録なのか?更新なのか?も分かるようになってるよ。

今作ってるブログ では、 コレ使って更新Ping飛ばしたり、 twitterに投げたりしとります。

Posted at: 
2008/02/07 01:23:31
4 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/07/hook-models-save-on-django/trackback/

Djangoのレスポンスコード

Djangoのちょっとした子ネタ。

今まで HttpResponse から始まって、 HttpResponseForbidden とか HttpResponseNotFound とか、 もりもりインポートするか、

t = loader.get_template('sometemplate.html')
response = HTTPResponse(t.render(Context({})))
response.status_code = 404
return response

とかして、ステータスコードをごにょごにょしてたんですが、 実は HttpResponse のコンストラクタで、 ステータスコードを指定出来る事を発見。

return HttpResponse(t.render(Context({})), status=404)

とかすると、ステータスコード404でレスポンスを返せるよ。 これは地味に便利 :-)

Posted at: 
2008/02/05 22:28:04
0 Comments
0 TrackBacks
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2008/02/05/responsecode-on-django/trackback/

mod_wsgiでHTTP_AUTHORIZATIONを受け取る。

Django でDigest認証用のバックエンドを書いていたんだけれども、 Apachemod_wsgi で動かすとなぜか認証を通過出来ない。

で、色々と泥臭いデバッグを繰り返したところ、 そもそも HTTP_AUTHORIZATION ヘッダが渡されていない様子。

request.META.get('HTTP_AUTHORIZATION')  # <-- コレがNoneになる。

ちょこっとググってみる前に、 mod_wsgi のドキュメントを眺めてみると、 ちゃんと Configuration GuidelinesConfiguration Directives に書いてありました。

WSGIPassAuthorization On

これを VertualHost ディレクティブに追加したら、 バッチリ HTTP_AUTHORIZATION を受け取れた! 認証も通りましたですよ!

セキュリティ面から、 WSGIPassAuthorization ディレクティブはデフォルトではOffらしい。 なるほどなるほど。

Posted at: 
2007/12/27 01:43:07
2 Comments
0 TrackBacks
Tags: 
apache
Django
mod_wsgi
Python
Trackback: 
http://humming.via-kitchen.com/2007/12/27/passed-httpauthorization-on-modwsgi/trackback/

newformsが変わってたよ。

いつの間にか、 newformsform_for_modelform_for_instance がdeprecatedになってるよ。 変わりに ModelForm が出来ていて、 モデルに紐づかないフォームと同じ様に定義出来るようになったっぽい。

# vim: encoding=utf-8 :

from django import newforms as forms
from blog.models import Entry

class EntryForm(forms.ModelForm):
    """
    エントリーに紐づくフォーム。
    """

    class Meta:
        model = Entry

基本はコレだけで良いみたい。 使い方は今までと変わらないのがありがたいっすなぁ。

>>> from blog.forms import EntryForm
>>> form = EntryForm()
>>> form.is_bound
False
>>> form.is_valid()
False
>>> del form
>>> data = {'title': u'test', 'slug': u'test', 'body': u'test'}
>>> form = EntryForm(data=data)
>>> form.is_bound
True
>>> form.is_valid()
True
>>> object = form.save()
>>> object
<Entry: test>

更新の時は、 __init__instance キーワードで渡してやると良いみたい。

>>> data = {'title': u'new title', 'slug': u'new-slug', 'body': u'new text'}
>>> # instanceキーワードで渡す。
>>> form = EntryForm(data=data, instance=object)
>>> form.is_bound
True
>>> form.is_valid()
True
>>> object = form.save()
>>> object
<Entry: new title>

バリデーションするフィールドを指定する fields は、 フォームの Meta クラスに移動したらしい。 それと、 exclude ってのも追加されたみたいで、 fields と逆の、バリデーションから省くフィールドを指定出来る。

# vim: encoding=utf-8 :

class EntryForm(forms.ModelForm):
    """
    """
    class Meta:
        #   ターゲットとなるモデルを指定。
        model = SomeModel
        #   バリデーションするフィールドを列挙。
        fields = ('some', 'fields', 'here')
        #   バリデーションしないフィールドを列挙。
        exclude = ('some', 'exclude', 'fields', 'here')

今までよりも少し拡張しやすくなったような。 フィールドのオーバーライドも出来るみたいなんだけども、 そこらへんはまだ試してないので、またおいおい。

Posted at: 
2007/12/23 17:26:02
2 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2007/12/23/changed-newforms/trackback/

Categories

Archives