知識の枝

"All is well"

Django ManyToManyをテンプレートで表示

約273日前 2021年4月26日0:32
デジタル
Django

改訂履歴


2021/4/25 投稿

1. 背景


Djangoを使った開発中に覚えたことを備忘録として残します。

今回はManyToManyで定義された要素をhtmlテンプレートで表示する方法を解説します。


2. ゴール


ManyToManyフィールドを持つモデルをテンプレートに渡し、引き当てられた要素を全て表示する。


3. はじめに


まず、ManyToManyとは何か。

models.pyで作成したモデル「Task」を考えます。
Taskモデルはその名の通りタスクを管理するToDoアプリ用のモデルです。

あなたはあるチームに属しており、あるプロジェクトのタスクを1つ任されたとします。
この場合、そのタスクに割り当てられた担当者はあなた1人ですので、ForeignKeyで1人選択すれば問題ありません。

では仮に、タスクに割り当てられた担当が複数人いたとします。
この場合は、先ほどのForeignKeyフィールドでは1人しか選択できないので使うことができません。

複数の割り当てを行いたいとき、そんなときに使えるのがManyToManyフィールドです。


割り当てる人を管理するモデルを「Person」とします。
models.pyは下記のように書くことができます。
models.py
class Person(models.Model):
"""Person detail"""
name = models.CharField(verbose_name='名前', max_length=30, blank=True, null=True)

def __str__(self):
return self.name

class Task(models.Model):
"""Task info"""
task = models.CharField(verbose_name='やること', max_length=255, blank=True, null=True)
priority = models.ForeignKey(
Priority, verbose_name='優先度', on_delete=models.PROTECT)
status = models.ForeignKey(
Status, verbose_name='状況', on_delete=models.PROTECT)
assign = models.ManyToManyField(
Person, verbose_name='担当') #ここが担当者の割り当て部分

def __str__(self):
return self.task
「assign」というフィールドに「Person」モデルからManyToManyで担当者を複数人選べるようにしています。

次項で、このTaskモデルのassignに設定された複数の担当者をhtmlテンプレートで表示する方法を解説します。


4. ManyToManyの表示


クラスベースビューでListViewを使う場合を考えます。
views.py
class TaskView(generic.ListView):
template_name = 'task_management.html'
model = Task
最低限の内容を書きました。

汎用ビューのListViewを継承し「TaskView」というビューを作りました。
タスクの一覧表示を行うページです。
class TaskView(generic.ListView):


使うhtmlテンプレートは「task_management.html」とします。
template_name = 'task_management.html'



一覧表示するモデルは「Task」です。
model = Task



Taskの中には下記4つのフィールドがあります。
・タスク内容(task)
・優先度(priority)
・状況(status)
・担当(assign)


それぞれの内容をhtmlテンプレートで表示する場合は下記のように書きます。
task_management.html
{% for task in task_list %}
<p>タスク内容:{{ task.task }}</p>
<p>優先度:{{ task.priority }}</p>
<p>状況:{{ task.status }}</p>
<p>担当:{{ task.assign }}</p>
{% endfor %}

実は、上記をそのまま使用すると「担当:」の部分だけhtmlで何も表示されません。

その他3つは1つのフィールドに1つのデータが入っているだけなので問題無いのですが、ManyToManyフィールドは複数のデータが入っている為、ただ単に「task.assign」とするだけでは表示できません。

task.assignには実際は複数のデータが割り当てられているのですが、print出力してみるとNoneが返ってきます。
print(task.assign)
>>> app名.Person.None #Noneが返ってくる


ちなみにtask.assignの型は
print(type(task.assign))
>>> <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
なんかめちゃ長い型でした。
「ManyRelatedManager」オブジェクトというらしいです。

このオブジェクトはイテラブルでは無いみたいなので、リストのようにfor文で回すことが出来ないみたいです。

イテラブルとは(引用)

イテラブルとは、一言で言うと「繰り返し可能なオブジェクト」のことです。

for文において、文字列や数字を繰り返すことが可能であり、「for i in A:のAの部分に用いることができるもの」といえます。

例えばリスト、タプルやrange関数で生成したオブジェクトなどがイテラブルに該当します。

https://techacademy.jp/ - イテラブルとは



「ManyRelatedManager」オブジェクトは「.all()」することでクエリセット型になります。

print(type(task.assign.all()))
>>> <class 'django.db.models.query.QuerySet'>
たしかにクエリセット型です。

クエリセット型であればイテラブルなのでfor文で取り出すことができます。


テンプレートでManyToManyを表示する方法は下記2通りが考えられます。

①views.pyでクエリセット型に変換してからhtmlテンプレートに渡す
②テンプレート内でクエリセット型に直す


②が簡単そうだったので解説します。
①は試していないので、もしかしたら上手くいかないかもしれません。


②テンプレート内でクエリセット型に直してfor文で取り出します。
html
{% for assign in task.assign.all %} #.allでクエリセット化します
{{ assign.name }} #.nameはPersonモデルのnameフィールドです
{% endfor %}
複数割り当てられた担当者をfor文で抜き出しています。
task.assign.allの中身にはPersonモデルが入っているので、担当者の名前を表示する場合は「assign.name」と書きます。

Personモデルのname
models.py
class Person(models.Model):
"""Person detail"""
name = models.CharField(verbose_name='名前', max_length=30, blank=True, null=True)


まとめて書くとこんな感じです。
task_management.html
{% for task in task_list %}
<p>タスク内容:{{ task.task }}</p>
<p>優先度:{{ task.priority }}</p>
<p>状況:{{ task.status }}</p>
<p>担当:
{% for assign in task.assign.all %}
{{ assign.name }}
{% endfor %}
</p>
{% endfor %}


これでManyToManyフィールドの中身が全て表示されます。


5. さいごに


ManyToManyフィールドをテンプレートで扱う方法は以上です。
腑に落ちてない箇所があるので、もう少し個人的に調べてみたいと思います。