Djangoを勉強してみる_モデルを学ぶ_その8(入門編)

Pocket

前回の続きで、Djangoについて学んでいきます。

今回もモデルについて勉強していきます。

Djangoの勉強内容は以下の本を参考にしています。

参考

Python Django3超入門 (日本語) 単行本 – 2020/6/13 掌田 津耶乃 (著)

モデル
クエリを作成する

コードは以前の記事を参考にしてください。

Djangoを勉強してみる(入門編)
Djangoを勉強してみる(入門編その2)
Djangoを勉強してみる_urlを学ぶ(入門編)
Djangoを勉強してみる_テンプレートを学ぶ(入門編)
Djangoを勉強してみる_テンプレートを学ぶ_その2(入門編)
Djangoを勉強してみる_フォームを学ぶ_その1(入門編)
Djangoを勉強してみる_フォームを学ぶ_その2(入門編)
Djangoを勉強してみる_フォームを学ぶ_その3(入門編)
Djangoを勉強してみる_モデルを学ぶ_その1(入門編)
Djangoを勉強してみる_モデルを学ぶ_その2(入門編)
Djangoを勉強してみる_モデルを学ぶ_その3(入門編)
Djangoを勉強してみる_モデルを学ぶ_その4(入門編)
Djangoを勉強してみる_モデルを学ぶ_その5(入門編)
Djangoを勉強してみる_モデルを学ぶ_その6(入門編)
Djangoを勉強してみる_モデルを学ぶ_その7(入門編)

今回の目的

前回はデータベース中身から必要なものを検索する機能を勉強しました。

今回はデータベースのリレーションシップについて勉強していきます。

リレーションシップとは

リレーションシップとは、データベース間のリレーション(関係。つながり)のことです。

ちゃんとしたWebアプリケーションを作ろうと思うと、データベース間での連携が必要になってきます。

例えば、ブログの記事で考えると記事1つにつき、タグなどが複数ついていることはよくあることです。

例)記事:Djangoの勉強 ⇔ タグ:Django、勉強

この記事とタグのデータベースを関連付けすることがリレーションシップです。

リレーションシップの種類

リレーションシップには4種類あります。

【種類】

  1. 1対1対応:テーブルAのレコード1つに対して、テーブルBのレコード1つが紐づいているという関連付けです。
    例えば、リンゴ1つに対して、100円というような感じです。

  2. 1対多対応:テーブルAのレコード1つに対して、テーブルBのレコードが複数紐づいているという関連付けです。
    例えば、リンゴ1つに対して、複数の種類のリンゴ(ふじ、王林)のような感じです。

  3. 多対1対応:2番の逆になります。複数の種類のリンゴがリンゴという1つのカテゴリーに紐づいている感じです。
  4. 多対多:テーブルAの複数のレコード対して、テーブルBの複数のレコードに紐づいているという関連付けです。
    例えば、果物(リンゴ、桃、ミカン)というカテゴリーに対して、複数の種類(ふじ、王林)、(白鳳、桃山)、(あけぼの、甘夏)のような感じです。

Djangoでリレーションシップの使い方

まずは、1対多モデルのリレーションシップの使い方です。
Djangoのmodels.pyで次のように書きます。

1対多モデル

●主モデル(1側)

class A(models.Mode):

●従モデル(多側)

class B(models.Mode):
 項目 = models.ForeignKey(モデル名)

主モデル側はいつも通りです。
従モデル側の方に「models.ForeignKey」という項目を追加します。

「ForeignKey」とは、外部キーのクラスのことです。外部キーとはこのモデルに割り当てられているテーブル以外のテーブルのキーという意味です。

続いて1対1モデルのリレーションシップの使い方です。

1対1モデル

●主モデル

class A(models.Mode):

●従モデル

class B(models.Mode):
 項目 = models.OneToOneField(モデル名)

従モデル側の項目に「models.OneToOneField」を追加します。

最後に多対多モデルのリレーションシップの使い方です。

多対多モデル

●主モデル

class A(models.Mode):

●従モデル

class B(models.Mode):
 項目 = models.ManyToManyField(モデル名)

従モデル側の項目に「models.ManyToManyField」を追加します。

以上が、リレーションシップの使い方になります。

それでは実際にこの中で最もよく使われている、「1対多」をやりたいと思います。

今回のイメージは、ブログの投稿記事です。
一つのユーザ(主側)に対して、複数の記事(従側)を紐づけします。

それではやっていきます。

1対多のリレーションシップを作成する

まずは、複数の記事を作成するモデルを作成します。

モデルのイメージは次のようにします。

タイトル:記事のタイトル
コンテンツ:記事の中身
投稿日時:投稿した日時

1.models.pyを編集

ファイル:sample_app>hello>models.py

コード:

from django.db import models

class Friend(models.Model):
    name = models.CharField(max_length=100)
    mail = models.EmailField(max_length=200)
    gender = models.BooleanField()
    age = models.IntegerField(default=0)
    birthday = models.DateField()

    def __str__(self):
        return '<User:id=' + str(self.id) + ',' + self.name + '(' + str(self.age) + ')>'

#以下を追加----
class Message(models.Model):
    friend = models.ForeignKey(Friend, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.CharField(max_length=300)
    pub_date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return '<Message:id=' + str(self.id) + ', ' + self.title + '(' + str(self.pub_date) + ')>'

    class Meta:
        ordering = ('pub_date',)

従側のモデルにMessageというクラスを新規作成しました。

作り方は上で説明した通り「models.ForeignKey」を使って、Friendクラスに関連付けしています。

引数に使われている「on_delete=models.CASCADE」とは、主側のデータを削除した時に関連しているMessageクラス側のデータも併せて削除するという意味になります。

その他の項目はモデルのイメージになります。

あと見慣れない「ordering = (‘pub_date’,)」はpub_data順に並べる設定です。

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

モデルを新規作成したのでマイグレーションします。

python manage.py makemigrations hello
python manage.py migrate

マイグレーションが終わったら、管理サイトで管理できるようにします。

3.admin.pyを編集

ファイル:sample_app>hello>admin.py

コード:

from django.contrib import admin
from .models import Friend
from .models import Message #追加

admin.site.register(Friend)
admin.site.register(Message) #追加

ちゃんと管理できているか確認するなら、サーバを起動して管理サイトで確認してください。

このまま先に進みます。次はMessageクラスを利用するページを作成します。

4.forms.pyを編集

ファイル:sample_app>hello>forms.py

コード:

from django import forms
from .models import Friend
from .models import Message #追加

class FriendForm(forms.ModelForm):
    class Meta:
        model = Friend
        fields = ['name', 'mail', 'age', 'gender', 'birthday']

class FindForm(forms.Form):
    find = forms.CharField(label='Find', required=False)

#以下を追加-----
class MessageForm(forms.ModelForm):
    class Meta:
        model = Message
        fields = ['title', 'content', 'friend']

フォームとして、タイトル、コンテンツ、フレンズを準備しました。pub_dataは自動で設定されるため準備は不要です。

次はmessage関数を作ります。

5.views.pyを編集

ファイル:sample_app>hello>views.py

コード:

from .models import Friend, Message
from .forms import FriendForm, MessageForm
from django.core.paginator import Paginator

def message(request, page=1):
    if(request.method == 'POST'):
        obj = Message()
        form = MessageForm(request.POST, instance=obj)
        form.save()

    data = Message.objects.all().reverse()
    paginator = Paginator(data, 5)
    params = {
        'title': 'Message',
        'form': MessageForm(),
        'data': paginator.get_page(page),
    }
    return render(request, 'hello/message.html', params)

必要な部分だけを追加しています。

messageメソッドの内容としては、POSTが送られてきた時に、モデルのMessageクラスのインスタンスを作成して、POSTで送られてきた値をMessageFormに入れて保存しています。

GETの場合は、Messageモデルのすべてのデータをpub_data順に並べ替えていたものをreverseを使って逆順にしています。

そのデータを引数として、Paginatorインスタンスを作成しています。
Paginatorとは、データが多い場合に指定したデータ数毎にページを分割する機能のことです。今回の場合は、5データごとにページを分割するように指定しています。

Paginatorについて詳しく知りたい場合は以下を参照してください。

https://docs.djangoproject.com/ja/3.0/ref/paginator/

最後にmessage用のテンプレートを作成します。

6.message.htmlを新規作成

ファイル:sample_app>hello>templates>hello>message.html

コード:

<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">

    <title>{{ title }}</title>
</head>
<body class = "container">
    <h1>{{ title }}</h1>
    <form action="{% url 'message' %}" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="send" class="btn btn-primary">
    </form>
        <table class="table">
            <tr>
                <th>title</th>
                <th>name</th>
                <th>datetime</th>
            </tr>
        {% for item in data %}
            <tr>
                <td>{{ item.title }}</td>
                <td>{{ item.friend.name }}</td>
                <td>{{ item.pub_date }}</td>
            </tr>
        {% endfor %}
        </table>
        <ul class="pagination justify-content-center">
            {% if data.has_previous %}
            <li class="page-item">
                <a class="page-link" href="{% url 'message' %}">
                    &laquo; first</a>
            </li>
            <li class="page-item">
                <a class="page-link" href="{% url 'message' %}{{data.previous_page_number}}">
                    &laquo; prev</a>
            </li>
            {% else %}
            <li class="page-item">
                <a class="page-link">&laquo; first</a>
            </li>
            <li class="page-item">
                <a class="page-link">&laquo; prev</a>
            </li>
            {% endif %}
            <li class="page-link">
                <a class="page-link">
                    {{data.number}}/{{data.paginator.num_pages}}</a>
            </li>
            {% if data.has_next %}
            <li class="page-item">
                <a class="page-link" href="{% url 'message' %}{{data.next_page_number }}">
                    next &raquo;</a>
            </li>
            <li class="page-item">
                <a class="page-link" href="{% url 'message' %}{{data.paginator.num_pages }}">
                    last &raquo;</a>
            </li>
            {% else %}
            <li class="page-item">
                <a class="page-link">next &raquo;</a>
            </li>
            <li class="page-item">
                <a class="page-link">last &raquo;</a>
            </li>
            {% endif %}
        </ul>
</body>
</html>

結構長いコードですが、一番確認したいのは主モデルデータを取り出しているところです。

<td>{{ item.title }}</td>
<td>{{ item.friend.name }}</td>
<td>{{ item.pub_date }}</td>

このように、messageモデルには存在しない「name」のデータをfriendモデルから取り出すことができます。

これがデータベースのリレーションシップを使った時の効果になります。

以上で設定が終わったので、動作確認してみます。

動作確認

python manage.py runserver

ブラウザで以下にアクセスします。

http://localhost:8000/hello/message

message画面が表示されます。

 

次に以下のデータを入力して保存します。

Title:20200921サンプルタイトル
Content:20200921サンプルコンテンツ
Friend:<User:id=1,テストデータ1(36)>

nameがテストデータ1なのでわかりにくいですが、新しくブログの記事データを作成することができて、かつ一人のユーザに対して多数の記事データを紐づけることができています。

ちなみに、主モデル側でも従モデルのデータを取り出すことが可能です。

その場合は次のように記載します。

7.index.htmlを新規作成

ファイル:sample_app>hello>templates>hello>index.html

コード:

<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">

    <title>{{ title }}</title>
</head>
<body class = "container">
    <h1>{{ title }}</h1>
    <p>{{ msg|safe }}</p>
    <a href="{% url 'create' %}" class="btn btn-primary">新規作成</a>
    <table class="table">
        <tr>
            <th>ID</th>
            <th>NAME</th>
            <th>GENDER</th>
            <th>MAIL</th>
            <th>AGE</th>
            <th>BIRTHDAY</th>
            <th>MESSAGES</th> #追加
        </tr>
    {% for item in data %}
        <tr>
            <td>{{ item.id }}</td>
            <td>{{ item.name }}</td>
            <td>{% if item.gender == False %}male{% endif %}
                {% if item.gender == True %}female{% endif %}</td>
            <td>{{ item.mail }}</td>
            <td>{{ item.age }}</td>
            <td>{{ item.birthday }}</td>

#以下を追加--- <td><ul> {% for ob in item.message_set.all %} <li>{{ ob.title }}</li> {% endfor %} </ul></td>
#ここまで----
<td><a href="{% url 'edit' item.id %}">編集</a></td> <td><a href="{% url 'delete' item.id %}">削除</a></td> </tr> {% endfor %} </table> </body> </html>

このように、繰り返しのfor文を使って「message_set.all」をセットします。
「_set」とは逆引きするための名前として扱われます。
逆引きとは、ForeignKeyのような関連項目がない主テーブルのモデルクラス側から十テーブル側を取り出すための項目名のことです。

今回は従モデル側のtitleを表示するようにしています。

以上で設定が終わりなのでindex画面を見てみます。

 

MESSAGESという項目に、従モデル側のtitleを表示することができました。

以上でDjangoの基本の機能の勉強は終了です。
これまで学んだ機能を使ってWebアプリケーションを作成して記事に載せていきたいと思います。

本日はここまでです。ありがとうございました。

P.S.

勉強を継続することが苦手ですか?

少し前からココナラというサービスで習慣化のテクニックについて教えるサービスを始めました。

もし、いつも3日坊主で終わってしまうという方や、ダイエットを続けたい、勉強したい、運動したいなど何か習慣化したいと思っている方がいましたら全力でサポートしますので、まずは覗いてみてください。

すでに何人かの方に実践してもらって効果が出ているという感想をいただいてます。

人生変わる?【習慣化の方法】を教えます 【残り1名】ダイエット、勉強、運動を続けることが苦手ですか?

Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です