Python Django入門 (5)

  • 73
    いいね
  • 16
    コメント
この記事は最終更新日から1年以上が経過しています。

子モデルのCRUD

子の読み方

親の書籍に対して、子の感想の一覧を表示してみます。

cms/models.py で、感想は書籍を外部キーとして持つよう定義しました。

class Impression(models.Model):
    """感想"""
    book = models.ForeignKey(Book, verbose_name='書籍', related_name='impressions')
    comment = models.TextField('コメント', blank=True)

よって、書籍に紐づく子供の感想は、related_nameを使って、以下のように読み出すことができます。

ここでも SQL は書きません。

impressions = book.impressions.all().order_by('id')   # 書籍の子供の、感想を読む

子の一覧のビュー

今回は、汎用ビュー の ListView を使って書いてみます。

これを使うと、ページングなどが簡単に実現できます。

cms/views.py に以下の記述を追加します。

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/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 %}">削除</button>
          </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

子の追加、修正のフォーム

cms/forms.py に以下のように追加します。

ここでは、cms/models.py の Impression モデルを追加、修正するための元となるフォームを作成します。

  :
from cms.models import Book, Impression
  :

class ImpressionForm(ModelForm):
    """感想のフォーム"""
    class Meta:
        model = Impression
        fields = ('comment', )

子の追加、修正のビュー

cms/views.py に以下のように追加します。

  :
from cms.models import Book, Impression
from cms.forms import BookForm, ImpressionForm
  :

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(request,
                  'cms/impression_edit.html',
                  dict(form=form, book_id=book_id, impression_id=impression_id))

子の追加、修正のテンプレート

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

子の削除のビュー

cms/views.py に以下のように追加します。

今回は、いきなり消さずに、Bootstrap の モーダルダイアログを出して、確認メッセージを出しています。
ただ、ビューの中身は、親の書籍の時と変わりません。

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

子のURLスキーム

cms/urls.py に以下のように追加します。

urlpatterns = [
    :

    # 感想
    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

それでは、ローカルサーバを起動し、「書籍の一覧」から「感想の一覧」をたどって、感想の登録/修正/削除を行ってみましょう。

http://127.0.0.1:8000/cms/book/

ここまでの説明のように親子関係のあるモデルのCRUDができれば、後はモデルをどう設計するかということなので、応用して色々なものが作れると思います。

Python Django入門 (6) に続きます。