DjangoのmodelでN:Nの関係を作る方法をご紹介します。
「ManyToManyField」を使用します。
目次
条件
- Django 3.1.2
- Python 3.7.0
概要
ここでは、「アカウント:場所 = 1:N 」の関係から、「アカウント:場所 = N:N 」の関係にする手順を示します。
アカウント:場所 = 1:N の場合
models.py
models.pyを以下のように定義します。
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
class CustomUser(AbstractUser):
def __str__(self):
return self.username + ":" + self.email
class Location(models.Model):
"""場所モデル"""
class Meta:
db_table = 'location'
name = models.CharField(verbose_name='地名', max_length=255)
memo = models.CharField(verbose_name='メモ', max_length=255, default='', blank=True)
author = models.ForeignKey(
'nvn.CustomUser',
on_delete=models.CASCADE,
)
created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
def __str__(self):
return self.name + ', ' + self.memo + ', ' + self.author.username
@staticmethod
def get_absolute_url(self):
return reverse('locations:index')
urls.py
トップ画面に場所情報一覧を表示します。
from django.urls import path
from . import views
app_name = 'nvn'
urlpatterns = [
# トップ画面
path('', views.IndexView.as_view(), name='index'),
# 詳細画面
path('locations/<int:pk>/', views.DetailView.as_view(), name='detail'),
]
views.py
一覧画面では、対象アカウントに紐づく情報のみ表示するようにします。
import logging
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import generic
from .models import Location
logger = logging.getLogger('development')
# 一覧画面
class IndexView(LoginRequiredMixin, generic.ListView):
paginate_by = 5
template_name = 'nvn/index.html'
model = Location
def get_queryset(self):
current_user = self.request.user
if current_user.is_superuser: # スーパーユーザの場合、リストにすべてを表示する。
return Location.objects.all()
else: # 一般ユーザは自分のレコードのみ表示する。
return Location.objects.filter(author=current_user.id)
# 詳細画面
class DetailView(generic.DetailView):
model = Location
template_name = 'nvn/detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.request.session.modified = True # セッション更新
context['location_pk'] = self.kwargs['pk']
return context
テンプレート(index.html)
一覧画面では、ログインしたアカウント名と紐づく場所一覧を表示します。
{% extends 'base.html' %}
{% block content %}
{% if user.is_authenticated %}
<h1>ユーザ名</h1>
<p class="user-name">{{ user.username }} でログイン中です。</p>
{% endif %}
<h1>My Locations</h1>
<section class="">
<ul>
{% for location in object_list %}
<li>
<h2><a href="{% url 'nvn:detail' location.pk %}">{{ location.name }}</a></h2>
<p>{{ location.memo }}</p>
</li>
{% empty %}
<li class="no-post">
<p>No location yet.</p>
{% endfor %}
</ul>
</section>
<hr>
{% if is_paginated %}
<section class="pagination">
<ul>
<li>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"><< Prev</a>
{% else %}
<< Prev
{% endif %}
</li>
<li>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next >></a>
{% else %}
Next >>
{% endif %}
</li>
</ul>
</section>
{% endif %}
{% endblock %}
実行結果
DB情報
ユーザー名「test1」と「test2」を用意します。
場所情報を3つ用意し、それぞれ以下のようにアカウントに紐づけます。
- test1: 東京、北海道
- test2: 沖縄
画面の表示
各アカウントに紐づけた通りの表示となります。
アカウント:場所 = N:N の場合
models.py
models.pyにおいて、CustomUserとの紐づけを「ManyToManyField」に変更します。
「makemigrations」および「migrate」を実行して、DBに反映します。
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
class CustomUser(AbstractUser):
def __str__(self):
return self.username + ":" + self.email
class Location(models.Model):
"""場所モデル"""
class Meta:
db_table = 'location'
name = models.CharField(verbose_name='地名', max_length=255)
memo = models.CharField(verbose_name='メモ', max_length=255, default='', blank=True)
author = models.ManyToManyField(CustomUser)
created_at = models.DateTimeField(verbose_name='登録日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
def __str__(self):
return self.name + ', ' + self.memo + ', ' + str(list(self.author.values_list('username', flat=True)))
@staticmethod
def get_absolute_url(self):
return reverse('locations:index')
実行結果
DB情報
アカウントと場所の紐づけ方法を変更したため、DB上の紐づけはリセット(紐づけなし)されています。
N:Nの関係にしたことで、管理画面では以下のように、場所に対してAuthor(アカウント)を複数設定できるようになります。
今回は以下のように紐づけました。
- test1: 東京、北海道
- test2: 沖縄、北海道
画面の表示
各アカウントに紐づけた通りの表示となります。
参考
Django公式ドキュメント
https://docs.djangoproject.com/ja/3.1/topics/db/examples/many_to_many/

