2
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Djangoで更新可能な一覧画面を作成する

Last updated at Posted at 2021-01-29

自治会の運営を楽にしたいと思って自治会ネットというWEBサービスを立ち上げました。
環境はAWS + Python(Django)です。
構築にあたって、偉大なる先人方の記事に助けられたので微力ながら自分も誰かの助けになれたらと思い、記事を書いてみることにしました。

やりたいこと

ユーザーの一覧を表示しつつ一部の情報だけを変更できるような画面を作りたい。
スクリーンショット 2021-01-29 9.36.01.png

djangoはデフォルトで用意されている管理画面でもそうですが、一覧画面→詳細画面に遷移して更新するのが基本です。
でもたくさんのデータを1つ1つ開いて更新していくのは面倒なので、権限とかの更新は一覧画面で一度に行えるようにしたい。
ニーズはありそうなのですが、意外と情報が少なかったので書いてみました。

実現方法

django-extra-viewsというライブラリを使います。
今回使ったのはこの中のModelFormSetView

手順

djangoの基本的な使い方については他の方が記事を書いてくださってますので省略します。

ライブラリのインストール

pip install django-extra-views

APPの追加

INSTALLED_APPS = [
    ...
    'extra_views',
    ...
]

models.py

from django.db import models

class Group(models.Model):
    code = models.CharField(max_length=15, primary_key=True)
    name = models.CharField('グループ名', max_length=30)
    
    def __str__(self):
        return self.code

class Member(models.Model):

    full_name = models.CharField('名前', max_length=150)
    group = models.ForeignKey(to=Group, on_delete=models.CASCADE)
    auth = models.BooleanField(
        '権限A',
        default=False,
    )

forms.py

from django import forms
from django.db import models
from django.forms import ModelForm

from .models import Member, Group

class MemberUpdateForm(ModelForm):
    class Meta:
        model = Member
        fields = ( 'full_name', 'group', 'auth')   

urls.py

from sample import views as sample
urlpatterns = [
    path('update_sample', sample.ListUpdateView.as_view(), name='update_sample'),
]

view.py

from extra_views import ModelFormSetView
from .models import Member
from .forms import MemberUpdateForm

class ListUpdateView(ModelFormSetView):   
    model = Member
    template_name = 'update_sample.html'
    form_class = MemberUpdateForm

update_sample.html

<form method="post">
  {{ formset }}
  <div>
    <button class="btn btn-warning" type="submit">変更</button>
  </div>
</form>

ここまでの結果

スクリーンショット 2021-01-29 10.40.57.png

とりあえず更新可能な一覧が表示されました。
最後の一行は新規行を追加するためのものです。
しかし、このままでは見た目も悪いし名前も編集できてしまうので修正していきます。
まずは{{ formset }}を展開します。

update_sample.html

<form id="members_form" method="post">
  {% csrf_token %}
  {{ formset.management_form }}
  <table class="table">
    <thead>
      <tr>
        <th scope="col">ID</th>
        <th scope="col">名前</th>
        <th scope="col">グループ</th>
        <th scope="col">権限A</th>
      </tr>
    </thead>
    <tbody>
      {% for form in formset %}
      <tr>
        <td>{{ form.id }}</td>
        <td>{{ form.full_name }}</td>
        <td>{{ form.group }}</td>            
        <td>{{ form.auth }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
  <div>
    <button class="btn btn-warning" type="submit">変更</button>
  </div>
</form>

formsetには「management_form」という、formのトータル行数や元の行数を持ってる要素が含まれているので、formsetを展開する場合には{{ formset.management_form }}を記述する必要があります。

management_form の中身

<input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS">
<input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS">
<input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">

また、formset = 複数formをまとめたものなので{% for form in formset %}で
formを一行ずつ取り出して表示してあげます。
bootstrapのtableにセットしてあげると簡単に見やすくできます。

ID(PK)はhidden項目ですが、書かないとformのvalidationでエラーになるので更新できません。
該当の列をdisplay: noneにするなどして見えなくしておきましょう。

ここまでの結果

スクリーンショット 2021-01-29 11.01.14.png
だいぶ見た目がマシになりました。
次に名前を編集不可にするため、readonly disabled(disabledだと更新できないです)属性をつけてcssでテキストボックスの見た目を消します。
(注)disableにしても開発者ツールとか使えば編集できてしまうので完全に禁止できるわけではないです。
何か良い方法ご存知の方がいれば教えてください。

属性の追加はこちらの記事参照

update_sample.html

{% load widget_tweaks %}                      #追加
<form id="members_form" method="post">
  {% csrf_token %}
  {{ formset.management_form }}
  <table class="table">
    <thead>
      <tr>
        <th class="hidden" scope="col">ID</th> #変更
        <th scope="col">名前</th>
        <th scope="col">グループ</th>
        <th scope="col">権限A</th>
      </tr>
    </thead>
    <tbody>
      {% for form in formset %}
      {% if not forloop.last %} #追加
      <tr>
        <td class="hidden">{{ form.id }}</td> #変更
        <td>{{ form.full_name }}</td>
        <td>{{ form.group }}</td>            
        <td>{{ form.auth }}</td>
      </tr>
      {% endif %} #追加
      {% endfor %}
    </tbody>
  </table>
  <div>
    <button class="btn btn-warning" type="submit">変更</button>
  </div>
</form>

css

input {
  border: 0;
  color: #000000;
  background-color: rgba(255,255,255,255);
}
.hidden {
  display: none;
}

最後に、グループの選択肢をコードではなく名前にします。

forms.py

from django import forms
from django.db import models
from django.forms import ModelForm
from .models import Member, Group

# nameをlabelにするModelChoiceField
class ModelNameChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return "%s" % obj.name

class MemberUpdateForm(ModelForm):
    class Meta:
        model = Member
        fields = ( 'full_name', 'group', 'auth')   

    group = ModelNameChoiceField(Group.objects)

完成!

スクリーンショット 2021-01-29 11.48.11.png

所感

初めて記事を書きましたが、間違ってないか実際に動かしながら確認するので時間もかかるし大変ですね。
先人の方々に改めて感謝です。
今後はAWSでの環境構築などの記事を書いていけたらと思います。

参考 

Python + Django で、1対多モデルの登録画面を作成する

2
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?