自治会の運営を楽にしたいと思って自治会ネットというWEBサービスを立ち上げました。
環境はAWS + Python(Django)です。
構築にあたって、偉大なる先人方の記事に助けられたので微力ながら自分も誰かの助けになれたらと思い、記事を書いてみることにしました。
やりたいこと
ユーザーの一覧を表示しつつ一部の情報だけを変更できるような画面を作りたい。
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>
ここまでの結果
とりあえず更新可能な一覧が表示されました。
最後の一行は新規行を追加するためのものです。
しかし、このままでは見た目も悪いし名前も編集できてしまうので修正していきます。
まずは{{ 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にするなどして見えなくしておきましょう。
ここまでの結果
だいぶ見た目がマシになりました。
次に名前を編集不可にするため、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)
完成!
所感
初めて記事を書きましたが、間違ってないか実際に動かしながら確認するので時間もかかるし大変ですね。
先人の方々に改めて感謝です。
今後はAWSでの環境構築などの記事を書いていけたらと思います。