Python Django 入門トップページ


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

このページは Django version 3 系の情報です.新たな version 4 系の情報はこちらからどうぞ.

目次

  1. 概要
  2. モデルを作る
  3. マイグレーション
  4. テストデータの設定
  5. テストデータの投入
  6. ロールバック,マイグレーション,シーディングの連続実行
  7. Campus 一覧を表示する
  8. Campus 詳細ページとリレーションシップ
  9. Faculty 一覧ページを作る
  10. Faculty 詳細ページ
  11. Faculty 編集ページとリレーションシップの参照制約
  12. generic モジュールを使わずにデータベースにアクセス
    1. Campus 一覧ページ
    2. Campus 詳細ページ
    3. Faculty 一覧ページ
    4. Faculty 詳細ページ
    5. Faculty 編集ページ
    6. Faculty 編集ページでセレクトコントロールを使う
  13. 複数のモデルをviewで扱う

目次に戻る

概要

「キャンパス」と「学部」の関係は「一対多」である.なお,Laravelではテーブル名に「campuses」と「faculties」という複数形を使ったが,Django では 「アプリケーション名_単数形」になるので注意する.

campus_faculty

目次に戻る

モデルを作る

まずは「Campus」と「Faculty」という2つのモデルを作る.このとき,「Faculty」モデルに外部キーを設定し,連鎖削除を行うためのオプションを設定する.

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']

次に,マイグレーションを生成する.

python manage.py makemigrations university ⏎

目次に戻る

マイグレーション

マイグレーションを実行する.

python manage.py migrate ⏎
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, university
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
  Applying university.0001_initial... OK

sqlite3 を使って,テーブルが作成されたことを確認しておく.

sqlite3 db.sqlite3 ⏎
sqlite> .tables ⏎
auth_group                  django_admin_log
auth_group_permissions      django_content_type
auth_permission             django_migrations
auth_user                   django_session
auth_user_groups            university_campus
auth_user_user_permissions  university_faculty
sqlite> .schema university_campus ⏎
CREATE TABLE IF NOT EXISTS "university_campus" (
  "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
  "campus" varchar(20) NOT NULL);
sqlite> .schema university_faculty ⏎
CREATE TABLE IF NOT EXISTS "university_faculty" (
  "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
  "faculty" varchar(20) NOT NULL,
  "established" integer NOT NULL,
  "campus_id" integer NOT NULL REFERENCES "university_campus" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "university_faculty_campus_id_2c0f928a" ON "university_faculty" ("campus_id");
sqlite> .exit ⏎

showmigrations でマイグレーションの実行状況を確認しておく.

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
contenttypes
 [X] 0001_initial
 [X] 0002_remove_content_type_name
sessions
 [X] 0001_initial
university
 [X] 0001_initial

目次に戻る

テストデータの設定

Campus と Faculty にテストデータを投入する.このために,university フォルダに fixtures というフォルダを作成し,その中に2つの json ファイルを設置する.

university/fixtures/campus-data.json
[
  {
    "model": "university.campus",
    "fields": {
      "campus": "KAC"
    }
  },

  {
    "model": "university.campus",
    "fields": {
      "campus": "KPC"
    }
  }
]
university/fixtures/faculty-data.json
[
  {
    "model": "university.faculty",
    "fields": {
      "faculty": "栄養学部",
      "established": 1966,
      "campus_id": 1
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "法学部",
      "established": 1967,
      "campus_id": 2
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "経済学部",
      "established": 1967,
      "campus_id": 1
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "薬学部",
      "established": 1972,
      "campus_id": 2
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "人文学部",
      "established": 2004,
      "campus_id": 1
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "経営学部",
      "established": 2004,
      "campus_id": 2
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "総合リハビリテーション学部",
      "established": 2005,
      "campus_id": 1
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "現代社会学部",
      "established": 2014,
      "campus_id": 2
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "グローバル・コミュニケーション学部",
      "established": 2015,
      "campus_id": 2
    }
  },

  {
    "model": "university.faculty",
    "fields": {
      "faculty": "心理学部",
      "established": 2018,
      "campus_id": 1
    }
  }
]

目次に戻る

テストデータの投入

作成したテストデータをデータベースに投入する.なお,次のコマンドはWindows用です.Macの場合はフォルダの区切りを「\」ではなく「/」に変更してください.

python manage.py loaddata university\fixtures\campus-data.json ⏎
Installed 2 object(s) from 1 fixture(s)
python manage.py loaddata university\fixtures\faculty-data.json ⏎
Installed 10 object(s) from 1 fixture(s)

念の為,いま投入されたデータを sqlite3 で確認する.

sqlite3 db.sqlite3 ⏎
sqlite> .headers ON ⏎ # 列名を表示するためのコマンド(省略しても良い)
sqlite> select * from university_campus; ⏎
id|campus
1|KAC
2|KPC
sqlite> select * from university_faculty; ⏎
id|faculty|established|campus_id
1|栄養学部|1966|1
2|法学部|1967|2
3|経済学部|1967|1
4|薬学部|1972|2
5|人文学部|2004|1
6|経営学部|2004|2
7|総合リハビリテーション学部|2005|1
8|現代社会学部|2014|2
9|グローバル・コミュニケーション学部|2015|2
10|心理学部|2018|1
sqlite> .exit ⏎

目次に戻る

ロールバック,マイグレーション,シーディングの連続実行

開発中は何度もデータベースをロールバックし,マイグレーション,シーディング(テストデータの投入)を繰り返すことになる.Windows では次のようなコマンドでよい.

python manage.py migrate university zero & python manage.py migrate & python manage.py loaddata university\fixtures\campus-data.json & python manage.py loaddata university\fixtures\faculty-data.json ⏎

Mac では & の代わりに ; を利用すると良い.

python manage.py migrate university zero; python manage.py migrate; python manage.py loaddata university/fixtures/campus-data.json; python manage.py loaddata university/fixtures/faculty-data.json ⏎

目次に戻る

Campus 一覧を表示する

ようやく準備ができたので,Campusの一覧をデータベースから取得して表示するコードを記述しよう.まず,university/views.py にクラスを追加する.このとき,13行目のように,template_name を使って html テンプレートを指定することもできる.

university/views.py
from django.shortcuts import render

from django.views.generic import ListView
from .models import Campus

# Create your views here.

def index(request):
    return render(request, 'index.html')

class CampusIndexView(ListView):
    model = Campus
    template_name = 'campus/index.html'

university フォルダ内に templates フォルダを作成し,その中に campus フォルダを作成する.さらに,その中に 上で指定したように index.html ファイルを設置する.

template/campus/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Campus一覧</title>
</head>
<body>
{% block content %}
<h1>キャンパス一覧</h1>
<ul>
    {% for campus in object_list %}
      <li>
          {{ campus.campus }}
      </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.CampusIndexView.as_view(), name='campus'),
]

Webサーバを起動してブラウザで動作を確認する.

python manage.py runserver ⏎

r-03-01

目次に戻る

Campus 詳細ページとリレーションシップ

次に,Campus 詳細ページを作成し,Campus に所属する学部の一覧もリレーションシップをたどって取得してみよう.

最初に views.py を編集する.

university/views.py
from django.shortcuts import render

from django.views.generic import ListView, DetailView
from .models import Campus

# Create your views here.

def index(request):
    return render(request, 'index.html')

class CampusIndexView(ListView):
    model = Campus
    template_name = 'campus/index.html'

class ShowCampusView(DetailView):
    model = Campus
    template_name = 'campus/show.html'

univeristy/templates/campus フォルダ内に show.html を作成する.

univeristy/templates/campus/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Campus</title>
</head>
<body>
{% block content %}
<h1>キャンパス {{ campus.campus }}</h1>
{% 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.CampusIndexView.as_view(), name='campus'),
    path('campus/<int:pk>/', views.ShowCampusView.as_view(), name='campus_show'),
]

キャンパス一覧ページにキャンパス詳細ページへのリンクを設置する.

template/campus/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Campus一覧</title>
</head>
<body>
{% block content %}
<h1>キャンパス一覧</h1>
<ul>
    {% for campus in object_list %}
      <li>
          <a href="{% url 'university:campus_show' campus.id %}">
            {{ campus.campus }}
          </a>
      </li>
    {% endfor %}
</ul>
{% endblock content %}

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.キャンパス詳細ページではまだキャンパス名だけが表示される状態である.

python manage.py runserver ⏎

r-03-02

r-03-03

ここで,キャンパス詳細ページでは,そのキャンパス(一側)に所属する学部(多側)を一覧で取得してみよう.このためには,show.html を次のように編集するだけで良い.ここで重要なことは,campus に所属する faculty は11行目のように campus.faculty_set.all で取得できるということである.

univeristy/templates/campus/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Campus</title>
</head>
<body>
{% block content %}
<h1>キャンパス {{ campus.campus }}</h1>
<ul>
  {% for faculty in campus.faculty_set.all %}
    <li>{{ faculty }}({{ faculty.established }}年)</li>
  {% endfor %}
</ul>
{% endblock content %}

</body>
</html>
python manage.py runserver ⏎

r-03-04

r-03-05

なお,urlに 「/university/campus/3/」のように存在しないキャンパスIDを指定した場合は,次のようなエラーが表示されることに注意する.

r-03-06

目次に戻る

Faculty 一覧ページを作る

次は,Facultyの一覧をデータベースから取得して表示するコードを記述する.なお,Faculty が多側になり,Campusが一側になることに注意する.まず,university/views.py にクラスを追加する.

university/views.py
from django.shortcuts import render

from django.views.generic import ListView, DetailView
from .models import Campus, Faculty

# Create your views here.

def index(request):
    return render(request, 'index.html')

class CampusIndexView(ListView):
    model = Campus
    template_name = 'campus/index.html'

class ShowCampusView(DetailView):
    model = Campus
    template_name = 'campus/show.html'

class FacultyIndexView(ListView):
    model = Faculty
    template_name = 'faculty/index.html'

university/templates フォルダ内に faculty フォルダを作成する.さらに,その中に上(21行目)で指定したように index.html を設置する.

template/faculty/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty一覧</title>
</head>
<body>
{% block content %}
<h1>学部一覧</h1>
<ul>
    {% for faculty in object_list %}
      <li>
          {{ faculty.faculty }}は{{ faculty.established }}年に設置されました
      </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.CampusIndexView.as_view(), name='campus'),
    path('campus/<int:pk>/', views.ShowCampusView.as_view(), name='campus_show'),
    path('faculty/', views.FacultyIndexView.as_view(), name='faculty'),
]

Webサーバを起動してブラウザで動作を確認する.

python manage.py runserver ⏎

r-03-07

さらに,リレーションシップを辿って,多側の Faculty から一側の Campus 情報を取得してみよう.

template/faculty/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty一覧</title>
</head>
<body>
{% block content %}
<h1>学部一覧</h1>
<ul>
    {% for faculty in object_list %}
      <li>
          {{ faculty.faculty }}は{{ faculty.established }}年に設置され,キャンパスは{{ faculty.campus }}です
      </li>
    {% endfor %}
</ul>
{% endblock content %}

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.

python manage.py runserver ⏎

r-03-08

目次に戻る

Faculty 詳細ページ

次は,Facultyの詳細ページを作成する.まず,university/views.py にクラスを追加する.

university/views.py
from django.shortcuts import render

from django.views.generic import ListView, DetailView
from .models import Campus

# Create your views here.

def index(request):
    return render(request, 'index.html')

class CampusIndexView(ListView):
    model = Campus
    template_name = 'campus/index.html'

class ShowCampusView(DetailView):
    model = Campus
    template_name = 'campus/show.html'

class FacultyIndexView(ListView):
    model = Faculty
    template_name = 'faculty/index.html'

class ShowFacultyView(DetailView):
    model = Faculty
    template_name = 'faculty/show.html'

univeristy/templates/faculty フォルダ内に show.html を作成する.

univeristy/templates/faculty/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty</title>
</head>
<body>
{% block content %}
<h1>{{ faculty.faculty }}</h1>
<p>
  {{ faculty.faculty }}は{{ faculty.established }}年に設置され,キャンパスは{{ faculty.campus }}です.
</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.CampusIndexView.as_view(), name='campus'),
    path('campus/<int:pk>/', views.ShowCampusView.as_view(), name='campus_show'),
    path('faculty/', views.FacultyIndexView.as_view(), name='faculty'),
    path('faculty/<int:pk>/', views.ShowFacultyView.as_view(), name='faculty_show'),
]

学部一覧ページに学部詳細ページへのリンクを設置する.

template/faculty/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty一覧</title>
</head>
<body>
{% block content %}
<h1>学部一覧</h1>
<ul>
    {% for faculty in object_list %}
      <li>
        <a href="{% url 'university:faculty_show' faculty.id %}">
          {{ faculty.faculty }}は{{ faculty.established }}年に設置され,キャンパスは{{ faculty.campus }}です
        </a>
      </li>
    {% endfor %}
</ul>
{% endblock content %}

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.

python manage.py runserver ⏎

r-03-09

r-03-10

目次に戻る

Faculty 編集ページとリレーションシップの参照制約

次は,Facultyの情報を編集するためのページを作成する.ここで,所属キャンパスに関するリレーションシップの参照制約がどのように働くか考察する.

まずは,学部名と設置年だけを編集できるような機能を作成する.university フォルダに forms.py というファイルを新規に作成する.

university/forms.py
from django import forms
from .models import Faculty

class FacultyForm(forms.ModelForm):

    class Meta:
        model = Faculty
        fields = ('faculty', 'established')
        widgets = {
              'faculty': forms.TextInput(attrs={
              'class': 'form-control'
            }),
              'established': forms.TextInput(attrs={
              'class': 'form-control'
            }),
        }
        labels = {
            'faculty': '学部名',
            'established': '設置年',
        }

university/templates/faculty フォルダ内に form.html というファイルを新規に作成する.

university/templates/faculty/form.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty</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>

university/views.py を編集する.なお,32行目は投稿が成功したときに,university アプリケーションの faculty という名前のルートにリダイレクトするという意味である.これはつまり,university/urls.py の 10行目で name='faculty' と定義された url が university/faculty/ のページへとリダイレクトすることを意味している.

university/views.py
from django.urls import reverse_lazy
from django.shortcuts import render

from django.views.generic import ListView, DetailView, UpdateView
from .forms import FacultyForm
from .models import Campus, Faculty

# Create your views here.

def index(request):
    return render(request, 'index.html')

class CampusIndexView(ListView):
    model = Campus
    template_name = 'campus/index.html'

class ShowCampusView(DetailView):
    model = Campus
    template_name = 'campus/show.html'

class FacultyIndexView(ListView):
    model = Faculty
    template_name = 'faculty/index.html'

class ShowFacultyView(DetailView):
    model = Faculty
    template_name = 'faculty/show.html'

class UpdateFacultyView(UpdateView):
    model = Faculty
    form_class = FacultyForm
    success_url = reverse_lazy('university:faculty')
    template_name = 'faculty/form.html'

ルートを記述する.

university/urls.py
from django.urls import path

from . import views

app_name = 'university'
urlpatterns = [
    path('', views.index, name='index'),
    path('campus/', views.CampusIndexView.as_view(), name='campus'),
    path('campus/<int:pk>/', views.ShowCampusView.as_view(), name='campus_show'),
    path('faculty/', views.FacultyIndexView.as_view(), name='faculty'),
    path('faculty/<int:pk>/', views.ShowFacultyView.as_view(), name='faculty_show'),
    path('faculty/<int:pk>/update', views.UpdateFacultyView.as_view(), name='faculty_update'),
]

最後に,詳細ページに編集ページヘ移動するためのリンクを設置する.

university/templates/faculty/show.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty</title>
</head>
<body>
{% block content %}
<h1>{{ faculty.faculty }}</h1>
<p>
  {{ faculty.faculty }}は{{ faculty.established }}年に設置され,キャンパスは{{ faculty.campus }}です.
</p>
<hr>
<div>
  <p>
    <a href="{% url 'university:faculty_update' faculty.id %}">
      [編集]
    </a>
  </p>
</div>

{% endblock content %}

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.実際に「経営学部」の学部名を「経営学部経営学科」に,設置年を「2020」に変更してみる.なお,動作の確認ができたら再度編集して元に戻しておいても良い.

python manage.py runserver ⏎

r-03-11

r-03-12

引き続き,「所属キャンパス」も修正できるようにしてみよう.まず,キャンパスのID番号を指定して変更するような仕組みを作る.このためには,forms.py に数行のコードを追加するだけで良い.

university/forms.py
from django import forms
from .models import Faculty

class FacultyForm(forms.ModelForm):

    class Meta:
        model = Faculty
        fields = ('faculty', 'established', 'campus')
        widgets = {
              'faculty': forms.TextInput(attrs={
              'class': 'form-control'
            }),
              'established': forms.TextInput(attrs={
              'class': 'form-control'
            }),
            'campus': forms.TextInput(attrs={
              'class': 'form-control'
            }),
        }
        labels = {
            'faculty': '学部名',
            'established': '設置年',
            'campus': 'キャンパス',
        }

Webサーバを起動してブラウザで動作を確認する.実際に「経営学部」の学部名を「経営学部経営学科」に,設置年を「2020」に,キャンパスIDを「3」変更してみる.このとき,キャンパスIDは「1」または「2」以外は参照整合性に違反するが,エラーの処理も自動的に行われている.キャンパスIDを「1」にして再度変更すると,KACに変更できる.

python manage.py runserver ⏎

キャンパスIDに「3」を指定して「学部情報の更新」をクリックする.

r-03-13

キャンパスID = 3 は参照整合性に違反するため,エラーが表示された.キャンパスに「1」を指定して「学部情報の更新」をクリックする.

r-03-14

学部情報が正しく更新された.

r-03-15

ひとまず,所属キャンパスの変更までが可能になった.しかしながら,キャンパスのIDを指定する方法は必ずしも操作が簡単であるとは思えない.キャンパス名のリストから選べるような仕組みを作りたい.そのために次はいつくかの準備を行う.

目次に戻る

generic モジュールを使わずにデータベースにアクセス

これまで利用してきた ListView などの django.views.generic モジュールを使うとモデルを指定するだけでデータベースから必要なデータが取得できるなど,非常に短いコードで記述できる.しかし,ここでは generic モジュールを利用しない方法についても説明する.コードは少し長くなってしまうがカスタマイズしやすいというメリットはある.

Campus 一覧ページ

まず,キャンパス一覧ページだけを変更する.views.py を次のように変更する.具体的には,CampusIndexView のクラスをコメントアウトし,campus_index( ) という関数を定義する.19行目では,Campusの一覧をデータベースから取得し,context という辞書に格納している.

university/views.py
from django.urls import reverse_lazy
from django.shortcuts import render

from django.views.generic import ListView, DetailView, UpdateView
from .forms import FacultyForm
from .models import Campus, Faculty

# Create your views here.

def index(request):
    return render(request, 'index.html')

# class CampusIndexView(ListView):
#     model = Campus
#     template_name = 'campus/index.html'

def campus_index(request):
    context = {}
    context['campuses'] = Campus.objects.all()
    return render(request, 'campus/index.html', context)

class ShowCampusView(DetailView):
    model = Campus
    template_name = 'campus/show.html'

class FacultyIndexView(ListView):
    model = Faculty
    template_name = 'faculty/index.html'

class ShowFacultyView(DetailView):
    model = Faculty
    template_name = 'faculty/show.html'

class UpdateFacultyView(UpdateView):
    model = Faculty
    form_class = FacultyForm
    success_url = reverse_lazy('university:faculty')
    template_name = 'faculty/form.html'

URL の /university/campus/ に対応するビューの関数が CampusIndexView( ) から campus_index( ) に変わったため,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:pk>/', views.ShowCampusView.as_view(), name='campus_show'),
    path('faculty/', views.FacultyIndexView.as_view(), name='faculty'),
    path('faculty/<int:pk>/', views.ShowFacultyView.as_view(), name='faculty_show'),
    path('faculty/<int:pk>/update', views.UpdateFacultyView.as_view(), name='faculty_update'),
]

また,template/campus/index.html で {% for campus in object_list %} のところで object_list を使っていたが,views.py の19行目では context という辞書のキー 'campuses' にデータベースから取得した内容が格納されているため,template/campus/index.html を変更する.

template/campus/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Campus一覧</title>
</head>
<body>
{% block content %}
<h1>キャンパス一覧</h1>
<ul>
    {% for campus in campuses %}
      <li>
          <a href="{% url 'university:campus_show' campus.id %}">
            {{ campus.campus }}
          </a>
      </li>
    {% endfor %}
</ul>
{% endblock content %}

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.以前と同じキャンパス一覧ページが表示できるはずである.

python manage.py runserver ⏎

r-03-02

目次に戻る

Campus 詳細ページ

次は Campus の詳細ページをカスタマイズできるようにしてみよう.views.py において,ShowCampusView クラスをコメントアウトし,campus_show 関数を定義するとともに,urls.py も編集する.

university/views.py(抜粋)
# class ShowCampusView(DetailView):
#     model = Campus
#     template_name = 'campus/show.html'

def campus_show(request, pk):
    context = {}
    context['campus'] = Campus.objects.get(pk=pk)
    return render(request, 'campus/show.html', context)
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:pk>/', views.campus_show, name='campus_show'),
    path('faculty/', views.FacultyIndexView.as_view(), name='faculty'),
    path('faculty/<int:pk>/', views.ShowFacultyView.as_view(), name='faculty_show'),
    path('faculty/<int:pk>/update', views.UpdateFacultyView.as_view(), name='faculty_update'),
]

上のように変更するだけで,KAC や KPC の詳細ページを閲覧できるはずである.しかしながら,上の views.py 7行目について,pk=pk という箇所は慣れるまで意味の理解が難しいかもしれない.これは次のように書くと理解しやすいと思われる.

university/views.py(抜粋)
def campus_show(request, campus_id):
    context = {}
    context['campus'] = Campus.objects.get(pk=campus_id)
    return render(request, 'campus/show.html', context)

この変更に伴って,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.FacultyIndexView.as_view(), name='faculty'),
    path('faculty/<int:pk>/', views.ShowFacultyView.as_view(), name='faculty_show'),
    path('faculty/<int:pk>/update', views.UpdateFacultyView.as_view(), name='faculty_update'),
]

つまり,モデル(つまりデータベースの university_campus テーブル)の主キー(pk: primary key) が URL のパラメータ campus_id と等しいレコードをデータベースから取得することを意味している.

Webサーバを起動してブラウザで動作を確認する.以前と同じキャンパス詳細ページが表示できるはずである.

python manage.py runserver ⏎

r-03-04

しかしながら,URL に不正な campus_id を指定した場合には 404 エラーにならず,500 エラーになってしまう.

r-03-16

このような動作は好ましくないため,不正な campus_id が指定された場合には 404 エラーを表示させるようにしたい.このためには get_object_or_404 というショートカットをインポートして objects.get の代わりに利用すると良い.

university/views.py(抜粋)
from django.urls import reverse_lazy
from django.shortcuts import render
from django.shortcuts import get_object_or_404

(中略)

def campus_show(request, campus_id):
    context = {}
    campus = get_object_or_404(Campus, pk=campus_id)
    context['campus'] = campus
    return render(request, 'campus/show.html', context)

Webサーバを起動してブラウザで動作を確認する.不正な campus_id を指定した場合には期待通り 404 エラーを表示できるようになった.

python manage.py runserver ⏎

r-03-06

目次に戻る

Faculty 一覧ページ

ここではCampus 一覧と同様に django.views.generic モジュールを使わずにデータベースにアクセスすることをやってみる.

views.py を次のように変更する.具体的には,FacultyIndexView のクラスをコメントアウトし,faculty_index( ) という関数を定義する.19行目では,Faculty の一覧をデータベースから取得し,context という辞書に格納している.

university/views.py(抜粋)
# class FacultyIndexView(ListView):
#     model = Faculty
#     template_name = 'faculty/index.html'

def faculty_index(request):
    context = {}
    context['faculties'] = Faculty.objects.all()
    return render(request, 'faculty/index.html', context)

URL の /university/campus/ に対応するビューの関数が FacultyIndexView( ) から faculty_index( ) に変わったため,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:pk>/', views.ShowFacultyView.as_view(), name='faculty_show'),
    path('faculty/<int:pk>/update', views.UpdateFacultyView.as_view(), name='faculty_update'),
]

また,template/faculty/index.html で {% for faculty in object_list %} のところで object_list を使っていたが,views.py の19行目では context という辞書のキー 'faculties' にデータベースから取得した内容が格納されているため,template/faculty/index.html を変更する.

template/faculty/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Faculty一覧</title>
</head>
<body>
{% block content %}
<h1>学部一覧</h1>
<ul>
    {% for faculty in faculties %}
      <li>
        <a href="{% url 'university:faculty_show' faculty.id %}">
          {{ faculty.faculty }}は{{ faculty.established }}年に設置され,キャンパスは{{ faculty.campus }}です
        </a>
      </li>
    {% endfor %}
</ul>
{% endblock content %}

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.以前と同じ学部一覧ページが表示できるはずである.

python manage.py runserver ⏎

r-03-17

目次に戻る

Faculty 詳細ページ

次は Faculty の詳細ページを Campus の詳細ページと同じようにカスタマイズできるようにしてみよう.views.py において,ShowFacultyView クラスをコメントアウト(または削除)し,faculty_show 関数を定義するとともに,urls.py も編集する.なお,views.py では get_object_or_404 ショートカットがインポートされていることを確認する.

university/views.py(抜粋)
from django.urls import reverse_lazy
from django.shortcuts import render
from django.shortcuts import get_object_or_404 # 要確認

(中略)

def faculty_index(request):
    context = {}
    context['faculties'] = Faculty.objects.all()
    return render(request, 'faculty/index.html', context)

# class ShowFacultyView(DetailView):
#     model = Faculty
#     template_name = 'faculty/show.html'

def faculty_show(request, faculty_id):
    context = {}
    faculty = get_object_or_404(Faculty, pk=faculty_id)
    context['faculty'] = faculty
    return render(request, 'faculty/show.html', context)
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:pk>/update', views.UpdateFacultyView.as_view(), name='faculty_update'),
]

Webサーバを起動してブラウザで動作を確認する.以前と同じ学部詳細ページが表示できるはずである.また不正な faculty_id を指定した場合には期待通り 404 エラーが表示できるはずである.

python manage.py runserver ⏎

r-03-18

r-03-19

目次に戻る

Faculty 編集ページ

次は Faculty の編集ページをカスタマイズできるようにしてみよう.まずフォームを作成する.すでにある university/forms.py の中身をすべて削除し,次のような内容に変更する.

university/forms.py
from django import forms

class FacultyForm(forms.Form):
    faculty = forms.CharField(label='学部名', max_length=20)
    established = forms.IntegerField(label='設置年', min_value=1900, max_value=2100)
    campus = forms.IntegerField(label='キャンパスID')

views.py において,UpdateFacultyView クラスをコメントアウト(または削除)し,faculty_update 関数を定義するとともに,urls.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

(中略)

# class UpdateFacultyView(UpdateView):
#     model = Faculty
#     form_class = FacultyForm
#     success_url = reverse_lazy('university:faculty')
#     template_name = 'faculty/form.html'

def faculty_update(request, faculty_id):
    if request.method == 'POST':
        faculty = get_object_or_404(Faculty, pk=faculty_id)
        faculty.faculty = request.POST['faculty']
        faculty.established = request.POST['established']
        faculty.campus_id = request.POST['campus']
        faculty.save()
        return redirect(reverse('university:faculty'))
    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)
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'),
]

Webサーバを起動してブラウザで動作を確認する.以前と同じように学部情報の修正ができるはずである.

python manage.py runserver ⏎

なお,学部名は最大20文字以上入力することができなくなっており,設置年は1900〜2100という条件がすでに実現されていることもここで確認しておこう.

r-03-20

ただし,キャンパスID に 1, 2 以外の値を入力すると 500 エラーになる.この問題は修正しなければならない.

r-03-20-2

さらに,学部名の前後にスペースを入れて「(space)栄養学部(space)」のように入力して更新すると,データベースにホワイトスペースを含んだ状態で登録されてしまう.sqlite で確認してみる.

sqlite3 db.sqlite3 ⏎
sqlite> .headers ON ⏎ # 列名を表示するためのコマンド(省略しても良い)
sqlite> select * from university_faculty; ⏎
id|faculty|established|campus_id
1|  栄養学部  |1966|2
2|法学部|1967|2
3|経済学部|1967|1
4|薬学部|1972|2
5|人文学部|2004|1
6|経営学部|2004|1
7|総合リハビリテーション学部|2005|1
8|現代社会学部|2014|2
9|グローバル・コミュニケーション学部|2015|2
10|心理学部|2018|1
sqlite> .exit ⏎

上で挙げたようないくつかの問題を解決する.まず,university/forms.py にフィールドごとに clean_フィールド名 という関数を追加し,フォームに入力された値を検証 (validation) するためのコードを記述する.たとえば,学部名は20文字まで入力できるが,18文字以上でエラーにする.また設置年については1950〜2025の値しか入力できないようにする.さらに,キャンパスIDはデータベースを検索して登録済みの値以外は受け付けないようにする.

university/forms.py
from django import forms
from .models import Campus

class FacultyForm(forms.Form):
    faculty = forms.CharField(label='学部名', max_length=20)
    established = forms.IntegerField(label='設置年', min_value=1900, max_value=2100)
    campus = forms.IntegerField(label='キャンパスID')

    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

    def clean_campus(self):
        campus_id = self.cleaned_data['campus']
        campus = Campus.objects.filter(pk=campus_id)
        if campus:
            pass
        else:
            raise forms.ValidationError("キャンパスIDが不正です")
        return campus_id

さらに views.py を修正する.具体的には,django.views.generic を使わなくなったので,そのインポート文 (5行目) をコメントアウト(または削除)する.また,13行目の form.is_valid() によって入力内容の検証が行われるので,検証に成功すればデータベース上のモデルを更新する.このとき,request.POST['faculty'] ではなく form.cleaned_data.get("faculty")を利用することで,ホワイトスペースを削除できる.さらに,フォームの検証でエラーになった場合には23行目でエラーメッセージをつけて返すことになる.

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 django.views.generic import ListView, DetailView, UpdateView
from .forms import FacultyForm
from .models import Campus, Faculty

(中略)

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)

Webサーバを起動してブラウザで動作を確認する.

python manage.py runserver ⏎

学部名に18文字を入力したり,設置年に「1949」を入力したり,キャンパスIDに「3」を入力したりするとそれぞれエラーの表示ができるようになった.

r-03-21

目次に戻る

Faculty 編集ページでセレクトコントロールを使う

Faculty の編集ページでキャンパスIDを直接指定することは直感的ではない.セレクトコントロールを使ってキャンパスの一覧から選択できるようにしよう.

forms.py を編集する.このとき,campus のテキストボックスをコメントアウト(または削除)し,これに関連する clean_campus 関数もコメントアウト(または削除)する.さらに,キャンパスの一覧から選択できるようにするためには ModelChoiceField を利用する.

university/forms.py
from django import forms
from .models import Campus

class FacultyForm(forms.Form):
    faculty = forms.CharField(label='学部名', max_length=20)
    established = forms.IntegerField(label='設置年', min_value=1900, max_value=2100)
    # campus = forms.IntegerField(label='キャンパスID')
    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

    # def clean_campus(self):
    #     campus_id = self.cleaned_data['campus']
    #     campus = Campus.objects.filter(pk=campus_id)
    #     if campus:
    #         pass
    #     else:
    #         raise forms.ValidationError("キャンパスIDが不正です")
    #     return campus_id

Webサーバを起動してブラウザで動作を確認する.キャンパスを一覧から選択できるようになった.

python manage.py runserver ⏎

r-03-22

目次に戻る

複数のモデルをviewで扱う

ここでは views.py において複数のモデルを扱う方法を確認する.具体的には1つのページで Faculty モデルだけでなく Campus モデルも取得し,両方のモデルの一覧を表示する.

ここで説明したように,generic モジュールを使わずに記述すると,複数のモデルも容易に扱うことができる.views.py の最後に show_all 関数を追加して,Campus と Faculty の一覧を取得する.

university/views.py(抜粋)
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/ フォルダに show_all フォルダを作成し,この中に index.html ファイルを設置する.

university/templates/show_all/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Campus, Faculty一覧</title>
</head>
<body>
{% block content %}
<h1>キャンパス一覧</h1>
<ul>
    {% for campus in campuses %}
      <li>
        <a href="{% url 'university:campus_show' campus.id %}">
          {{ campus.campus }}
        </a>
      </li>
    {% endfor %}
</ul>

<h1>学部一覧</h1>
<ul>
    {% for faculty in faculties %}
      <li>
        <a href="{% url 'university:faculty_show' faculty.id %}">
          {{ faculty.faculty }}は{{ faculty.established }}年に設置され,キャンパスは{{ faculty.campus }}です
        </a>
      </li>
    {% endfor %}
</ul>
{% endblock content %}

</body>
</html>

また,/university/show_all/ という URL を有効にするために,university/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('show_all/', views.show_all, name="show_all"),
]

トップページに show_all へのリンクを追加する.

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="show_all/">
      すべての 一覧
    </a>
  </li>
</ul>

</body>
</html>

Webサーバを起動してブラウザで動作を確認する.キャンパスと学部の一覧をまとめて表示できるようになった.

python manage.py runserver ⏎

r-03-23

r-03-24