Hits:118 ... « 4 5 6 7 8 9 10 11 12 13 »

Django FormModelにてトラブった

今日はDjango! ずっと古いリビジョンで動かしていたので,最新版のDjangoに耐えられるように修正. maxlengthをmax_lengthへ,escapeを削除,form_for_model/form_for_instanceからFormModelへ…

前者の2つは特に簡単なコトなんですが,FormModelでチト苦労した. というのも,BookmarkやBlogのタグ付け(ManyToManyField)を「del.icio.us っぽく」していた. 詳細は nobu さんの ManyToManyFieldをちょっとdel.icio.usっぽく を見てみて下さい. という訳で,自分のBookmarkは下記のような感じになっていた.(この部分は個人メモを兼ねているので飛ばしてもらってかまいません.)

#####
# project/bookmark/forms.py
#
class MultiTagInput(forms.TextInput):

    def render(self, name, value, attrs=None):
        if isinstance(value, QuerySet):
            value = ' '.join([t.name for t in value])
        return super(MultiTagInput, self).render(name, value, attrs)

class MultiTagField(forms.Field):

    def __init__(self, **kwargs):
        kwargs.update({
            'widget' : MultiTagInput,
        })
        super(MultiTagField, self).__init__(**kwargs)

    def clean(self, value):
        super(MultiTagField, self).clean(value)
        values = []
        for name in value.replace('\u3000', ' ').split(' '):
            if len(name):
                tag, created = Label.objects.get_or_create(name=name)
                values.append(tag.id)
        if not len(values):
            raise forms.ValidationError(_('This field is required.'))
        return values
ENTRY_FORMFIELDS['labels'] = MultiTagField
def entry_form_callback(f, **kwargs):
    try:
        return ENTRY_FORMFIELDS[f.name](**kwargs)
    except:
        return f.formfield(**kwargs)


#####
# project/bookmark/views.pyの一部
#
def edit(request, id):
    """ 更新処理 """
    bookmark = get_object_or_404(Bookmark, pk=id)
    f = forms.form_for_instance(bookmark, formfield_callback=entry_form_callback)
    form = f(request.POST.copy() or None)

このforms.pyModelFormに対応するように,コールバック関数(entry_form_callback)の削除したり, 下記のようなModelFormを追記した.

class MultiTagInput(forms.TextInput):

    def render(self, name, value, attrs=None):
        if isinstance(value, QuerySet):
            value = ' '.join([t.name for t in value])
        return super(MultiTagInput, self).render(name, value, attrs)


class MultiTagField(forms.Field):

    def clean(self, value):
        super(MultiTagField, self).clean(value)
        values = []
        for name in value.replace('\u3000', ' ').split(' '):
            if len(name):
                tag, created = Label.objects.get_or_create(name=name)
                values.append(tag.id)
        if not len(values):
            raise forms.ValidationError(_('This field is required.'))
        return values


class LabelForm(forms.ModelForm):
    """ Form for Bookmark Label """
    class Meta:
        model = Label
        fields = ('name',)
        exclude = ('created_at', 'updated_at')


class BookmarkForm(forms.ModelForm):
    """ Form for Bookmark """
    labels = MultiTagField(widget=MultiTagInput) # ここで上記のFieldを設定.
    class Meta:
        model = Bookmark
        fields = ('title', 'url', 'comment', 'labels')
        exclude = ('created_at', 'updated_at')

そして,views.pyでは,

def edit(request, id):
    """ 更新処理 """
    bookmark = get_object_or_404(Bookmark, pk=id)
    form = BookmarkForm(request.POST.copy() or None, instance=bookmark)

てな感じに修正してうまくいくと思ったのですが….だめだったぁ〜.新規作成は成功なのですが,更新しようとするとタグフィールドにタグ名(label.name)ではなくIDになってしまった. ということで,コードを追いかけてみました.

今まで使っていたform_for_instanceは下記の部分でManyToManyにうまく対応してやっているように見える.

# site-packages/django/newforms/models.py

def form_for_instance(instance, form=BaseForm, fields=None,
                      formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
    warn("form_for_instance is deprecated. Use ModelForm instead.",
        PendingDeprecationWarning, stacklevel=3)
    model = instance.__class__
    opts = model._meta
    field_list = []
    for f in opts.fields + opts.many_to_many:

        if not f.editable:
            continue
        if fields and not f.name in fields:
            continue
        current_value = f.value_from_object(instance) # <= フィールドにでる値
        formfield = formfield_callback(f, initial=current_value)
        if formfield:
            field_list.append((f.name, formfield))
    base_fields = SortedDict(field_list)
    return type(opts.object_name + 'InstanceForm', (form,),
        {'base_fields': base_fields, '_model': model,
         'save': make_instance_save(instance, fields, 'changed')})

けどもFormModel部分では,

# site-packages/django/newforms/models.py

class BaseModelForm(BaseForm):
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 instance=None):
        opts = self._meta
        if instance is None:
            # if we didn't get an instance, instantiate a new one
            self.instance = opts.model()
            object_data = {}
        else:
            self.instance = instance
            object_data = model_to_dict(instance, opts.fields, opts.exclude) # <= 自分の希望通りの動きをしてくれてないような感じ….
        # if initial was provided, it should override the values from instance
        if initial is not None:
            object_data.update(initial)  # <= updateしてる.
        BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)

ん〜とコードを眺めていると「initial」を渡してあげればーupdateしてるのでー views.py を下記のように変更して,とりあえず,目的は達成!

def edit(request, id):
    """ 更新処理 """
    bookmark = get_object_or_404(Bookmark, pk=id)
    # タグフィールドに表示したいものをinitialで渡してあげる.
    form = BookmarkForm(request.POST.copy() or None, instance=bookmark, initial={'labels' : bookmark.labels.all()})

もっとうまい方法があるのかなぁ〜.このままだとーあとでコード見たときに何しているかわからないよね? 多くの部分を省いて説明しているので,わけのわからない内容になってしまったかも….

しっかし,久しぶりにDjango触れてー楽しかった☆

[Python] [Django]

2008/02/19 23:48 | Comments(0)

Rails acts_as_paranoid を覗く

前回のBLOG 続き,

では,init.rbを覗いてみる.(素人なのでー間違っている箇所が多いと思います.ツッコミいただければー幸いです.)

# vendor/plugins/acts_as_paranoid/init.rb

class << ActiveRecord::Base
  def belongs_to_with_deleted(association_id, options = {})
    with_deleted = options.delete :with_deleted
    returning belongs_to_without_deleted(association_id, options) do
      if with_deleted
        reflection = reflect_on_association(association_id)
        association_accessor_methods(reflection,            Caboose::Acts::BelongsToWithDeletedAssociation)
        association_constructor_method(:build,  reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
        association_constructor_method(:create, reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
      end
    end
  end

  alias_method_chain :belongs_to, :deleted
end
ActiveRecord::Base.send :include, Caboose::Acts::Paranoid

1行目からわからない! " class A < B " なら AクラスはBクラスを継承するということだが…. これなんなん!?

これは,クラスメソッドの追加でした.つまり,ActiveRecord::Base クラスに belongs_to_with_deleted メソッドを追加している.下記にちょこっと試してみた!

$ irb
> class Base
>    def hoge
>        'hoge'
>    end
> end
> class << Base
>    def hige
>         'hige'
>    end
> end
>
> Base.hige    # 追加されたクラスメソッドの実行
=> "hige"
> Base.hoge   # インスタンスメソッドだから実行できない
NoMethodError: undefined method `hoge' for Base:Class
> obj = Base.new
> obj.hige       # クラスメソッドだから実行できない
NoMethodError: undefined method `hige' for #<Base:0xb7e8738c>
> obj.hoge      # インスタンスメソッドの実行
=> "hoge"

とういうことで納得.次に「alias_method_chain :belongs_to, :deleted」これ何してんだろ…. これrubyじゃなくてーrailsのメソッドだった…. このファイルを覗くと答えが見れる.

ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/module/aliasing.rb`

alias という言葉通りな感じですね. 試しに使ってみる.

$ ./script/console
>> class Gly
>>     def name
>>         'glycine'
>>     end
>> end
>> class G < Gly
>>     def name_with_short
>>         name_without_short + '( GLY )'
>>     end
>>     alias_method_chain :name, :short
>> end
>> Gly.new.name
=> "glycine"
>> G.new.name
=> "glycine( GLY )"

name_without_short は,拡張前のメソッドを呼び出せる. name_with_short は,拡張したメソッドを呼び出せる. この説明だとわかりにくいですよね.

この段階になっても,Model に acts_as_paranoid と書いただけでープラグインがうまく動くことは理解できない. 最後の行の「ActiveRecord::Base.send :include, Caboose::Acts::Paranoid」これですね. ここは,(クラスメソッドが追加された)ActiveRecord::Base に Caboose::Acts::Paranoid モジュールを include している. Paranoidモジュールは,vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb にあります. しかし,このモジュールが読み込まれてもー (ClassMethodsモジュール内の)acts_as_paranoid メソッドは,Model から呼び出せない. と思ったらー(Paranoidモジュールの)self.included(base) ってーのが ClassMethodsモジュールをextend してるわ!ということで Model に acts_as_paranoid と書くとー Modelを 読み込んだとき に acts_as_paranoidメソッドが実行される.

メモがてらにモジュールのincludeを試してみる.

$ irb
> module Paranoid
>     def self.included(base)
>         p self    #=> Pranoid
>         p base  #=> Base
>         base.extend ClassMethods
>     end
>
>     module ClassMethods
>        def acts_as_paranoid
>             'ACTS_AS_PARANOID'
>        end
>     end
> end
>
> class Base
>     include Paranoid
> end
>
> Base.acts_as_paranoid
=> "ACT_AS_PARANOID"

これで何とかーModelにacts_as_paranoid と書くことだけでーModelにプラグインが反映されることが理解. いやー疲れた.やっぱ言語勉強しないとね.今回もー toshi78 さんにも協力してもらっちゃいました.

結局,このプラグインを全部解読してませんが,色々と疑問. なんで,こんなにモジュールがネストされているんだろ? また,ActiveRecord::Baseにクラスメソッドを最初に追加したんだろ? だってーこの init.rb は Rails がロードされたときに読み込まれる.だもんでー 論理削除を必要としないModelにまでー不必要なクラスメソッドが追加されちゃいません?パフォーマンス悪くなっちゃうんじゃない? 素人の俺には「ActiveRecord::Base.send :include, Caboose::Acts::Paranoid」の中でーうまく「belongs_to_with_deleted」クラスメソッドを追加する処理をすればいいのではないのかなぁ〜.

と,素人の考えが色々と浮かぶ….

[RubyonRails] [Ruby]

2008/02/19 01:16 | Comments(0)

Railsプラグイン acts_as_paranoid

Railsのプラグインとやらは結構色々あるみたい.ということで,Railsプラグインを使ってみた. 論理削除をするプラグインってーのがあるので試しに…. (論理削除するプラグインなんていらないでしょ!? と思う方も多いのではないかな!? 実際自分は思ったがープラグインを試すには簡単そうだったのでね)

単純な登録/閲覧/更新/物理削除 できるサンプルアプリに論理削除プラグインを追加してみた.

# db/migrate/001_create_entries.rb

class CreateEntries < ActiveRecord::Migration
  def self.up
    create_table :entries do |t|
      t.column      :name,      :string,        :null => false
      t.column      :created_at,:datetime,      :null => false
      t.column      :updated_at,:datetime,      :null => false
    end
  end

  def self.down
    drop_table :entries
  end
end

プラグインのインストール.

# リポジトリの追加
$ ./script/plugin source http://techno-weenie.net/svn/projects/plugins
# 論理削除プラグイン(acts_as_paranoid)のインストール
$ ./script/plugin install acts_as_paranoid

インストールって,svn::export しているだけのようです.exportは,vendor/plugins/acts_as_paranoid/ にされてます. 本体の「lib/commands/plugin.rb」の中を覗くとー実行コマンドが覗ける.

カラム(deleted_at)の追加

# db/migrate/002_add_delete.rb

class AddDelete < ActiveRecord::Migration
  def self.up
    add_column      :entries, :deleted_at, :datetime
  end

  def self.down
    remove_column   :entries, :deleted_at
  end
end

Entryモデルに「acts_as_paranoid」を記述!

# app/models/entry.rb

class Entry < ActiveRecord::Base
acts_as_paranoid
end

これで終了! だけど下記のようなエラーが発生.

undefined method `construct_count_options_from_args' for Entry:Class

Trace覗くと,

vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb:91:in `count_with_deleted'

となっていたので,Google検索で調べたらー

90 def count_with_deleted(*args)
91     #calculate_with_deleted(:count, *construct_count_options_from_args(*args))       # 変更前
92     calculate_with_deleted(:count, *construct_count_options_from_legacy_args(*args)) # 変更後
93 end

とやったら修正できた. ほんで,試しにエントリーを削除してみたらー,論理削除された!

DBを覗いてみたら

mysql> SELECT * FROM entries;
+----+-----------+---------------------+---------------------+---------------------+
| id | name      | created_at          | updated_at          | deleted_at          |
+----+-----------+---------------------+---------------------+---------------------+
|  1 | ENDLESS   | 2008-02-18 12:02:00 | 2008-02-18 12:02:36 | NULL                |
|  2 | TEST NAME | 2008-02-18 12:02:00 | 2008-02-18 12:02:44 | 2008-02-18 12:16:09 |
+----+-----------+---------------------+---------------------+---------------------+

id=2 を削除したら, deleted_at に削除した日時が記録された. あれ!? ケド updated_at は更新されてないみたい.まぁ~削除日時が登録されているからいいのか….

と,プラグインを追加して論理削除が成功したのはいいのですがーこの「acts_as_paranoid」1行が何をしたのかが問題ですよね. Rubyをまともに読めない俺だからいけないのでしょう...

ということで,プラグインを覗いてみる! 怪しいファイル見っけ! vendor/plugins/acts_as_paranoid/init.rb これが勝手に読み込まれているようだ!

あっ...早速言語の壁!Ruby(Rails?)が読めない! alias_method_chain,association_accessor_methods この辺りから調べねば…. ということで,今日はここまでです.

[RubyonRails]

2008/02/18 20:24 | Comments(0)

Railsに挑戦

とりあえず,単純な登録/閲覧/更新/削除/コメントができるBLOGを作成してみた. これを通して,migrationでDB管理して遊んでみたり,モデル操作を遊んでみたりした.migrationは気持ちよいね. ん〜書き方もまだ何が正しいかわからないです. そんなcontrollerのソースがこれ↓. なんだかーこれだと無駄が多いような気がする. 色んなサンプル見てみないといけないっすね!!

class BlogController < ApplicationController

    def index
        self.list
        render :action => 'list'
    end

    verify :method => :post, :only => [:create, :update],
           :redirect_to => { :action => :list }

    def list
        @title = 'BLOG LIST'
        @blog_pages, @blogs = paginate(
            :blog_blogs,
            :per_page => 5,
            :conditions => "is_active = true",
            :order => "created_at DESC"
        )
    end


    def show
        begin
            @blog = BlogBlog.find(
                @params[:id],
                :conditions => "is_active = true"
            )
            @title = @blog.title
            @comment = BlogComment.new
        rescue ActiveRecord::RecordNotFound
            redirect_to '/404.html'
        end
    end


    def new
        @title = 'NEW BLOG'
        @blog  = BlogBlog.new()
    end


    def create
        @blog = BlogBlog.new(@params[:blog])
        if @blog.save
            redirect_to :action => 'list'
        else
            render :action => 'new'
        end
    end


    def edit
        @title = 'EDIT BLOG'
        begin
            @blog  = BlogBlog.find(
                @params[:id],
                :conditions => "is_active = true"
            )
        rescue ActiveRecord::RecordNotFound
            redirect_to '/404.html'
        end
    end


    def update
        begin
            @blog = BlogBlog.find(
                @params[:id],
                :conditions => "is_active = true"
            )
        rescue ActiveRecord::RecordNotFound
            redirect_to '/404.html'
        end
        if @blog.update_attributes(@params[:blog])
            redirect_to :action => 'show', :id => @blog.id
        else
            render :action => 'edit'
        end
    end


    def delete
        begin
            BlogBlog.update(@params[:id], :is_active => false)
            flash[:notice] = 'success delete blog.'
        rescue  ActiveRecord::RecordNotFound
            flash[:notice] = 'cannot delete blog.'
        end
        redirect_to :action => 'list'
    end


    def comment
        begin
            @blog = BlogBlog.find(
                @params[:id],
                :conditions => "is_active = true"
            )
        rescue
            redirect_to '/404.html'
        end
        @params[:comment]['blog_blog_id'] = @params[:id]
        @comment = BlogComment.new(@params[:comment])
        if @comment.save
            redirect_to :action => 'show', :id => @params[:id]
        else
            render :action => 'show'
        end
    end

end

今度は,このBLOGにタグ付けできるような処理(多対多:has_and_belongs_to_many)をやってみよう.とりあえず,書いてみてrubyに慣れてみよう.

追記: paramsは,@params ではなく params で受け取れるようです. また,beginもやらなくてもOKのようです.Railsが裏でやってくれているようです. 結局,バージョン遅れの汚いソースコードとなっておりました.

[RubyonRails] [Ruby]

2008/02/14 21:19 | Comments(1000)

対話シェル

個人メモ

自分はよくPythonを使っているとき,Pythonシェル(ipythonなど)で dir関数を使って中身を片っ端から覗いて,さらにhelp関数で使用方法を確認したりしていた.

Rubyの場合はどうやるんだろと調べてみた.( toshi78 さんにも協力していただいちゃいました.ありがとうございます.) Rubyシェル(irb)で Object.methods とやるとオブジェクトが持つメソッドを見ることができた.ん~とりあえずこれでいいかな!?

ほんで,help関数にあたるモノを探すと refe というのをインストールするとRubyのリファレンスからエントリを引っ張ることができるとのこと. では早速,

$ sudo /usr/local/ruby/bin/gem install refe

インストールできたようなので実験

$ refe String concat
    String#concat
    --- self << other
    --- concat(other)

        文字列 other の内容を self に連結します。
        other が 0 から 255 の範囲の Fixnum である場合は
        その 1 バイトを末尾に追加します。

        self を返します。

と成功すればOK! が,俺の場合上記のようにうまく行かなかった…文字化けした…. 文字化けは下記のように修正して成功. 参考サイト : http://d.hatena.ne.jp/nagaton/20060914/1158247239

$ sudo vi /usr/local/ruby/lib/ruby/gems/1.8/gems/refe-0.8.0.3/lib/refe/searcher.rb
def adjust_encoding( str )
  if shift_jis_platform?
    NKF.nkf('-Es', str)
  else
    str
  end
end

# の部分を下記のように修正

def adjust_encoding( str )
  NKF.nkf('-w', str)
  #if shift_jis_platform?
  #  NKF.nkf('-Es', str)
  #else
  #  str
  #end
end

おーよかったよかった….しかし,この refe って Rubyシェル内で使えないじゃんか! ということで,さらに .irbrc に下記を追記. 参考サイト : http://d.hatena.ne.jp/secondlife/20051114/1131894899

# 補完を有効
    require 'irb/completion'

    module Kernel
      def r(arg)
        puts `refe #{arg}`
      end
      private :r
    end

    class Module
      def r(meth = nil)
        if meth
          if instance_methods(false).include? meth.to_s
            puts `refe #{self}##{meth}`
          else
            super
          end
        else
          puts `refe #{self}`
        end
      end
    end

では,早速..

$ irb
>> Array.r :inspect
Array < Object#inspect
 --- inspect

    オブジェクトを人間が読める形式に変換した文字列を返します。

    組み込み関数 p は、このメソッドの結果を使用して
    オブジェクトを表示します。

=> nil

出来たみたい!

でも…,これってRubyのリファレンスから引っ張ってきているだけなので….Pythonのhelp関数とは違う….

もちろん,Railsのコンソール起動(./script/console)でRailsのモデルのリファレンスがひけるわけがない…. ん~Railsではないのかなぁ~. Pythonのhelpって便利なんだよなぁ~.

[Ruby]

2008/02/13 17:41 | Comments(1000)

Hits:118 ... « 4 5 6 7 8 9 10 11 12 13 »