Djangoにて、「フォームで入力すればModelに保存される」という、Modelに紐付いたFormが必要になりました。
調べてみたところ、ModelFormを使えば良さそうでした。
Creating forms from models | Django documentation | Django
そこで、Class-based generic view(以下、CBV)でModelFormを使ってみました。
目次
- 環境
- CBV + ModelFormを使うための作業
- 準備
- プロジェクトにmyappアプリを追加
- Modelの作成
- ModelFormの作成
- Viewの作成
- HTMLテンプレートの作成
- ルーティング設定
- ソースコード
- 参考
環境
- Windows10
- Pytnon 3.4.3
- Django 1.9.1
CBV + ModelFormを使うための作業
作業として、
- データ保存のため、Modelを作成する
- FormのデータをModelに保存するため、ModelFormを作成する
- ModelFormをHTMLフォームに使うため、CBVのCreateViewやUpdateViewを作成する
- HTMLフォームを表示するため、テンプレートを作成する
- アプリのルーティングを設定する
が必要そうでした。
なお、今回はデータの参照・削除・リダイレクトのCBVも試してみようと、
- django.views.generic.DetailView
- django.views.generic.ListView
- django.views.generic.edit.CreateView
- django.views.generic.edit.UpdateView
- django.views.generic.edit.DeleteView
- django.views.generic.base.RedirectView
の計6個のCBVを使います。
準備
virtualenv環境の作成から、Djangoアプリの作成までを行います。
d:\Sandbox>mkdir Django_Class_based_view_Sample d:\Sandbox>cd Django_Class_based_view_Sample d:\Sandbox\Django_Class_based_view_Sample>virtualenv -p c:\python34\python.exe env d:\Sandbox\Django_Class_based_view_Sample>env\Scripts\activate (env) d:\Sandbox\Django_Class_based_view_Sample>pip install django (env) d:\Sandbox\Django_Class_based_view_Sample>django-admin startproject myproject . (env) d:\Sandbox\Django_Class_based_view_Sample>python manage.py startapp myapp
プロジェクトにmyapp
アプリを追加
settings.py
に追加します。
INSTALLED_APPS = [
...
'myapp',
]
Modelの作成
単純化するため、リレーションのないModelを用意しました。
from django.db import models class ItemModel(models.Model): name = models.CharField('Name', max_length=255, blank=True) unit_price = models.DecimalField('UnitPrice', max_digits=10, decimal_places=0)
ModelFormの作成
Modelを元にしたModelFormを作ります。
from django import forms from .models import ItemModel class ItemForm(forms.ModelForm): class Meta: model = ItemModel fields = '__all__'
なお、Modelのうち一部の項目のみ必要な場合は、fields = ['name', 'unit_price',]
のようにします。
また、Django1.8よりfields
またはexclude
が必須となったため、両方とも存在しない場合は以下のエラーが出ます。
Creating forms from models | Django documentation | Django
django.core.exceptions.ImproperlyConfigured: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form ItemForm needs updating.
Viewの作成
今回はすべてCBVで作成します。
CBVは継承関係が分かりづらいため、以下を参考にして継承関係を確認しながら実装を進めます。
Django Class-Based-View Inspector -- Classy CBV
更新系のCreateViewやUpdateView
ModelとModelFormを指定します。
class ItemCreate(CreateView): model = ItemModel form_class = ItemForm
参照系のDetailViewやListView、削除系のDeleteView
データソースとして今回はModelを指定します。
class ItemDetail(DetailView): model = ItemModel
リダイレクトのRedirectView
今回はリダイレクト時のステータスコードを301にしたいのですが、Django1.9よりpermanent = False
がデフォルトなので、このままではステータスコードが302となります。
Base views | Django documentation | Django
そのため、url
の他に、ステータスコードに関係するpermanent
も設定します。
class ItemRedirect(RedirectView): url = reverse_lazy('ns:item-list') # Django1.9より、permanent=Falseがデフォルトになった permanent = True
get_success_url()
について
CreateView, UpdateView, DeleteViewでは、処理成功時のリダイレクト先のURLをget_success_url()
で指定できます。
それに関係していくつか悩んだことがあったので、まとめておきます。
新規登録時に採番されたModelのIDを取得する方法について
CreateViewを使って新規登録が成功した後、UpdateViewやDetailViewなどのViewへリダイレクトする場合、新規登録したModelのIDが必要になります。
取得するには、get_success_url()
内で self.object.id
を使います。
create view - django createview how to get the object that is created - Stack Overflow
リダイレクトURLのハードコーディング回避について
URLが変わる可能性があるため、get_success_url()
ではURLのハードコーディングを回避したいと考えました。
そのため、以下を参考に、ほとんどのCBVはreverse()
、RedirectViewのみreverse_lazy()
を使うことにします。
- DjangoでのURL⇔view関数の正引き・逆引き - orangain flavor
- what is reverse() in Django - Stack Overflow
- python - Django RedirectView and reverse() doesn't work together? - Stack Overflow
- RedirectViewでリダイレクト先の指定にnamed urlを使う - 雑記
なお、urls.pyに記載されたURLのエントリを参照するため、メソッドの第一引数を <project.urlsのnamespace>:<app.urlsのname>
という形式で指定します。
また、DetailViewなど、ModelのIDがURLの一部に必要な場合は、メソッドの第二引数をargs=(self.object.id,)
とすることで、ModelのIDを渡すことができます。
HTMLテンプレートの作成
Viewで指定するHTMLテンプレートを作成します。
なお、テンプレートに関しても悩んだことをまとめておきます。
デフォルトのテンプレートファイル名について
template_name
などの明示的なテンプレート名指定が無い場合、<app_name>/<model_name>_form.html
などのデフォルトテンプレート名が使われます。
Form handling with class-based views | Django documentation | Django
ただ、settings.py
中のTEMPLATES
が
# デフォルトでは以下の設定がされているはず TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
の場合、path/to/project/<app_name>/templates/<app_name>
ディレクトリの中に、以下のテンプレートファイルを置く必要があります。
テンプレートファイル名 | テンプレートを使用するView |
---|---|
<model_name>_list.html |
ListView |
<model_name>_detail.html |
DetailView |
<model_name>_form.html |
CreateView, UpdateViewで共用 |
<model_name>_confirm_delete.html |
DeleteView |
なお、今回のModel名はItemModel
というPascalCaseですが、テンプレート名はitemmodel_list.html
などと、すべて小文字となります。
CSRF対策について
HTMLテンプレートのフォームに {% csrf_token %}
が無い場合、Submit時に以下のエラーがブラウザに表示されます。
Forbidden (403) CSRF verification failed. Request aborted.
原因は、CSRF対策の設定不足のためです。
- Cross Site Request Forgery protection | Django documentation | Django
- Django の csrf_token について - 混沌脳内
そのため、DjangoのHTMLフォームに {% csrf_token %}
を忘れずに記述します。
Modelのデータを参照する方法について
テンプレートからModelを参照するときのデフォルトオブジェクト名は以下の通りです。
テンプレートで使うオブジェクト名 | テンプレートが属するView |
---|---|
object | DetailView, DeleteView |
object_list | ListView |
form | UpdateView |
- 参考
なお、フォームでModelのフィールドを表示するオプションとして、form.as_table
やform.as_p
、form.as_ul
などが用意されています。
Working with forms | Django documentation | Django
テンプレートでURLをハードコーディングしない方法について
url <project.urlsのnamespace>:<app.urlsのname>
形式で指定します。
Using {% url ??? %} in django templates - Stack Overflow
なお、ModelのIDなどのパラメータが必要な場合には、以下のように、url <project.urlsのnamespace>:<app.urlsのname> <パラメータ>
という形式で指定します。
<a href="{% url 'ns:item-update' object.id %}">Go UpdateView</a>
ルーティング設定
project.urls
やapp.urls
にルーティング設定を記述します。
なお、URLにModelのIDを含む場合、urlpatterns
を
url(r'^(?P<pk>[0-9]+)/$', views.ItemDetail.as_view(), name='item-detail'),
とすることで、<pk>
にModelのIDを引き渡すことができます。
URL dispatcher | Django documentation | Django
そのため、プロジェクトのurls.pyは
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^mysite/', include('myapp.urls', namespace='ns')), ]
アプリのurls.pyは
urlpatterns = [ url(r'^$', views.ItemRedirect.as_view(), name='item-redirect'), url(r'^create/$', views.ItemCreate.as_view(), name='item-create'), url(r'^list/$', views.ItemList.as_view(), name='item-list'), url(r'^(?P<pk>[0-9]+)/$', views.ItemDetail.as_view(), name='item-detail'), url(r'^(?P<pk>[0-9]+)/update/$', views.ItemUpdate.as_view(), name='item-update'), url(r'^(?P<pk>[0-9]+)/delete/$', views.ItemDelete.as_view(), name='item-delete'), ]
となりました。
ちなみに、http://localhost:8000/mysite/
にアクセスした際 http://localhost:8000/mysite/list
へとリダイレクトする目的で、RedirectViewを使っています。
ソースコード
GitHubに上げておきました。
thinkAmi-sandbox/Django_Class_based_view_Sample
参考
公式ドキュメント
- Generic display views | Django documentation | Django
- Generic editing views | Django documentation | Django