ModelFormを使ってデータ編集機能実装

ほかにやっている人(id:perezvon:20080202:1201931223)がいましたが
Djangoの1.0でModelForm(モデルより生成されるフォーム)を使って、データ編集機能を実装してみました。

Modelの定義

from django.db import models
from django.contrib.auth.models import User

...

class Todo(models.Model):
	title = models.CharField(max_length=200)
	description = models.TextField(blank=True, max_length=4000)
	datetime_created = models.DateTimeField(auto_now_add=True)
	datetime_updated = models.DateTimeField(auto_now=True)
	is_finished = models.BooleanField(default=False)
	priority=models.ForeignKey(Priority)
	category=models.ForeignKey(Category)
	user = models.ForeignKey(User)
	def __unicode__(self):
		"""docstring for __unicode__"""
		return self.title

ModelForm

上記のモデルをもとに生成されるフォームの定義を行います。

from django import  forms
from djsite.todo.models import Category,Todo

....

class TodoEditForm(forms.ModelForm):
	search_title =  forms.CharField(widget=forms.HiddenInput,
			max_length=100, label=u'タイトル', required=False)
	search_category = forms.IntegerField(widget=forms.HiddenInput,
			required=False,label=u'カテゴリー')
	search_include_finished = forms.BooleanField(required=False,
            widget=forms.HiddenInput,label=u'完了分も含める')
	id = forms.IntegerField(widget=forms.HiddenInput,
			required=False,label=u'id')
	class Meta:
		model = Todo
		exclude = ('is_finished','user',)
    1. Mataという内部クラスを定義、そのmodel属性にモデルを指定するとそのモデルからのフィームインスタンスが生成されるようになります。
    2. id属性は、変更用にフォームページが開かれたときにモデルの主キーを格納するため定義しています。
    3. その他の「search_XXX」属性は、遷移もとページを再表示するためのパラメータを格納するために追加定義しました。今回の具体例としては、一覧の検索条件を格納するためのものです。

Viewの定義

以下は、編集操作のためのViewです。GETリクエストの場合は、フォームページの表示を行います。POSTの場合を更新処理を行い、一覧表示のページに戻るようにしています。

from django.http import HttpResponse,HttpResponseRedirect

from django.shortcuts import render_to_response

from djsite.todo.models import Todo
from djsite.todo.forms import TodoSearchForm, TodoEditForm

.....

def edit_todo(request):
	if request.POST:
		if request.POST.has_key('id') and  len(request.POST['id']):
			try:
				todo = Todo.objects.get(id=int(request.POST['id']))
			except Exception, e:
				render_to_response('error.html')
			form = TodoEditForm(request.POST, instance=todo)
		else:
			form = TodoEditForm(request.POST)
		if form.is_valid():
			user = request.user
			todo = form.save(commit=False)
			todo.user_id = user.id
			todo.save()
			# list へ戻る
			cleaned = form.clean()
			search_form = TodoSearchForm()
			args = []
			if cleaned['search_category'] :
				args.append("category=" + str(cleaned['search_category']))
			if cleaned['search_title']:
				args.append("title=" + str(cleaned['search_title']))
			if cleaned['search_include_finished']:
				args.append("include_finished=" + str(cleaned['search_include_finished']))
			response =  HttpResponseRedirect('list?' + '&'.join(args))
			return response
		else:
			print form.errors
			return render_to_response('edit_todo.html',
				dict(form=form, method="post"))
	else:
		form = TodoEditForm(request.GET)
		if request.GET.has_key('id'):
			try:
				todo = Todo.objects.get(id=int(request.GET['id']))
			except Exception, e:
				return render_to_response('error.html')
			form = TodoEditForm(instance=todo)
		return render_to_response('edit_todo.html',
			dict(form=form,method="get"))
    1. GETされたフォームページを表示するさいは、モデルインスタンスを指定してフォームインスタンスを生成しています。
    2. POSTされた内容を登録するさい、モデルフォームをリクエストパラメータのみを指定して生成したときは新規登録となり、instance引数としてモデルインスタンスも指定した場合は、既存行の変更登録となります。
    3. POST時の後半の処理は、遷移元である一覧ページの戻るための処理です。Hiddenフィールドに格納しておいたパターメータを含めての一覧ページへのリダイレクトを行っています。

テンプレート

以下は、編集ページのテンプレート(edit_todo.html)です。

<head>
</head>
<body>
	<form action="./edit" method="POST">
		<table>
	    <tr>
		<td>
	    <label class="fortextinput" for="id_title">
	      タイトル:
	    </label>
		</td>
		<td>
	    {{ form.title }}
	    {%if form.title.errors   %}
		{%ifequal method 'post' %}
	      <span style="color: red;">
	      {{ form.title.errors|join:", " }}
	      </span>
	    {% endifequal %}
	    {% endif %}
		</td>
	    </tr>
		<tr>
		<td>
	    <label class="fortextinput" for="id_description">
	      内容:
	    </label>
		<td>
	    {{ form.description }}
		{%ifequal method 'post' %}
	    {%if form.description.errors %}
	      <span style="color: red;">
	      {{ form.description.errors|join:", " }}
	      </span>
	    {% endif %}
	    {% endifequal %}
		</td>
	    </tr>

		<tr>
		<td>
	    <label class="fortextinput" for="id_description">
	      優先度:
	    </label>
		<td>
	    {{ form.priority }}
		{% ifequal method 'post' %}
	    {%if form.priority.errors %}
	      <span style="color: red;">
	      {{ form.priority.errors|join:", " }}
	      </span>
	    {% endif %}
	    {% endifequal %}
		</td>
	    </tr>


		<tr>
		<td>
	    <label class="fortextinput" for="id_description">
	      カテゴリー:
	    </label>
		<td>
	    {{ form.category }}
		{% ifequal method 'post' %}
	    {%if form.category.errors %}
	      <span style="color: red;">
	      {{ form.category.errors|join:", " }}
	      </span>
	    {% endif %}
	    {% endifequal %}
	
		</td>
	    </tr>
		
	    {{ form.id }}
	    {{ form.search_category }}
	    {{ form.search_title }}
	    {{ form.search_include_finished }}
		
	  </table>
	  	<input type="submit">
	
	</form>
</body>
</html>
    1. エラーを表示する条件として「{% ifequal method 'post' %}」というのを入れていますが、これは、GETによる初期表示時にエラーメッセージが表示されないようにするための条件です。「method」という変数は、view関数からこのテンプレートを表示するさいに渡しているものです(標準で定義されるわけではないです)。