Help us understand the problem. What is going on with this article?

Python Django入門 (5)

子モデルのCRUD

子の読み方

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

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

class Impression(models.Model):
    """感想"""
    book = models.ForeignKey(Book, verbose_name='書籍', related_name='impressions', on_delete=models.CASCADE)
    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/cms/base_html を継承して mybook/cms/templates/cms/impression_list.html を作成します。

{% extends "cms/base.html" %}

{% block title %}感想の一覧{% endblock title %}

{% block content %}
    <h4 class="mt-4 border-bottom">感想の一覧 <small class="text-muted ml-3">{{ book.name }}</small></h4>
    <a href="{% url 'cms:impression_add' book_id=book.id %}" class="btn btn-primary btn-sm my-3">追加</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-outline-primary btn-sm">修正</a>
            <button class="btn btn-outline-danger btn-sm del_confirm" data-toggle="modal" data-target="#deleteModal" data-pk="{{ impression.id }}" data-url="{% url 'cms:impression_del' book_id=book.id impression_id=impression.id %}">削除</button>
          </td>
        </tr>
        {% endfor %}
      </tbody>
    </table>

    {% if is_paginated %}
    <nav aria-label="Page navigation">
      <ul class="pagination">
        {% if page_obj.has_previous %}
          <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous"><span aria-hidden="true">&laquo;</span><span class="sr-only">Previous</span></a></li>
        {% else %}
          <li class="page-item disabled"><a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span><span class="sr-only">Previous</span></a></li>
        {% endif %}
        {% for linkpage in page_obj.paginator.page_range %}
          {% ifequal linkpage page_obj.number %}
            <li class="page-item active"><a class="page-link" href="#">{{ linkpage }}</a></li>
          {% else %}
            <li class="page-item"><a class="page-link" href="?page={{ linkpage }}">{{ linkpage }}</a></li>
        {% endifequal %}
        {% endfor %}
        {% if page_obj.has_next %}
          <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next"><span aria-hidden="true">&raquo;</span><span class="sr-only">Next</span></a></li>
        {% else %}
          <li class="page-item disabled"><a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span><span class="sr-only">Next</span></a></li>
        {% endif %}
      </ul>
    </nav>
    {% endif %}

    <a href="{% url 'cms:book_list' %}" class="btn btn-secondary btn-sm">戻る</a>

    {# 削除を確認するモーダル ダイアログ #}
    <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="deleteModalLabel">確認</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          </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-secondary" data-dismiss="modal">Cancel</button>
          </div>
        </div>
      </div>
    </div>
{% endblock content %}

{% block extra_js %}
<script>
$(function() {
  $('.del_confirm').on('click', function () {
     $("#del_pk").text($(this).data("pk"));
     $('#del_url').attr('href', $(this).data("url"));
  });
});
</script>
{% endblock %}

出来上がる一覧ページは、以下のようなものとなります。(まだ、この先のことをしないと動かないので、こういうイメージになると考えて下さい)

django10.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 "cms/base.html" %}
{% load bootstrap4 %}

{% block title %}感想の編集{% endblock title %}

{% block content %}
    <h4 class="mt-4 mb-5 border-bottom">感想の編集</h4>
    {% if impression_id %}
    <form action="{% url 'cms:impression_mod' book_id=book_id impression_id=impression_id %}" method="post">
    {% else %}
    <form action="{% url 'cms:impression_add' book_id=book_id %}" method="post">
    {% endif %}
      {% csrf_token %}
      {% bootstrap_form form layout='horizontal' %}
      <div class="form-group row">
        <div class="offset-md-3 col-md-9">
          <button type="submit" class="btn btn-primary">送信</button>
        </div>
      </div>
    </form>
    <a href="{% url 'cms:impression_list' book_id=book_id %}" class="btn btn-secondary btn-sm">戻る</a>
{% endblock content %}

追加、修正のページは、以下のようになります。(まだ、この先のことをしないと動かないので、こういうイメージになると考えて下さい)

django11.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)

django12.jpg

子のURLスキーム

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

urlpatterns = [
    :

    # 感想
    path('impression/<int:book_id>/', views.ImpressionList.as_view(), name='impression_list'),  # 一覧
    path('impression/add/<int:book_id>/', views.impression_edit, name='impression_add'),        # 登録
    path('impression/mod/<int:book_id>/<int:impression_id>/', views.impression_edit, name='impression_mod'),  # 修正
    path('impression/del/<int:book_id>/<int:impression_id>/', 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-outline-primary btn-sm">修正</a>
            <a href="{% url 'cms:book_del' book_id=book.id %}" class="btn btn-outline-danger btn-sm">削除</a>
            <a href="{% url 'cms:impression_list' book_id=book.id %}" class="btn btn-outline-info btn-sm">感想の一覧</a>
          </td>

django13.jpg

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

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

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

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

kaki_k
jsl
株式会社日本システム技研は、1976年に設立した長野に本社を構えるIT企業です。主にPython/Djangoの使用したWebアプリケーションの受託開発とプロダクト開発を行なっています。エンジニア向けコミュニティGEEKLAB.NAGANO( http://geeklab-nagano.com ) の運営をしています。
https://jsl.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした