1. fantm21
Changes in body
Source | HTML | Preview
@@ -1,303 +1,303 @@
子モデルのCRUD
-------------
### 子の読み方
親の書籍に対して、子の感想の一覧を表示してみます。
`cms/models.py` で、感想は書籍を外部キーとして持つよう定義しました。
```python
class Impression(models.Model):
'''感想'''
book = models.ForeignKey(Book, verbose_name=u'書籍', related_name='impressions')
comment = models.TextField(u'コメント', blank=True)
```
よって、書籍に紐づく子供の感想は、related_nameを使って、以下のように読みだすことができます。
ここでも SQL は書きません。
```python
impressions = book.impressions.all().order_by('id') # 書籍の子供の、感想を読む
```
### 子の一覧のビュー
今回は、`汎用ビュー` の ListView を使って書いてみます。
これを使うと、ページングなどが簡単に実現できます。
`cms/views.py` に以下の記述を追加します。
```python
from django.views.generic.list import ListView
:
class ImpressionList(ListView):
'''感想の一覧'''
context_object_name='impressions'
template_name='cms/impression_list.html'
paginate_by = 2 # 1ページは最大2件ずつでページングする
def get(self, request, *args, **kwargs):
book = get_object_or_404(Book, pk=kwargs['book_id']) # 親の書籍を読む
impressions = book.impressions.all().order_by('id') # 書籍の子供の、感想を読む
self.object_list = impressions
context = self.get_context_data(object_list=self.object_list, book=book)
return self.render_to_response(context)
```
### 子の一覧のテンプレート
BootStrapを応用して、
* ページングありの時、「前へ、1、2、次へ」などのページ番号を表すリンクを置く。
* 削除する時、いきなり消すのではなく、確認のモーダルダイアログを出す。
ということをしています。ちょっと長いです。
-`mybook/templates/base_html` を継承して `mybook/cms/templates/cms/impression_list.html` を作成します。
+`mybook/cms/templates/base.html` を継承して `mybook/cms/templates/cms/impression_list.html` を作成します。
```
{% extends "base.html" %}
{% block title %}感想の一覧{% endblock title %}
{% block extrahead %}
<script>
$(function() {
$('.del_confirm').on('click', function () {
$("#del_pk").text($(this).attr("pk"));
$('#del_url').attr('href', $(this).attr("url"));
});
});
</script>
<style>
table {
margin-top: 8px;
}
</style>
{% endblock %}
{% block content %}
<h3 class="page-header">感想の一覧 <small>{{ book.name }}</small></h3>
<a href="{% url 'cms:impression_add' book_id=book.id %}" class="btn btn-default btn-sm">追加</a>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>コメント</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for impression in impressions %}
<tr>
<td>{{ impression.id }}</td>
<td>{{ impression.comment|linebreaksbr }}</td>
<td>
<a href="{% url 'cms:impression_mod' book_id=book.id impression_id=impression.id %}" class="btn btn-default btn-sm">修正</a>
<button class="btn btn-default btn-sm del_confirm" data-toggle="modal" data-target="#deleteModal" pk="{{ impression.id }}" url="{% url 'cms:impression_del' book_id=book.id impression_id=impression.id %}">削除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li>
{% else %}
<li class="disabled"><a href="#">&laquo;</a></li>
{% endif %}
{% for linkpage in page_obj.paginator.page_range %}
{% ifequal linkpage page_obj.number %}
<li class="active"><a href="#">{{ linkpage }}</a></li>
{% else %}
<li><a href="?page={{ linkpage }}">{{ linkpage }}</a></li>
{% endifequal %}
{% endfor %}
{% if page_obj.has_next %}
<li><a href="?page={{ page_obj.next_page_number }}">&raquo;</a></li>
{% else %}
<li class="disabled"><a href="#">&raquo;</a></li>
{% endif %}
</ul>
{% endif %}
<div>
<a href="{% url 'cms:book_list' %}" class="btn btn-default btn-sm">戻る</a>
</div>
{# 削除を確認するモーダル ダイアログ #}
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="deleteModalLabel">確認</h4>
</div>
<div class="modal-body">
<p>ID: <span id="del_pk"></span> を削除しますか?</p>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-primary" id="del_url">OK</a>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
{% endblock content %}
```
出来上がる一覧ページは、以下のようなものとなります。
![cms03.jpg](https://qiita-image-store.s3.amazonaws.com/0/48136/715a9833-84ca-2184-7687-2fd4f83c6dcd.jpeg "cms03.jpg")
### 子の追加、修正のフォーム
`cms/forms.py` に以下のように追加します。
ここでは、`cms/models.py` の Impression モデルを追加、修正するための元となるフォームを作成します。
```python
:
from cms.models import Book, Impression
:
class ImpressionForm(ModelForm):
'''感想のフォーム'''
class Meta:
model = Impression
fields = ('comment', )
```
### 子の追加、修正のビュー
`cms/views.py` に以下のように追加します。
```python
:
from cms.forms import BookForm, ImpressionForm
from cms.models import Book, Impression
:
def impression_edit(request, book_id, impression_id=None):
'''感想の編集'''
book = get_object_or_404(Book, pk=book_id) # 親の書籍を読む
if impression_id: # impression_id が指定されている (修正時)
impression = get_object_or_404(Impression, pk=impression_id)
else: # impression_id が指定されていない (追加時)
impression = Impression()
if request.method == 'POST':
form = ImpressionForm(request.POST, instance=impression) # POST された request データからフォームを作成
if form.is_valid(): # フォームのバリデーション
impression = form.save(commit=False)
impression.book = book # この感想の、親の書籍をセット
impression.save()
return redirect('cms:impression_list', book_id=book_id)
else: # GET の時
form = ImpressionForm(instance=impression) # impression インスタンスからフォームを作成
return render_to_response('cms/impression_edit.html',
dict(form=form, book_id=book_id, impression_id=impression_id),
context_instance=RequestContext(request))
```
### 子の追加、修正のテンプレート
`mybook/templates/base_html` を継承して `mybook/cms/templates/cms/impression_edit.html` を作成します。
```
{% extends "base.html" %}
{% load bootstrap %}
{% block title %}感想の編集{% endblock title %}
{% block content %}
<h3 class="page-header">感想の編集</h3>
{% if impression_id %}
<form action="{% url 'cms:impression_mod' book_id=book_id impression_id=impression_id %}" method="post" class="form-horizontal" role="form">
{% else %}
<form action="{% url 'cms:impression_add' book_id=book_id %}" method="post" class="form-horizontal" role="form">
{% endif %}
{% csrf_token %}
{{ form|bootstrap_horizontal }}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">送信</button>
</div>
</div>
</form>
<a href="{% url 'cms:impression_list' book_id=book_id %}" class="btn btn-default btn-sm">戻る</a>
{% endblock content %}
```
追加、修正のページは、以下のようになります。
![cms04.jpg](https://qiita-image-store.s3.amazonaws.com/0/48136/40376edb-2c27-444d-833c-eb210becc47b.jpeg "cms04.jpg")
### 子の削除のビュー
`cms/views.py` に以下のように追加します。
今回は、いきなり消さずに、Bootstrap の モーダルダイアログを出して、確認メッセージを出しています。
ただ、ビューの中身は、親の書籍の時と変わりません。
```python
def impression_del(request, book_id, impression_id):
'''感想の削除'''
impression = get_object_or_404(Impression, pk=impression_id)
impression.delete()
return redirect('cms:impression_list', book_id=book_id)
```
![cms05.jpg](https://qiita-image-store.s3.amazonaws.com/0/48136/afdedc17-7b38-3afc-abd6-ed73b9b63438.jpeg "cms05.jpg")
### 子のURLスキーム
`cms/urls.py` に以下のように追加します。
```python
urlpatterns = patterns('',
:
# 感想
url(r'^impression/(?P<book_id>\d+)/$', views.ImpressionList.as_view(), name='impression_list'), # 一覧
url(r'^impression/add/(?P<book_id>\d+)/$', views.impression_edit, name='impression_add'), # 登録
url(r'^impression/mod/(?P<book_id>\d+)/(?P<impression_id>\d+)/$', views.impression_edit, name='impression_mod'), # 修正
url(r'^impression/del/(?P<book_id>\d+)/(?P<impression_id>\d+)/$', views.impression_del, name='impression_del'), # 削除
)
```
### 親の一覧の修正
親の書籍の一覧から、該当する書籍の「感想一覧」が出せるよう、リンクを追加します。
`mybook/cms/templates/cms/book_list.html` に1行追加します。
```
<td>
<a href="{% url 'cms:book_mod' book_id=book.id %}" class="btn btn-default btn-sm">修正</a>
<a href="{% url 'cms:book_del' book_id=book.id %}" class="btn btn-default btn-sm">削除</a>
<a href="{% url 'cms:impression_list' book_id=book.id %}" class="btn btn-default btn-sm btn-primary">感想の一覧</a>
</td>
```
![cms06.jpg](https://qiita-image-store.s3.amazonaws.com/0/48136/36e5eefd-01e8-08ee-0b84-848b1e5ddb82.jpeg "cms06.jpg")
それでは、ローカルサーバを起動し、「書籍の一覧」から「感想の一覧」をたどって、感想の登録/修正/削除を行ってみましょう。
```bash
http://127.0.0.1:8000/cms/book/
```
ここまでの説明のように親子関係のあるモデルのCRUDができれば、後はモデルをどう設計するかということなので、応用して色々なものが作れると思います。
[Python Django入門 (6)](http://qiita.com/kaki_k/items/b76acaeab8a9d935c35c) に続きます。