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/