[Django] 日本ひげ男協会のサイトを作成する。その7
注意
この記事は、id:SumiTomohiko:20070126:1169833683の続きです。
編集画面
今回は編集画面を作成します。会員登録画面と似ているので楽勝だろうと思いきや、問題がひとつありました。それは、画像の扱いについてです。編集画面には、アップロードするファイルを指定するテキストボックスがあります。編集画面を開いた直後は、ここは空欄になっています(アップロードしたファイルのローカルのパスをサーバで覚えておくわけはないので、当り前です)。このままの状態でサーバに送信すると、ファイルがないものとみなされ、既存のファイルが削除されてしまいます。削除されないようにするには、また同じファイルをアップロードしなければなりませんが、これでは不便です。そこで今回は、「画像を削除する」チェックボックスを設けることにしました。ファイル名の欄が空白のときは既存の画像をそのまま使い続け、「画像を削除する」チェックボックスがチェックされたときだけ、画像を削除することにします。これら、ファイル名欄と「画像を削除する」チェックボックスによる動作をデシジョンテーブルで表すと、以下のようになります。
既存の画像の有無 | ファイル名 | チェックボックス | 動作 |
---|---|---|---|
なし | なし | チェックしない | なにもしない |
なし | なし | チェックする | なにもしない |
なし | あり | チェックしない | アップロードされた画像を設定する |
なし | あり | チェックする | エラーとする |
あり | なし | チェックしない | 既存の画像のままとする |
あり | なし | チェックする | 既存の画像を削除する |
あり | あり | チェックしない | アップロードされた画像で置き換える |
あり | あり | チェックする | エラーとする |
さてこのようにすると、Userモデルの項目と画面の項目が一対一で対応しなくなるので、以前 (id:SumiTomohiko:20070126:1169768661) と同じように、マニピュレータを作成する必要があります。ここでは、会員登録画面との共通性を利用して、継承して定義します。マニピュレータを記述するファイルは、user/manipulators.pyとしていますが、以下のようになります。
class UserManipulator(forms.Manipulator): def __init__(self): self.fields = [ forms.TextField(field_name="account", length=16, maxlength=32, is_required=True, validator_list=[validators.isAlphaNumeric]), forms.TextField(field_name="name", length=16, maxlength=32, is_required=True), forms.PasswordField(field_name="password", length=16, maxlength=32, is_required=True, validator_list=[password_length_validator, password_validator]), forms.PasswordField(field_name="password2", length=16, maxlength=32, is_required=True, validator_list=[password_length_validator]), forms.PositiveSmallIntegerField(field_name="from_year", maxlength=4, is_required=True, validator_list=[from_year_validator]), forms.ImageUploadField(field_name="image_file"), forms.LargeTextField(field_name="introduction", rows=5, maxlength=8192), forms.URLField(field_name="web", maxlength=256), forms.URLField(field_name="blog", maxlength=256), ] class UserAddManipulator(UserManipulator): pass class UserEditManipulator(UserManipulator): def __init__(self): super(UserEditManipulator, self).__init__() self.fields.extend([ forms.HiddenField(field_name="id"), forms.CheckboxField(field_name="delete_image", validator_list=[delete_image_validator])])
UserEditManipulatorのdelete_imageフィールドのヴァリデータに設定されているdelete_image_validatorは、ファイルをアップロードしながら削除も指示していないか検証するためのヴァリデータです。user/validators.pyで、以下のように定義されています。
class DeleteImageValidator(object): def __init__(self, message="画像をアップロードするか、削除するか、どちらかにしてください。"): self._message = message def __call__(self, field_data, all_data): if (all_data.has_key("image_file")) and (field_data == "on"): raise ValidationError, self._message
ビューは、HTTPのメソッドがPOSTならデータベースを更新し、そうでなければ新規に画面を描画するという、いつものパターンで作成します。会員登録画面との共通点を関数化しつつ、以下のようにしました。
def _save_user(user, new_data, manipulator, errors): manipulator.do_html2python(new_data) user.account = new_data["account"] user.name = new_data["name"] user.password = new_data["password"] user.from_year = new_data["from_year"] user.introduction = new_data["introduction"] user.web = new_data["web"] user.blog = new_data["blog"] image_file = new_data["image_file"] if image_file: filename = image_file["filename"] user.image = filename user.save_image_file(filename, image_file["content"]) elif new_data.has_key("delete_image") and new_data["delete_image"]: filename = user.get_image_filename() if os.path.exists(filename): os.remove(filename) user.image = "" try: user.save() except StandardError, e: if str(e) != "column account is not unique": raise _add_error(errors, "account", "このアカウント名はすでに使用されています。") def edit(request, id): manipulator = manipulators.UserEditManipulator() if request.POST: new_data = request.POST.copy() new_data.update(request.FILES) errors = manipulator.get_validation_errors(new_data) if not errors: u = User.objects.get(id=id) _save_user(u, new_data, manipulator, errors) if not errors: return _redirect_to_list() else: u = User.objects.get(id=id) else: errors = {} u = User.objects.get(id=id) new_data = dict(id=id, account=u.account, name=u.name, password=u.password, password2=u.password, from_year=u.from_year, introduction=u.introduction, web=u.web, blog=u.blog) form = forms.FormWrapper(manipulator, new_data, errors) return render_to_response('user/user_edit.html', { "form": form, "location": juma.settings.LOCATION, "image_url": u.get_image_url() }) def add(request): manipulator = manipulators.UserAddManipulator() if request.POST: new_data = request.POST.copy() new_data.update(request.FILES) errors = manipulator.get_validation_errors(new_data) if not errors: u = User() _save_user(u, new_data, manipulator, errors) if not errors: request.session[juma.user.settings.USER_ID_KEY] = u.id return _redirect_to_list() else: errors = new_data = {} form = forms.FormWrapper(manipulator, new_data, errors) return render_to_response('user/user_add.html', { "form": form })
この後テンプレートを作成したのですが、面白くともなんともないので、省略します。
あとは、user/urls.pyにURLを追加するだけです。以下の1行を追加します。
(r'^edit/(?P<id>[\d]+)/$', 'juma.user.views.edit'),
これで編集画面ができました。編集画面では、現在の画像も表示できるようになっています。
なお、エラーで画面が再表示されたとき、ファイル名が空白になってしまうのは、仕様です。仮りに<input type="file" value="/foo/bar">と書いても、ファイル名には何も表示されません。この点に関しては、利用する方々に御不便をおかけします。