ある学生が履修できる「講義」は多数あり,ある講義を履修する「学生」も多数いるということから,「学生」と「講義」の関係は「多対多」です.ここでは「学生」が「講義」を履修するという機能をDjangoで作成します.なお,履修した科目に「得点」情報を持たせたいような場合には,次のページのカスタム中間テーブルを利用する必要があります.
まず,トップページから学生一覧と講義一覧のページへのリンクを設置する.もちろんまだつながらないはずである.
university/templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>University</title>
</head>
<body>
<h1>University アプリケーション</h1>
<ul>
    <li>
        <a href="campus/">
            Campus 一覧
        </a>
    </li>
    <li>
        <a href="faculty/">
            Faculty 一覧
        </a>
    </li>
    <li>
        <a href="student/">
            Student 一覧
        </a>
    </li>
    <li>
        <a href="lecture/">
            Lecture 一覧
        </a>
    </li>
    <li>
        <a href="show_all/">
            すべての 一覧
        </a>
    </li>
</ul>
</body>
</html>
「Student」と「Lecture」という2つのモデルを作成する.このとき,30行目によって多対多のリレーションシップが自動的に設定される.つまり,中間テーブルが自動的に作成されることになる.なお,30行目の ManyToManyField は students のように複数形にすることが推奨されている.
university/models.py
from django.db import models
# Create your models here.
class Campus(models.Model):
    campus = models.CharField(max_length=20)
    def __str__(self):
      return self.campus
class Faculty(models.Model):
    faculty = models.CharField(max_length=20)
    established = models.IntegerField()
    campus = models.ForeignKey(Campus, on_delete=models.CASCADE)
    def __str__(self):
        return self.faculty
    class Meta:
        ordering = ['established']
class Student(models.Model):
    name = models.CharField(max_length=20)
    def __str__(self):
        return self.name
class Lecture(models.Model):
    name = models.CharField(max_length=20)
    students = models.ManyToManyField(Student)
    def __str__(self):
        return self.name
モデルを修正したので,マイグレーションを再度生成する必要がある.さらに,マイグレーションを実行してテーブルも生成する.
(py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py showmigrations ⏎ admin [X] 0001_initial [X] 0002_logentry_remove_auto_add [X] 0003_logentry_add_action_flag_choices auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length [X] 0010_alter_group_name_max_length [X] 0011_update_proxy_permissions [X] 0012_alter_user_first_name_max_length contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial university [X] 0001_initial (py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py makemigrations university ⏎ Migrations for 'university': university\migrations\0002_student_lecture.py - Create model Student - Create model Lecture (py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py showmigrations ⏎ admin [X] 0001_initial [X] 0002_logentry_remove_auto_add [X] 0003_logentry_add_action_flag_choices auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length [X] 0010_alter_group_name_max_length [X] 0011_update_proxy_permissions [X] 0012_alter_user_first_name_max_length contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial university [X] 0001_initial [ ] 0002_student_lecture (py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py migrate ⏎ Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, university Running migrations: Applying university.0002_student_lecture... OK (py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py showmigrations ⏎ admin [X] 0001_initial [X] 0002_logentry_remove_auto_add [X] 0003_logentry_add_action_flag_choices auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length [X] 0010_alter_group_name_max_length [X] 0011_update_proxy_permissions [X] 0012_alter_user_first_name_max_length contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial university [X] 0001_initial [X] 0002_student_lecture (py39) C:\Users\lecture\Documents\django\django_relationship>
生成されたテーブルを sqlite3 で確認する.このとき,university_student テーブル,university_lecture テーブルだけでなく,中間テーブルにあたる university_lecture_students テーブルも作成されていることに注意する.
(py39) C:\Users\lecture\Documents\django\django_relationship>sqlite3 db.sqlite3 ⏎ SQLite version 3.38.2 2022-03-26 13:51:10 Enter ".help" for usage hints. sqlite> .tables ⏎ auth_group django_migrations auth_group_permissions django_session auth_permission university_campus auth_user university_faculty auth_user_groups university_lecture auth_user_user_permissions university_lecture_students django_admin_log university_student django_content_type sqlite> .schema university_student ⏎ CREATE TABLE IF NOT EXISTS "university_student" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL ); sqlite> .schema university_lecture ⏎ CREATE TABLE IF NOT EXISTS "university_lecture" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL ); sqlite> .schema university_lecture_students ⏎ CREATE TABLE IF NOT EXISTS "university_lecture_students" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "lecture_id" bigint NOT NULL REFERENCES "university_lecture" ("id") DEFERRABLE INITIALLY DEFERRED, "student_id" bigint NOT NULL REFERENCES "university_student" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE UNIQUE INDEX "university_lecture_students_lecture_id_student_id_9a23d8d1_uniq" ON "university_lecture_students" ("lecture_id", "student_id"); CREATE INDEX "university_lecture_students_lecture_id_27153bef" ON "university_lecture_students" ("lecture_id"); CREATE INDEX "university_lecture_students_student_id_e7421ae5" ON "university_lecture_students" ("student_id"); sqlite> .exit ⏎ (py39) C:\Users\lecture\Documents\django\django_relationship>
Student と Lecture にテストデータを投入する.すでに作成されているはずの university/fixtures フォルダの中に2つの json ファイルを設置する.
university/fixtures/student-data.json
[
    {
        "model": "university.student",
        "fields": {
            "name": "井上"
        }
    },
    {
        "model": "university.student",
        "fields": {
            "name": "藤井"
        }
    },
    {
        "model": "university.student",
        "fields": {
            "name": "飯田"
        }
    },
    {
        "model": "university.student",
        "fields": {
            "name": "足立"
        }
    },
    {
        "model": "university.student",
        "fields": {
            "name": "武田"
        }
    }
]
university/fixtures/lecture-data.json
[
    {
        "model": "university.lecture",
        "fields": {
            "name": "経営戦略論"
        }
    },
    {
        "model": "university.lecture",
        "fields": {
            "name": "管理会計"
        }
    },
    {
        "model": "university.lecture",
        "fields": {
            "name": "情報ネットワーク論"
        }
    }
]
作成したテストデータをデータベースに投入する.なお,次のコマンドはWindows用です.Macの場合はフォルダの区切りを「\」ではなく「/」に変更してください.
(py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py loaddata university\fixtures\student-data.json ⏎ Installed 5 object(s) from 1 fixture(s) (py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py loaddata university\fixtures\lecture-data.json ⏎ Installed 3 object(s) from 1 fixture(s) (py39) C:\Users\lecture\Documents\django\django_relationship>
念の為,いま投入されたデータを sqlite3 で確認する.もちろん中間テーブルにはまだ登録されていない.
(py39) C:\Users\lecture\Documents\django\django_relationship>sqlite3 db.sqlite3 ⏎ SQLite version 3.38.2 2022-03-26 13:51:10 Enter ".help" for usage hints. sqlite> .tables ⏎ auth_group django_migrations auth_group_permissions django_session auth_permission university_campus auth_user university_faculty auth_user_groups university_lecture auth_user_user_permissions university_lecture_students django_admin_log university_student django_content_type sqlite> .headers ON ⏎ sqlite> select * from university_student; ⏎ id|name 1|井上 2|藤井 3|飯田 4|足立 5|武田 sqlite> select * from university_lecture; ⏎ id|name 1|経営戦略論 2|管理会計 3|情報ネットワーク論 sqlite> select * from university_lecture_students; ⏎ sqlite> .exit ⏎ (py39) C:\Users\lecture\Documents\django\django_relationship>
Studentの一覧をデータベースから取得して表示するコードを記述しよう.まず,university/views.py に student_index 関数を追加する.
university/views.py(抜粋)
from django.urls import reverse_lazy, reverse
from django.shortcuts import render, redirect
from django.shortcuts import get_object_or_404
from .forms import FacultyForm
from .models import Campus, Faculty, Student
# Create your views here.
(中略)
def faculty_update(request, faculty_id):
    if request.method == 'POST':
        form = FacultyForm(request.POST)
        if form.is_valid():
            faculty = get_object_or_404(Faculty, pk=faculty_id)
            faculty.faculty = form.cleaned_data.get("faculty")
            faculty.established = form.cleaned_data.get("established")
            faculty.campus_id = form.cleaned_data.get("campus")
            faculty.save()
            return redirect(reverse('university:faculty'))
        else:
            # エラーメッセージをつけて返す
            return render(request, 'faculty/form.html', {'form': form})
    else:
        context = {}
        faculty = get_object_or_404(Faculty, pk=faculty_id)
        context['form'] = FacultyForm(
                            initial={
                                'faculty' : faculty.faculty,
                                'established' : faculty.established,
                                'campus' : faculty.campus_id,
                            })
        return render(request, 'faculty/form.html', context)
def student_index(request):
    context = {}
    context['students'] = Student.objects.all()
    return render(request, 'student/index.html', context)
def show_all(request):
    context = {}
    context['campuses'] = Campus.objects.all()
    context['faculties'] = Faculty.objects.all()
    return render(request, 'show_all/index.html', context)
すでに作成されているはずの university/templates フォルダに,student フォルダを作成し,その中に index.html ファイルを設置する.
template/student/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student一覧</title>
</head>
<body>
{% block content %}
<h1>学生一覧</h1>
<ul>
    {% for student in students %}
        <li>
            {{ student.id }} : {{ student.name }}
        </li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
ルートを定義する.
university/urls.py
from django.urls import path
from . import views
app_name = 'university'
urlpatterns = [
    path('', views.index, name='index'),
    path('campus/', views.campus_index, name='campus'),
    path('campus/<int:campus_id>/', views.campus_show, name='campus_show'),
    path('faculty/', views.faculty_index, name='faculty'),
    path('faculty/<int:faculty_id>/', views.faculty_show, name='faculty_show'),
    path('faculty/<int:faculty_id>/update', views.faculty_update, name='faculty_update'),
    path('student/', views.student_index, name='student'),
    path('show_all/', views.show_all, name="show_all"),
]
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
次に,Student 詳細ページを作成する.最初に views.py を編集する.
university/views.py(抜粋)
def student_index(request):
    context = {}
    context['students'] = Student.objects.all()
    return render(request, 'student/index.html', context)
def student_show(request, student_id):
    context = {}
    student = get_object_or_404(Student, pk=student_id)
    context['student'] = student
    return render(request, 'student/show.html', context)
def show_all(request):
    context = {}
    context['campuses'] = Campus.objects.all()
    context['faculties'] = Faculty.objects.all()
    return render(request, 'show_all/index.html', context)
university/templates/student フォルダ内に show.html を作成する.
univeristy/templates/student/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student</title>
</head>
<body>
{% block content %}
<h1>学生</h1>
<p>ID: {{ student.id }}</p>
<p>name: {{ student.name }}</p>
{% endblock content %}
</body>
</html>
ルートを定義する.
university/urls.py
from django.urls import path
from . import views
app_name = 'university'
urlpatterns = [
    path('', views.index, name='index'),
    path('campus/', views.campus_index, name='campus'),
    path('campus/<int:campus_id>/', views.campus_show, name='campus_show'),
    path('faculty/', views.faculty_index, name='faculty'),
    path('faculty/<int:faculty_id>/', views.faculty_show, name='faculty_show'),
    path('faculty/<int:faculty_id>/update', views.faculty_update, name='faculty_update'),
    path('student/', views.student_index, name='student'),
    path('student/<int:student_id>', views.student_show, name='student_show'),
    path('show_all/', views.show_all, name="show_all"),
]
学生一覧のページに学生詳細ページへのリンクを設置する.
univeristy/templates/student/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student一覧</title>
</head>
<body>
{% block content %}
<h1>学生一覧</h1>
<ul>
    {% for student in object_list %}
        <li>
            <a href="{% url 'university:student_show' student.id %}">
                {{ student.id }} : {{ student.name }}
            </a>
        </li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
Lecture の一覧をデータベースから取得して表示するコードを記述しよう.まず,university/views.py に lecture_index 関数を追加する.
university/views.py(抜粋)
from django.urls import reverse_lazy, reverse
from django.shortcuts import render, redirect
from django.shortcuts import get_object_or_404
from .forms import FacultyForm
from .models import Campus, Faculty, Student, Lecture
(中略)
def lecture_index(request):
    context = {}
    context['lectures'] = Lecture.objects.all()
    return render(request, 'lecture/index.html', context)
すでに作成されているはずの university/templates フォルダに,lecture フォルダを作成し,その中に index.html ファイルを設置する.
template/lecture/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Lecture一覧</title>
</head>
<body>
{% block content %}
<h1>講義一覧</h1>
<ul>
    {% for lecture in lectures %}
        <li>
            {{ lecture.id }} : {{ lecture.name }}
        </li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
ルートを定義する.
university/urls.py
from django.urls import path
from . import views
app_name = 'university'
urlpatterns = [
    path('', views.index, name='index'),
    path('campus/', views.campus_index, name='campus'),
    path('campus/<int:campus_id>/', views.campus_show, name='campus_show'),
    path('faculty/', views.faculty_index, name='faculty'),
    path('faculty/<int:faculty_id>/', views.faculty_show, name='faculty_show'),
    path('faculty/<int:faculty_id>/update', views.faculty_update, name='faculty_update'),
    path('student/', views.student_index, name='student'),
    path('student/<int:student_id>', views.student_show, name='student_show'),
    path('lecture/', views.lecture_index, name='lecture'),
    path('show_all/', views.show_all, name="show_all"),
]
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
次に,Lecture 詳細ページを作成する.最初に views.py を編集する.
university/views.py(抜粋)
from django.urls import reverse_lazy, reverse
from django.shortcuts import render, redirect
from django.shortcuts import get_object_or_404
from .forms import FacultyForm
from .models import Campus, Faculty, Student, Lecture
(中略)
def lecture_index(request):
    context = {}
    context['lectures'] = Lecture.objects.all()
    return render(request, 'lecture/index.html', context)
def lecture_show(request, lecture_id):
    context = {}
    lecture = get_object_or_404(Lecture, pk=lecture_id)
    context['lecture'] = lecture
    return render(request, 'lecture/show.html', context)
university/templates/lecture フォルダ内に show.html を作成する.
univeristy/templates/lecture/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Lecture</title>
</head>
<body>
{% block content %}
<h1>講義</h1>
<p>ID: {{ lecture.id }}</p>
<p>name: {{ lecture.name }}</p>
{% endblock content %}
</body>
</html>
ルートを定義する.
university/urls.py
from django.urls import path
from . import views
app_name = 'university'
urlpatterns = [
    path('', views.index, name='index'),
    path('campus/', views.campus_index, name='campus'),
    path('campus/<int:campus_id>/', views.campus_show, name='campus_show'),
    path('faculty/', views.faculty_index, name='faculty'),
    path('faculty/<int:faculty_id>/', views.faculty_show, name='faculty_show'),
    path('faculty/<int:faculty_id>/update', views.faculty_update, name='faculty_update'),
    path('student/', views.student_index, name='student'),
    path('student/<int:student_id>', views.student_show, name='student_show'),
    path('lecture/', views.lecture_index, name='lecture'),
    path('lecture/<int:lecture_id>', views.lecture_show, name='lecture_show'),
    path('show_all/', views.show_all, name="show_all"),
]
講義一覧のページに講義詳細ページへのリンクを設置する.
univeristy/templates/lecture/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Lecture一覧</title>
</head>
<body>
{% block content %}
<h1>講義一覧</h1>
<ul>
    {% for lecture in object_list %}
        <li>
          <a href="{% url 'university:lecture_show' lecture.id %}">
              {{ lecture.id }} : {{ lecture.name }}
          </a>
        </li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
Student と Lecture について一覧と詳細が取得,表示できるようになった.ここで,講義の履修情報に関するテーブル,すなわち中間テーブル(関連リレーションという)のテストデータを設定する.具体的には,university/fixtures フォルダ内に lecture_students-data.json というファイルを設置する.ただその前に sqlite3 を使って,中間テーブルのフィールド名を確認しておくと良い.
(py39) C:\Users\lecture\Documents\django\django_relationship>sqlite3 db.sqlite3 ⏎ SQLite version 3.38.2 2022-03-26 13:51:10 Enter ".help" for usage hints. sqlite> .tables ⏎ auth_group django_migrations auth_group_permissions django_session auth_permission university_campus auth_user university_faculty auth_user_groups university_lecture auth_user_user_permissions university_lecture_students django_admin_log university_student django_content_type sqlite> .schema university_lecture_students ⏎ CREATE TABLE IF NOT EXISTS "university_lecture_students" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "lecture_id" bigint NOT NULL REFERENCES "university_lecture" ("id") DEFERRABLE INITIALLY DEFERRED, "student_id" bigint NOT NULL REFERENCES "university_student" ("id") DEFERRABLE INITIALLY DEFERRED ); CREATE UNIQUE INDEX "university_lecture_students_lecture_id_student_id_9a23d8d1_uniq" ON "university_lecture_students" ("lecture_id", "student_id"); CREATE INDEX "university_lecture_students_lecture_id_27153bef" ON "university_lecture_students" ("lecture_id"); CREATE INDEX "university_lecture_students_student_id_e7421ae5" ON "university_lecture_students" ("student_id"); sqlite> .exit ⏎ (py39) C:\Users\lecture\Documents\django\django_relationship>
中間テーブルのテーブル名が「university_lecture_students」であり,フィールド名が「lecture_id」と「student_id」であることが確認できたので,lecture_students-data.json には次のような内容を登録する.
university/fixtures/lecture_students-data.json
[
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 1,
            "student_id": 1
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 2,
            "student_id": 1
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 1,
            "student_id": 2
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 3,
            "student_id": 2
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 1,
            "student_id": 3
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 2,
            "student_id": 3
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 3,
            "student_id": 3
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 2,
            "student_id": 4
        }
    },
    {
        "model": "university.lecture_students",
        "fields": {
            "lecture_id": 3,
            "student_id": 4
        }
    }
]
作成したテストデータをデータベースに投入する.なお,次のコマンドはWindows用です.Macの場合はフォルダの区切りを「\」ではなく「/」に変更してください.
(py39) C:\Users\lecture\Documents\django\django_relationship>python manage.py loaddata university\fixtures\lecture_students-data.json ⏎
Installed 9 object(s) from 1 fixture(s)
(py39) C:\Users\lecture\Documents\django\django_relationship>
念の為,いま投入されたデータを sqlite3 で確認する.
(py39) C:\Users\lecture\Documents\django\django_relationship>sqlite3 db.sqlite3 ⏎ SQLite version 3.38.2 2022-03-26 13:51:10 Enter ".help" for usage hints. sqlite> .headers ON ⏎ sqlite> select * from university_lecture_students; ⏎ id|lecture_id|student_id 1|1|1 2|2|1 3|1|2 4|3|2 5|1|3 6|2|3 7|3|3 8|2|4 9|3|4 sqlite> .exit ⏎ (py39) C:\Users\lecture\Documents\django\django_relationship>
ようやく多対多のリレーションシップを取得する準備ができたので,学生詳細情報のページで履修講義の一覧を取得できるようにしてみよう.これは一対多で取得した方法と同様の方法で取得できる.
univeristy/templates/student/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student</title>
</head>
<body>
{% block content %}
<h1>学生</h1>
<p>ID: {{ student.id }}</p>
<p>name: {{ student.name }}</p>
<h2>履修講義</h2>
<ul>
    {% for lecture in student.lecture_set.all %}
        <li>{{ lecture.name }}</li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
学生一覧ページで履修講義も取得できるようにしてみよう.やはり取得方法は同じである.
univeristy/templates/student/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student一覧</title>
</head>
<body>
{% block content %}
<h1>学生一覧</h1>
<ul>
    {% for student in object_list %}
        <li>
            <a href="{% url 'university:student_show' student.id %}">
                {{ student.id }} : {{ student.name }}
            </a>
            <ul>
                {% for lecture in student.lecture_set.all %}
                    <li>{{ lecture.name }}</li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
  次は逆方向で講義詳細ページで履修学生を一覧で取得する.ほぼ同様であるが,lecture.student_set.all ではなく lecture.students.all を利用することに注意する.
univeristy/templates/lecture/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Lecture</title>
</head>
<body>
{% block content %}
<h1>講義</h1>
<p>ID: {{ lecture.id }}</p>
<p>name: {{ lecture.name }}</p>
<h2>履修者一覧</h2>
<ul>
    {% for student in lecture.students.all %}
        <li>{{ student.id }} : {{ student.name }}</li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
講義一覧ページでも履修学生を一覧で表示してみよう.
univeristy/templates/lecture/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Lecture一覧</title>
</head>
<body>
{% block content %}
<h1>講義一覧</h1>
<ul>
    {% for lecture in object_list %}
        <li>
            <a href="{% url 'university:lecture_show' lecture.id %}">
                {{ lecture.id }} : {{ lecture.name }}
            </a>
            <ul>
                {% for student in lecture.students.all %}
                    <li>{{ student.id }} : {{ student.name }}</li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.
python manage.py runserver ⏎
ここでは学生情報を更新するページを作成する.さらに,履修講義も変更できるようにしたい.
  まずはフォームを作成する.具体的には university/forms.py に StudentForm クラスを追加する.なお,25行目のように明示的に required=False を指定しなければ,少なくとも1つの講義は履修しなければならなくなる.また27行目の widget=forms.CheckboxSelectMultiple() を指定することで,チェックボックスのコントロールが利用されるようになる.
univeristy/forms.py
from django import forms
from .models import Campus, Lecture
class FacultyForm(forms.Form):
    faculty = forms.CharField(label='学部名', max_length=20)
    established = forms.IntegerField(label='設置年', min_value=1900, max_value=2100)
    campus = forms.ModelChoiceField(label='キャンパス', required=True, queryset=Campus.objects.all())
    def clean_faculty(self):
        faculty = self.cleaned_data['faculty']
        if len(faculty) > 17:
          raise forms.ValidationError("学部名は最長17文字です")
        return faculty
    def clean_established(self):
        established = self.cleaned_data['established']
        if(established < 1950 or established > 2025):
            raise forms.ValidationError("設置年は1950〜2025にしてください")
        return established
class StudentForm(forms.Form):
    name = forms.CharField(label='氏名', max_length=20)
    lectures = forms.ModelMultipleChoiceField(
        label='履修講義',
        required=False,
        queryset=Lecture.objects.all(),
        widget=forms.CheckboxSelectMultiple()
        )
    def clean_name(self):
        name = self.cleaned_data['name']
        if len(name) > 17:
            raise forms.ValidationError("氏名は最長17文字です")
        return name
views.py に student_update 関数を追加する.このコードでは18行目で該当学生の履修データを中間テーブルから一旦全て削除してから21行目で新たに全ての講義を登録し直しています.次のページのように中間テーブルに得点などのデータが保存されている場合には工夫が必要になるでしょう.
univeristy/views.py
from django.urls import reverse_lazy, reverse
from django.shortcuts import render, redirect
from django.shortcuts import get_object_or_404
from .forms import FacultyForm, StudentForm
from .models import Campus, Faculty, Student, Lecture
(中略)
def student_update(request, student_id):
    if request.method == 'POST':
        form = StudentForm(request.POST)
        if form.is_valid():
            student = get_object_or_404(Student, pk=student_id)
            student.name = form.cleaned_data.get("name")
            student.save()
            # 関連付けを一旦クリアする
            student.lecture_set.clear()
            # 新たに関連付け
            for lecture_name in form.cleaned_data.get("lectures"):
                student.lecture_set.add(lecture_name.id)
            return redirect(reverse('university:student'))
        else:
            # エラーメッセージをつけて返す
            return render(request, 'student/form.html', {'form': form})
    else:
        context = {}
        student = get_object_or_404(Student, pk=student_id)
        context['form'] = StudentForm(
                            initial={
                                'name' : student.name,
                                'lectures': student.lecture_set.all,
                            })
        return render(request, 'student/form.html', context)
university/templates/student/ フォルダに form.html を作成する.
university/templates/student/form.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student</title>
</head>
<body>
{% block content %}
<h1>学生情報の編集</h1>
<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">学生情報の更新</button>
</form>
{% endblock content %}
</body>
</html>
urls.py にルートを追加する.
university/urls.py
from django.urls import path
from . import views
app_name = 'university'
urlpatterns = [
    path('', views.index, name='index'),
    path('campus/', views.campus_index, name='campus'),
    path('campus/<int:campus_id>/', views.campus_show, name='campus_show'),
    path('faculty/', views.faculty_index, name='faculty'),
    path('faculty/<int:faculty_id>/', views.faculty_show, name='faculty_show'),
    path('faculty/<int:faculty_id>/update', views.faculty_update, name='faculty_update'),
    path('student/', views.student_index, name='student'),
    path('student/<int:student_id>', views.student_show, name='student_show'),
    path('student/<int:student_id>/update', views.student_update, name='student_update'),
    path('lecture/', views.lecture_index, name='lecture'),
    path('lecture/<int:lecture_id>', views.lecture_show, name='lecture_show'),
    path('show_all/', views.show_all, name="show_all"),
]
最後に Student の詳細ページに編集のためのリンクを設置する.
univeristy/templates/student/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Student</title>
</head>
<body>
{% block content %}
<h1>学生</h1>
<p>ID: {{ student.id }}</p>
<p>name: {{ student.name }}</p>
<h2>履修講義</h2>
<ul>
    {% for lecture in student.lecture_set.all %}
        <li>{{ lecture.name }}</li>
    {% endfor %}
</ul>
<hr>
<div>
    <p>
        <a href="{% url 'university:student_update' student.id %}">
            [編集]
        </a>
  </p>
</div>
{% endblock content %}
</body>
</html>
Webサーバを起動してブラウザで動作を確認する.氏名だけでなく履修講義の更新もできるようになった.
python manage.py runserver ⏎