【Python/Django】OneToOneFieldを利用してUserモデルを拡張する

OneToOneFieldを利用したUserの拡張手順の説明ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django における「OneToOneField を利用した User の拡張」について解説をしていきます。

下記ページで解説しているとおり、OneToOneField フィールドは、2つのモデルクラスのインスタンスを1対1で関連付けるためのリレーションフィールドになります。

Djangoのリレーションの解説ページアイキャッチ 【Django入門7】リレーションの基本

この OneToOneField を利用するメリットの1つは、既存のモデルクラスを変更することなく、そのモデルクラスから新たなフィールドを利用できるよう拡張できる点になります。

関連付けによって、既存のモデルクラスを変更することなく利用可能なフィールドを追加する様子

今回は、この既存のモデルクラスとして User を扱い、この User を変更することなく OneToOneField で拡張する手順について説明していきます。

この User は、Django に標準で用意されたユーザーを管理するモデルクラスであり、この User を利用してユーザーのログイン等を実現することが可能です。このログインについては下記ページで解説していますので、興味があれば下記ページをご参照いただければと思います。

ログインの実現方法の解説ページアイキャッチ 【Django入門10】ログイン機能の実現

ただし、User にはユーザーを管理する際の一般的なフィールドしか存在しませんので、開発対象のウェブアプリ特有のフィールドが必要になるのであれば、そのフィールドを別途追加する必要があります。ですが、User は Django によって用意されている既に完成されたモデルクラスなので、User を直接変更することは非推奨です。そのため、別の方法でフィールドの追加を行う必要があります。

その方法の1つが、今回紹介する OneToOneField を利用した User の拡張になります。具体的には、User に追加したいフィールドを持つモデルクラスを定義し、そのモデルクラスのインスタンスと User とを関連付ける方法になります。これにより、User を変更することなく、User に擬似的にフィールドを追加することが可能となります。

ウェブアプリ独自のユーザー管理モデルを導入する方法2

このような、OneToOneField を利用した User の拡張手順について、このページで解説を行なっていきます。

ちなみに、ユーザーを管理するモデルクラスにフィールドを追加する方法としては、ユーザーを管理するモデルクラスとして別途 カスタムユーザー を定義し、その カスタムユーザー を開発対象のウェブアプリに応じてカスタマイズする方法も挙げられます。

ウェブアプリ独自のユーザー管理モデルを導入する方法1

これに関しては、下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。

モデルの解説ページアイキャッチ 【Django入門6】モデルの基本

OneToOneField を利用した User の拡張

では、OneToOneField を利用した User の拡張手順について解説していきます。

OneToOneField を利用した User の拡張手順 

OneToOneField は Django のリレーションフィールドの1つであり、この OneToOneField のフィールドをモデルクラスに定義することで、他のモデルクラスとの1対1の関連付けを実施することが可能となります。

このリレーションについては別途下記ページで解説していますので、リレーションについての詳細に関しては下記ページを参照していただければと思います。

Djangoのリレーションの解説ページアイキャッチ 【Django入門7】リレーションの基本

この OneToOneField を利用すれば、既存のモデルクラスを変更することなく、その既存のモデルクラスに擬似的に任意のフィールドを追加して拡張することが可能となります。

OneToOneField を利用して User の拡張を行う具体的な手順は下記のようになります。

  • User に追加したいフィールドを持つモデルクラスを新たに定義する
  • 定義したモデルクラスに、第1引数を User とする OneToOneField を定義する
  • 定義したモデルクラスのインスタンスと User のインスタンスを関連づける

まず、User に追加したいフィールドを持つモデルを models.py に新たに定義します。

OneToOneFieldを利用したUserの拡張手順の説明図1

次に、定義したモデルクラスに User を第1引数とする OneToOneField のフィールドを定義します。これにより、定義したモデルクラスのインスタンスと User のインスタンスが関連付け可能な状態となります。

OneToOneFieldを利用したUserの拡張手順の説明図2

また、リレーションフィールドを定義すれば、その定義先のモデルクラスには、その フィールド名 のデータ属性が追加されることになり、さらに、そのリレーションフィールドの第1引数に指定されたモデルクラスには、相手のモデルクラス名 のデータ属性が追加されることになります。

OneToOneFieldを利用したUserの拡張手順の説明図3

これらの追加されたデータ属性を利用することで、定義したモデルクラスのインスタンスと User のインスタンスを関連付けることが可能となります。例えば、User のインスタンスのデータ属性 相手のモデルクラス名 に定義したモデルクラスのインスタンスを参照させることで、これらのインスタンス同士が関連付けられることになります。

OneToOneFieldを利用したUserの拡張手順の説明図4

このように、2つのインスタンスを関連付けておけば、追加されたデータ属性を介して一方のインスタンスから他方のインスタンスのフィールドに値をセットしたり、フィールドから値を取得したりすることができるようになります。そのため、User の変更は行なっていないにも関わらず、User から他方のインスタンスのフィールドを利用することで、あたかも User にフィールドが追加されたように User のインスタンスを扱うことが可能となります。

OneToOneFieldを利用したUserの拡張手順の説明図5

以上が、OneToOneField を利用して User の拡張を行う手順の説明となります。

スポンサーリンク

OneToOneField で拡張した User の利用例

次は、OneToOneField を利用した User の拡張の利用例として、ユーザーの名前・パスワード・身長・体重を管理するウェブアプリの開発例を示していきたいと思います。

簡単な例となりますが、OneToOneField を利用した User の拡張の仕方や、拡張した  User の使い方のポイントは掴めると思いますので、是非ここからの解説も読み進めていただければと思います。

プロジェクトとアプリの作成

最初に、プロジェクトとアプリの作成を行なっていきます。この辺りはいつも通りの手順で実施すれば良いです。

具体的には、まず適当なフォルダに移動した後、下記のコマンドを実行して test_project を生成します(念のため言っておきますが、コマンドの先頭の % に関しては入力不要です)。

% django-admin startproject test_project

これにより、test_project というフォルダが生成されるので、このフォルダに移動を行い、続いて下記のコマンドを実行して test_app を作成します。

% python manage.py startapp test_app

以上でプロジェクトとアプリが作成できたことになります。

ただし、現状ではプロジェクトにアプリがまだ認識されていないため、test_project/settings.py 内の INSTALLED_APPS を下記のように変更してアプリの登録を行います。これにより、test_app がプロジェクトに登録されることになります。

アプリの登録
INSTALLED_APPS = [
    'test_app', # 追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

モデルクラスを定義する

続いて、test_app/models.py を変更してモデルクラスの定義を行なっていきます。

追加したいフィールドを持つモデルクラスの定義

まずは、User に追加したいフィールドを持つモデルクラスの定義を行なっていきます。

今回は、ユーザーの名前・パスワード・身長・体重を管理するアプリを開発していくわけですが、User には身長や体重を管理するフィールドは存在しません。そのため、新たにモデルクラスを定義し、そのモデルクラスに身長と体重を管理するフィールドを持たせるようにしていきます。具体的には、身長(cm)を示すフィールド height、体重(kg)を示すフィールド weight を持つ Profile というモデルクラスを定義していきたいと思います。

定義するモデルクラスProfile

これは、test_app/models.py を下記のように変更することで実現できます。

Profileの定義
from django.db import models

class Profile(models.Model):
    height = models.FloatField()
    weight = models.FloatField()

リレーションフィールドの定義

続いて、Profile に第1引数を User とするリレーションフィールドを定義し、Profile のインスタンスと User のインスタンスとを関連付けできるようにしていきます。

リレーションフィールドとしては OneToOneField を利用します。これにより、リレーションフィールドを定義することで Profile のインスタンスと User のインスタンスとは1対1の関連付けが行えるようになります。

ProfileにOneToOneFieldのフィールドを定義する様子

このために、test_app/models.py を下記のように変更します。

リレーションフィールドの定義
from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    height = models.FloatField()
    weight = models.FloatField()
    user = models.OneToOneField(User, on_delete=models.CASCADE)

このようにリレーションフィールドを定義すれば、User のインスタンスには profile というデータ属性が、Profile のインスタンスには user というデータ属性が追加され、これらのデータ属性を利用することで、インスタンスの関連付けであったり、関連したインスタンスの取得が行えるようになります。

この辺りの、リレーションに関する一般的な解説は下記ページで説明していますので、詳細に関しては下記ページを参照してください。

Djangoのリレーションの解説ページアイキャッチ 【Django入門7】リレーションの基本

また、OneToOneField には on_delete 引数の指定が必須となります。この on_delete 引数に関しては下記ページで解説していますので、詳細に関しては下記ページを参照してください。

【Python/Django】on_deleteについて分かりやすく解説

以上で、モデルクラスの定義は完了となります。

スポンサーリンク

フォームクラスを定義する

今回は、ユーザーの登録をフォームから実施できるようにしていきたいと思います。

フォームの表示方法等、フォームの扱い方等に関しては下記ページで解説していますので、詳しくは下記ページを参照していただければと思います。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

定義するフォームクラス

フォームでは下記の入力フィールドを表示してユーザーが各種データを入力できるようにしていきたいと思います。括弧内は、その情報を扱うモデルクラスのフィールド名を表しており、usernamepassword に関しては User で、heightweight に関しては Profile に定義されたフィールドとなります。

  • 名前(username
  • パスワード(password
  • 身長(height
  • 体重(weight

ちなみに、今回はパスワードは不要なのですが、Userpassword が必須フィールドととして定義されているため、フォームから入力受付を行うようにしたいと思います。ちなみに、ログイン機能等を実現する時には password の入力受付は必須となります。

また、今回は、単なるフォームクラスではなく、モデルフォームクラスを定義してフォームを扱うようにしていきたいと思います。下記ページで解説しているとおり、モデルフォームクラスとは ModelForm を継承するクラスであり、モデルフォームクラスでは model 属性で指定したモデルクラスをベースとしたフォームを定義することができます。具体的には、model 属性で指定したモデルクラスに定義されたフィールドと同じフィールドを持つフォームを定義することができます。

Djangoのモデルフォーム解説ページアイキャッチ 【Django入門8】モデルフォームの基本

ただし、今回は UserProfile の2つのモデルクラスを扱うため、モデルフォームクラスも2つ定義することになります。具体的には、User をベースとするモデルフォームクラスには usernamepassword の2つの入力フィールドを持たせ、Profile をベースとするモデルフォームクラスには height と weight の2つの入力フィールドを持たせるようにしていきます。

さらに、User 等のユーザー管理モデルクラスをベースとしてモデルフォームクラスを定義する場合は、ModelForm ではなく UserCreationForm を継承させる方が良いので(パスワードが自動的に暗号化される)、今回も User をベースとするモデルフォームクラスは UserCreationForm を継承して定義していきたいと思います。UserCreationForm もモデルフォームクラスの一種となるため、UserCreationForm を継承して定義したフォームもモデルフォームクラスと同様にして扱うことが可能です。

少し前置きが長くなりましたが、次は実際に上記のような2つのモデルフォームクラスを定義していきましょう。

forms.py

まずは、test_app フォルダの中に forms.py という名前のファイルを新規作成してください。そして、forms.py の中身を下記のように変更すれば、上記のような2つのモデルフォームクラスを定義することができます。

モデルフォームクラスの定義
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile

class UserForm(UserCreationForm):
    class Meta:
        model = User
        fields = ['username']

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['height', 'weight']

モデルフォームクラスでは、model 属性に指定したモデルクラスに定義されたフィールドのうち、fields で指定したフィールドのみを持つフォームが定義されることになります。なので、ProfileForm のインスタンスを出力すれば、heightweight の入力フィールドが表示されることになります。

UserForm では fields'username' しか指定していないので、username の入力フィールドしか表示されないようにも思えますが、UserCreationForm を継承すれば password の入力フィールドは自動的に追加されるようになっているため、UserForm のインスタンスを出力すれば usernamepassword の入力フィールド(確認用パスワードの入力フィールドを含む)が表示されることになります。さらに、password の入力フィールドへの入力文字列は伏せ字で表示されることになりますし、password の入力フィールドに入力されたパスワードはデータベース保存時に暗号化された状態で保存されることになります。この辺りは UserCreationForm を継承すれば自動的に UserCreationForm が行ってくれます。

2つのフォームの扱い方

ただし、上記のようにフォームクラスを定義した場合、usernamepassword の入力フィールドを表示するフォームと heightweight の入力フィールドを表示するフォームとが別々に作成されることになります。そのため、これら4つの入力フィールドを一緒に表示するためには、テンプレートファイルから、これらのフォームを一緒に出力する必要があります(確認用パスワードの入力フィールドも含めると5つの入力フィールドを含むフォームが出力されることになります)。

UserFormとProfileFormを同時に出力した様子

そのため、こういった2つのモデルフォームクラスのインスタンスを一緒に出力できるよう、テンプレートファイルやビューを作成していく必要があることになります。

ビューとテンプレートファイルを作成する

ここからは、ビューをとテンプレートファイルを作成していきたいと思います!

今回は、ウェブアプリで2つのページを表示できるようにしていきたいと思います。1つがユーザー登録フォームを表示するページで、もう1つが登録済みのユーザー一覧を表示するページになります。そのため、ここからは、これらのページを表示するためのビューとテンプレートファイルを作成していくことになります。

ユーザー登録フォームを表示するテンプレートファイル

最初に、ユーザー登録フォームを表示するページのテンプレートファイルを作成していきましょう!

まずは、テンプレートファイルの置き場所として test_app フォルダの下に templates フォルダを作成し、さらに templates フォルダの下に test_app フォルダを作成してください。

そして、最後に作成した test_app フォルダの中に register.html を新規作成し、そのファイルの中身を下記のように変更してください。

register.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <main class="container my-5 bg-light">
        <h1>ユーザー登録</h1>
        <form action="{% url 'register' %}" method="post">
            {% csrf_token %}
            <table class="table table-hover">
                <tbody>
                    {% for form in forms %}
                    {{ form }}
                    {% endfor %}
                </tbody>
            </table>
            <input type="submit" class="btn btn-primary" value="送信">
        </form>
    </main>
</body>

スタイルシートの読み込みなどを行っているので少し複雑なようにも感じるかもしれませんが、このテンプレートファイルでポイントになるのは下記の部分のみになります。

複数のフォームのインスタンスの出力
{% for form in forms %}
{{ form }}
{% endfor %}

前述の通り、今回は定義した2つのフォームクラスを一緒に出力して1つのフォームとして表示するようにしていきます。これは、ビューから、それらの2つのフォームのインスタンスを要素とするリストを受け取り、そのリストに含まれるインスタンスを全て出力することで実現することができます。

そのため、上記のコードのように、リスト(上記の場合は forms)に対して for ループを実施し、さらにそのリストに含まれるインスタンス(上記の場合は form)を1つ1つ出力するようにすれば、複数のフォームの出力が実現できることになります。

ただし、フォームのインスタンスのリストを register.html が受け取れるよう、ビューは、フォームのインスタンスのリストがセットされたコンテキストを作成する必要があります。

ユーザー登録フォームを扱うビュー

ということで、その点を考慮しながら、次はビュー側を作成していきます。

このビューはフォームを扱うビューとなりますので、基本的には下記ページの ビューでフォームクラスを扱う で説明している通りに実装すれば良いだけになります。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

ただし、先ほど説明したように、コンテキストには2つのフォームのインスタンス、具体的には UserForm のインスタンスと ProfileFormのインスタンスを要素とするリストを 'forms' キーの値としてセットしておく必要があります。

また、表示されるフォームでは UserForm のフィールドと ProfileFormのフィールドの両方の値が入力されるわけですから、その送信されてくるデータには UserForm のフィールドの値と ProfileFormのフィールドの値が含まれることになります。そういったデータから、UserForm のインスタンスと ProfileForm のインスタンスを生成して妥当性の検証を行う必要がありますし、これらのモデルフォームクラスのインスタンスから、ベースとなるモデルクラスのインスタンス、具体的には User と Profileのインスタンスを取得し、さらには、それらのインスタンスの関連付けも必要となります。

これらの全てを満たす、ユーザー登録フォームを扱うビューは、下記のような register_view 関数となります。この register_view は、test_app/views.py に定義する必要があります。 

register_view
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from .forms import UserForm, ProfileForm

def register_view(request):
    if request.method == 'POST':
        # 受信データからUserFormのインスタンス生成
        user_form = UserForm(request.POST)

        # 受信データからProfileFormのインスタンス生成
        profile_form = ProfileForm(request.POST)

        forms = [user_form, profile_form]

        if user_form.is_valid() and profile_form.is_valid():
            # 受信データが妥当な場合

            # フォームの各種フィールドに入力された値から
            # UserとProfileのインスタンス生成
            user = user_form.instance
            profile = profile_form.instance

            # インスタンスを関連付け
            user.profile = profile

            # インスタンスをレコードとして保存
            user.save()
            profile.save()

            # ユーザー一覧ページに遷移
            return redirect('users')


    else:
        # フィールドが空のUserFormのインスタンス生成
        user_form = UserForm(request.POST)

        # フィールドが空のProfileFormのインスタンス生成
        profile_form = ProfileForm(request.POST)

        forms = [user_form, profile_form]

    # 2つのフォームのインスタンスのリストをコンテキストにセット
    context = {
        'forms': forms
    }

    return render(request, 'test_app/register.html', context)

前述の通り、この register_view の基本的な処理の流れは、下記ページの ビューでフォームクラスを扱う で説明した内容の通りになっています。

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

ただし、これも前述の通り、コンテキストの値としてリストをセットする点や、インスタンス同士の関連付けが必要となる点は注意が必要となります。

前者に関しては、forms 変数にリストを参照させるあたりの処理を見ていただければ、どういった処理が必要であるかについて理解していただけると思いますので、説明を省略します。

後者に関しても、request.method == 'POST' が成立した時の処理を確認していただければ、どういった処理を実行すれば実現できるのかを理解していただけるのではないかと思います。が、念の為、ポイントを3点説明しておきます。

ポイントの1つ目は、下記のフォームのインスタンスを生成している箇所となります。

フォームのインスタンスの生成
# 受信データからUserFormのインスタンス生成
user_form = UserForm(request.POST)

# 受信データからProfileFormのインスタンス生成
profile_form = ProfileForm(request.POST)

今回、1つのフォームに UserForm のフィールドと ProfileForm のフィールドの両方を表示するようになっていますので、フォームのボタンクリック時にウェブアプリに送信されてくるデータにも、UserForm のフィールドに入力された値と ProfileForm のフィールドに入力された値の両方が含まれることになります。

このようなデータであっても、request.POST を引数として UserForm() を実行すれば UserForm のフィールドに入力された値を反映した UserForm のインスタンスが、request.POST を引数として ProfileForm() を実行すれば ProfileForm のフィールドに入力された値を反映した ProfileForm のインスタンスをそれぞれ生成することが可能です。

複数のフォームクラスのフィールドを含むフォームから受信したデータからの各フォームクラスのインスタンスの生成方法についての説明図

なので、もちろん実行するコンストラクタの数は増えることになりますが、1つのフォームに複数のフォームクラスのフィールドを含ませて表示させたとしても、基本的には1つのフォームクラスのフィールドから構成されるフォームと同様の手順でフォームクラスのインスタンスは生成可能です。ただし、フォームから受信したデータを扱うためには妥当性の検証を実施する必要がありますので、複数のフォームクラスのインスタンスを生成するのであれば、それら全てに対して is_valid メソッドを実施させる必要があることに注意してください。

ポイントの2つ目は、モデルフォームクラスのインスタンスからの、そのベースとなるモデルクラスのインスタンスの取得方法になります。is_valid メソッドを実行して妥当性の検証結果が OK と判断されたモデルフォームクラスのインスタンスからは、instance データ属性より、そのモデルフォームクラスのベースとして指定されたモデルクラスのインスタンスが取得可能となります。このインスタンスの各種フィールドには、モデルフォームクラスのインスタンスのフィールドにセットされている値がそのまま反映されていることになります。

モデルフォームクラスからのモデルクラスの取得

このように、フォームのインスタンスからモデルクラスのインスタンスを容易に取得可能であるという点もモデルフォームクラスの特徴の1つとなります。そして、この手順で User のインスタンスと Profile のインスタンスを取得しているのが下記部分となります。

インスタンスの取得
# フォームの各種フィールドに入力された値から
# UserとProfileのインスタンス生成
user = user_form.instance
profile = profile_form.instance

3つ目のポイントが、上記で取得した User のインスタンスと Profile のインスタンスとの関連付けになります。この Profile は、あくまでも User にフィールドを追加するために定義したモデルクラスなので、User のインスタンスと Profile のインスタンスを関連付けないと意味がありません。また、モデルクラスを定義する で説明したように、Profile にリレーションフィールドを定義することで、User のインスタンスにはデータ属性 profile が追加されることになります。そして、そのリレーションフィールドが OneToOneField の場合、リレーションフィールドの定義によって追加されたデータ属性で相手を参照させることで、参照元と参照先のインスタンスを関連付けることができます。

で、この関連付けと、その関連付けしたインスタンスのデータベースへの保存を行なっているのが下記部分となります。

インスタンスの関連付け
# インスタンスを関連付け
user.profile = profile

# インスタンスをレコードとして保存
user.save()
profile.save()

地味ですが、上記の user.save()profile.save() の実行順序も重要となります。これらの実行順序が逆になると例外が発生し、データベースへの保存に失敗することになります。この理由は、下記ページの プライマリーキー確定後に関連付けを行う必要がある で解説しているので、詳細を知りたい方は下記ページを参照していただければと思います。

Djangoのリレーションの解説ページアイキャッチ 【Django入門7】リレーションの基本

ユーザー一覧を表示するテンプレートファイル

続いて、ユーザー一覧を表示するページを実現していきましょう!具体的には、データベースに保存されている全ユーザーの名前・身長・体重の一覧を表示するページを実現していきたいと思います。

ポイントは、身長・体重の表示になります。これらの身長・体重を扱うフィールドを User のインスタンスは持っていません。ですが、User のインスタンスに関連付けられた Profile のインスタンスが height フィールドと weight フィールドを持っていますので、User のインスタンスから、関連付けられた Profile のインスタンスの height フィールドと weight フィールドの値を取得してやれば、ユーザーの名前に合わせて身長・体重も表示することができることになります。

また、関連付けられたインスタンスのフィールドの値の取得は、関連付けを行うリレーションフィールドが OneToOneField である場合、リレーションフィールドの定義によって追加されたデータ属性を利用し、インスタンス.データ属性.フィールド名 によって取得可能となります。

具体的には、User のインスタンスを user とすれば、user.profile.height で身長の値、user.profile.weight で体重の値が取得できることになります。

Userのインスタンスからの身長と体重の取得方法

なので、テンプレートファイルは、全 User のインスタンスを要素とするリストさえビューから受け取れば、その User から取得した1つ1つのインスタンスに設定された名前・身長・体重を出力することができることになります。で、このような出力を行うテンプレートファイルが下記となります。このファイルは、前述でテンプレートファイルの置き場として作成した test_app フォルダの中に、users.html という名前で保存してください。

users.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <main class="container my-5 bg-light">
        <h1>ユーザーの身長と体重</h1>
        <table class="table table-hover">
            <tbody>
                <tr>
                    <th>名前</th>
                    <th>身長</th>
                    <th>体重</th>
                </tr>
                {% for user in users %}
                <tr>
                    <td>{{ user.username }}</td>
                    <td>{{ user.profile.height }}</td>
                    <td>{{ user.profile.weight }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </main>
</body>

実際に、各ユーザーの名前・身長・体重の出力を行なっている箇所は下記となります。UserOneToOneField を利用して拡張する場合、User のインスタンスと他のモデルクラスのインスタンスとの関連付けも必要ですし、何らかの機能を実現するためには、下記のように User のインスタンスに関連付けられた相手のフィールドの値の取得も必要となります。

名前と身長と体重の出力
{% for user in users %}
<tr>
    <td>{{ user.username }}</td>
    <td>{{ user.profile.height }}</td>
    <td>{{ user.profile.weight }}</td>
</tr>
{% endfor %}

ユーザー一覧を表示するビュー

次は、ユーザー一覧を表示するビューの定義を行います。このビューに関しては、一般的なインスタンスの一覧の表示と同様の実装で実現できます。

具体的には、下記のように views.py を変更して users_view を定義してやれば良いだけになります。

views.py
from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from .forms import UserForm, ProfileForm

def register_view(request):
    if request.method == 'POST':
        # 受信データからUserFormのインスタンス生成
        user_form = UserForm(request.POST)

        # 受信データからProfileFormのインスタンス生成
        profile_form = ProfileForm(request.POST)

        forms = [user_form, profile_form]

        if user_form.is_valid() and profile_form.is_valid():
            # 受信データが妥当な場合

            # フォームの各種フィールドに入力された値から
            # UserとProfileのインスタンス生成
            user = user_form.instance
            profile = profile_form.instance

            # インスタンスを関連付け
            user.profile = profile

            # インスタンスをレコードとして保存
            user.save()
            profile.save()

            # ユーザー一覧ページに遷移
            return redirect('users')


    else:
        # フィールドが空のUserFormのインスタンス生成
        user_form = UserForm(request.POST)

        # フィールドが空のProfileFormのインスタンス生成
        profile_form = ProfileForm(request.POST)

        forms = [user_form, profile_form]

    # 2つのフォームのインスタンスのリストをコンテキストにセット
    context = {
        'forms': forms
    }

    return render(request, 'test_app/register.html', context)

def users_view(request):
    # Userのインスタンスを全て取得
    users = User.objects.all()

    # Userのインスタンスのリストをコンテキストにセット
    context = {
        'users': users
    }

    return render(request, 'test_app/users.html', context)

URL とビューの関数のマッピング

最後に、ここまで作成してきたビューの関数が、クライアントからのリクエストに応じて実行されるよう、urls.py で URL とビューの関数とのマッピングを行なっていきます。

まず、test_project/urls.py を下記のように変更します。

test_project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('test/', include('test_app.urls'))
]

続いて、test_app/urls.py を新規に作成し、中身を下記のように変更します。

test_app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('register/', views.register_view, name='register'),
    path('users/', views.users_view, name='users')
]

これにより、URL が /test/register/ のリクエストを受け取った時には register_view が実行されてユーザー登録フォームが表示され、さらに URL が /test/users/ のリクエストを受け取った時には users_view が実行されてユーザー一覧が表示されるようになります。

以上で、ユーザーの名前・パスワード・体重・身長を管理するウェブアプリの完成となります。ポイントは、今回は User を一切変更することなく、ユーザーの体重・身長を管理を実現したという点になります。このようにOneToOneField を利用することで、既存のモデルクラスを変更することなく、擬似的な拡張を行うことが可能となります。

Userを変更することなくheightとweightがUserから利用になった様子

既存のモデルクラスを変更できない・変更したくないような場面では、この OneToOneField の利用が有効ですので、是非 OneToOneField での既存のモデルクラスの拡張の仕方については覚えておいてください!

スポンサーリンク

まとめ

このページでは、Django における「OneToOneField を利用した User の拡張」について解説しました。

OneToOneField を利用することで、2つのモデルクラスのインスタンス同士を1対1で関連付けすることができるようになります。そして、これを利用することで、User を変更しなくても User から他のモデルクラスのフィールドを参照することができ、User から新たなフィールドを扱うことができるようになります。

今回は User を拡張することを目的とした解説になりましたが、もちろん他のモデルクラスを変更することなく拡張したいような場合にも利用できます。品質などを考慮すると、既存のモデルクラスは変更せず、そのまま利用したくなることも多いです。そんな時に、この OneToOneField を利用した拡張方法が有効となりますので、この拡張方法については是非覚えておきましょう!

同じカテゴリのページ一覧を表示