Python Django 入門トップページ


リレーションシップを使いこなそう

  1. 概要
  2. プロジェクトの作成
  3. 一対多のリレーションシップ
  4. 多対多のリレーションシップ
    1. 概要
    2. トップページからのリンクを作る
    3. モデルを作る
    4. テストデータの設定
    5. テストデータの投入
    6. Student 一覧を表示する
    7. Student 詳細ページ
    8. Lecture 一覧を表示する
    9. Lecture 詳細ページ
    10. 中間テーブルのテストデータ設定
    11. 中間テーブルのテストデータの投入
    12. 学生詳細で履修講義を取得
    13. 学生一覧で履修講義を取得
    14. 講義詳細で履修学生一覧を取得
    15. 講義一覧で履修学生一覧を取得
    16. 学生情報を更新する
  5. 多対多のカスタム中間テーブル

目次に戻る

リレーションシップを使いこなそう

多対多のリレーションシップ

概要

ある学生が履修できる「講義」は多数あり,ある講義を履修する「学生」も多数いるということから,「学生」と「講義」の関係は「多対多」です.ここでは「学生」が「講義」を履修するという機能をDjangoで作成します.なお,履修した科目に「得点」情報を持たせたいような場合には,次のページのカスタム中間テーブルを利用する必要があります.

lecture_student

目次に戻る

まず,トップページから学生一覧と講義一覧のページへのリンクを設置する.もちろんまだつながらないはずである.

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>
django2022-00071

目次に戻る

モデルを作る

「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 一覧を表示する

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 ⏎
django2022-00072

目次に戻る

Student 詳細ページ

次に,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 ⏎
django2022-00073

目次に戻る

Lecture 一覧を表示する

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 ⏎
django2022-00074

目次に戻る

Lecture 詳細ページ

次に,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 ⏎
django2022-00075

目次に戻る

中間テーブルのテストデータ設定

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 ⏎
django2022-00076

目次に戻る

学生一覧で履修講義を取得

学生一覧ページで履修講義も取得できるようにしてみよう.やはり取得方法は同じである.

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 ⏎
django2022-00077

目次に戻る

講義詳細で履修学生一覧を取得

次は逆方向で講義詳細ページで履修学生を一覧で取得する.ほぼ同様であるが,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 ⏎
django2022-00078

目次に戻る

講義一覧で履修学生一覧を取得

講義一覧ページでも履修学生を一覧で表示してみよう.

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 ⏎
django2022-00079

目次に戻る

学生情報を更新する

ここでは学生情報を更新するページを作成する.さらに,履修講義も変更できるようにしたい.

まずはフォームを作成する.具体的には 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 ⏎
django2022-00080

目次に戻る