読者です 読者をやめる 読者になる 読者になる

Blogを作る(5)記事の削除

Pylons

 記事の削除を行えるようにします。

削除の実装

 blogcontroller/controllers/entries.pyのEntiresControllerのdeleteメソッドを実装します。

    def delete(self, id):
        """DELETE /id: Delete an existing item."""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="DELETE" />
        # Or using helpers:
        #    h.form(h.url_for('entry', id=ID), method='delete')
        # url_for('entry', id=ID)
        current = model.session_context.current
        obj = current.query(Post).get_by(id=id) or abrot(404)
        current.delete(obj)
        current.flush()
        redirect_to('/entries')

 SQLAlchemyのORM機能を使って実装しましたが、Prepared Statementを使ってもっとあっさり書いた方が正解かもしれません。

HTTPメソッドによる制限

 Pylons0.9.4以降のREST風URLマッピングを使用している場合、リソースの削除は、/entires/1のようなURLに、HTTP DELETEメソッドでリクエストを送ることにより実行されます。
 前回エントリでも紹介したように、Pylonsでは_methodという擬似HTTPメソッドパラメータによってHTTP PUT/DELETEを代用しているので、次のようなURLをGETでリクエストすると、コントローラのdeleteメソッドが呼ばれることになります。

http://localhost:5000/entires/1?_method=DELETE

 試しに、ブラウザのロケーションバーから、クエリ文字列?_method=DELETEを付けてアクセスしてみてください。該当する記事があった場合、問答無用で削除されます。
 この動作はちょっと気持ち悪いので、pylons.decorators.rest.restrictデコレータを使って、deleteメソッドを呼び出せるは、HTTP DELETEとPOSTだけに制限します。

from pylons.decorators.rest import restrict

class EntriesController(BaseController):
    # ... 略

    @restrict('POST', 'DELETE')
    def delete(self, id):
        # restrictデコレータを追加。
        # ... 上記の削除が続く。

削除確認画面を追加

 これで、GETでアクセスした場合に記事が削除されるという動作を回避することができますが、そのかわり削除確認画面を作らなくてはなりません。この画面のテンプレートをblogtutorial/templates/delete.mytとして用意します。

<h2><% _('Delete the post') %></h2>
<% h.form(h.url_for('entry', id=c.entry.id), method='delete') %>
<p><% _('Are you sure to delete this entry?') %></p>
<h3><% c.entry.title | h %></h3>
<div>
<% c.entry.content | h %>
</div>
<p>
<% h.submit(value=_('Delete')) %>
</p>
<% h.end_form() %>

<% h.link_to(_('Cancel'), url=h.url_for('edit_entry', id=c.entry.id)) %>

 ポイントは、

<% h.form(h.url_for('entry', id=c.entry.id), method='delete') %>

を使うことで、パラメータ_method=DELETEをHTTP POSTで送信するようにしていることです。上のrestrictデコレータを、@restrict('DELETE')とせずに、@restrict('POST', 'DELETE')と、あえてPOSTを許容しているのはこのためです。


 この画面に対応するURLは、/entries/1;deleteとしました。(Railsの場合、普通はどうやるんだろう?)
 このままでは、URLルーティングが行われないので、blogcontroller/config/routing.pyを修正し、これに対応するルーティングを設定します。

    map.resource('entry', 'entries')

    # 追加
    map.connect('confirm_delete', 'entries/:(id);delete', controller='entries', action='confirm_delete', condition=dict(method='GET'))

 また、この画面に対応するアクションをconfirm_deleteメソッドとして追加します。

class EntriesController(BaseController):
    # ... 略

    def confirm_delete(self, id):
        c.entry = model.session_context.current.query(Post).get_by(id=id) or abort(404)
        return render_response('entries/delete.myt')


 次のリンクをblogtutorial/templates/entries/form.mytに追加し、変更画面から削除確認画面に遷移させるようにしました。

<% h.link_to(_('Delete'), h.url_for('confirm_delete', id=c.entry.id)) %>

 これで、記事の削除機能も完了です。

 Pylonsのオフィシャルのドキュメントには、JavaScirptのconfirmを使って削除の確認を行う例Prototype.jsを使ってDrag&Dropでリソースを削除する例が掲載されていますが・・・、正直、それってどうでしょう?