[Django] 日本ひげ男協会のサイトを作成する。その5

注意

この記事は、id:SumiTomohiko:20070126:1169809341の続きです。

環境

この記事の内容は、Ubuntu 6.10, Python 2.4, Django 0.95で確認しました。

ログイン

ここまでで、会員を登録し、一覧表示する機能がなんとかできました。あと必要な機能は、自分の情報を編集し、あるいは退会する機能です。これを実現するためには、ログインとログアウトの機能が必要です。というわけで、まずログインを実装します。

Djangoの認証システム

ところで、Djangoには独自の認証システムがついています。しかし、これにはいくつか問題があります。

  1. ユーザを表すクラスに、django.contrib.auth.models.Userクラスを使わなければならない。このクラスには、first_nameやlast_nameといった、認証には関係せず、アプリケーションでも必要がない属性がついている。継承して使う方法もドキュメントには記されていない。
  2. 何より、juma.user.models.Userクラスを既に作ってしまっている。

というわけで、今回はDjangoが用意している認証システムは利用せず、自前でログインとログアウトを実装します。

Djangoの認証システムというのは、settings.pyにある、

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'juma.user', 
    'django.contrib.admin', 
)

のうち、django.contrib.authアプリケーションを指します。独自に認証システムを実装するのなら、これを削除してもいいのですが、それはできません。なぜならば、管理アプリケーションであるdjango.contrib.adminアプリケーションが、django.contrib.authアプリケーションに依存しているためです。django.contrib.adminも削除するのならdjango.contrib.authも削除できるのですが、管理アプリケーションも使いたいと思うので、これらはこのまま残しておくことにします。

ログイン

では、ログインを作成します。ログインするURLは、/user/login/にします。user/urls.pyは、以下のようになります。

from django.conf.urls.defaults import *
from juma.user.models import User

urlpatterns = patterns('',
    (r'^add/$', 'juma.user.views.add'), 
    (r'^list/$', 'django.views.generic.list_detail.object_list', { 'queryset': User.objects.all(), 'allow_empty': True, 'paginate_by': 20 }), 
    (r'^media/image/(?P<file_name>[-\w\d_\.]+)$', 'juma.user.views.image'), 
    (r'^login/$', 'juma.user.views.login'), 
)

# vim: tabstop=4 shiftwidth=4 expandtab

次に、ビューを作成します。最終的に、以下のようになりました。

def login(request):
    manipulator = juma.user.manipulators.LoginManipulator()
    if request.POST:
        new_data = request.POST.copy()
        errors = manipulator.get_validation_errors(new_data)
        if not errors:
            manipulator.do_html2python(new_data)
            try:
                user = User.objects.get(account=new_data["account"])
                if user.password != new_data["password"]:
                    raise User.DoesNotExist
            except User.DoesNotExist:
                _add_error(errors, "account", "アカウント名またはパスワードが違います。")
            else:
                request.session[USER_ID_KEY] = user.id
                return _redirect_list()
    else:
        errors = new_data = {}

    form = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('user/login.html', {"form": form})

Userオブジェクトを得たのち、パスワードを比較するという単純なものです(パスワードは、そのうちハッシュにするかもしれません)。認証に成功したら、USER_ID_KEY ("_user_id") というキーで、セッションにUserクラスのIDを設定します。その後、一覧画面に遷移します。

juma.user.manipulators.LoginManipulatorクラスは、user/manipulators.pyに記述します。

class LoginManipulator(forms.Manipulator):
    def __init__(self):
        self.fields = (
            forms.TextField(field_name="account", length=16, is_required=True), 
            forms.PasswordField(field_name="password", length=16, is_required=True), 
        )

アカウント名を入力する欄と、パスワードを入力する欄があるだけです。余計な情報を与えないために、maxlength属性もヴァリデータもつけません。

残りは、テンプレートです。template/user/login.htmlにしました。

<h1>ログイン</h1>

<form action="" method="POST">
    <div class="login">
    <p>
        <table>
            <tr>
                <td>アカウント名</td>
                <td>
                    {{ form.account }}
                    {% for error in form.account.errors %}
                        &nbsp;{{ error|escape }}
                    {% endfor %}
                </td>
            </tr>
            <tr>
                <td>パスワード</td>
                <td>
                    {{ form.password }}
                    {% for error in form.password.errors %}
                        &nbsp;{{ error|escape }}
                    {% endfor %}
                </td>
            </tr>
        </table>
    </p>
    <p>
        <center><input type="submit" value="ログインする"/></center>
    </p>
    </div>
</form>
<!--
    vim: tabstop=4 shiftwidth=4 expandtab
-->

以上で、ログインの実装は完了です。/user/loginをブラウザで開くと、ログインが面画が表示されます。

なお、ログインすると、データベースのdjango_sessionテーブルにセッションの情報が保存されます。

$ sqlite3 data.sqlite                          [~/projects/juma]
SQLite version 3.3.5
Enter ".help" for instructions
sqlite> select * from django_session;
91a18ca60354426aa3cac4f329b2b51f|KGRwMQpTJ191c2VyX2lkJwpwMgpJMQpzLmE2ZTA2MGI2MDBlOTQ3ZGUzMGUxYjdhNTgyYmFiMTU5
2007-02-09 08:37:45.764221
ログアウト
残りの機能はログアウトです。/user/logoutにアクセスしたら、ログアウトするようにします。user/urls.pyは、以下のようになります。
from django.conf.urls.defaults import *
from juma.user.models import User

urlpatterns = patterns('',
    (r'^add/$', 'juma.user.views.add'), 
    (r'^list/$', 'django.views.generic.list_detail.object_list', { 'queryset': User.objects.all(), 'allow_empty': True, 'paginate_by': 20 }), 
    (r'^media/image/(?P<file_name>[-\w\d_\.]+)$', 'juma.user.views.image'), 
    (r'^login/$', 'juma.user.views.login'), 
    (r'^logout/$', 'juma.user.views.logout'), 
)

# vim: tabstop=4 shiftwidth=4 expandtab
ビューは、以下の通りです。
def logout(request):
    try:
        del request.session[USER_ID_KEY]
    except KeyError:
        pass
    return _redirect_list()
ログアウトには画面がないので、マニピュレータもテンプレートもありません。これで以上です。/user/logout/にアクセスすると、ログアウトし、会員一覧画面に戻ります。django_sessionテーブルから、レコードも削除されます。
$ sqlite3 data.sqlite                          [~/projects/juma]
SQLite version 3.3.5
Enter ".help" for instructions
sqlite> select * from django_session;