Djangoチュートリアル4-モデルの定義-

Djangoチュートリアル4-モデルの定義-

Djangoチュートリアル4-モデルの定義-

この記事は部内での講習会用に作成された記事です。
前回まではDJangoの組み込みのユーザーモデルを使用して、ユーザー登録機能を実装しました。

今回はDjango側が用意したモデルではなく自分たちで記事モデルを用意し、ユーザーがブログ記事を投稿できるようにします🚀

前回までのチュートリアルの続きとなっていますので、まだ前回までのチュートリアルを見ていない方は、以下のリンクから前回の記事を確認してください。

Djangoチュートリアル3-ユーザー登録-

Djangoでのユーザー登録・認証の方法について紹介します。


記事のモデルを作成する

データベースで特定のデータを保存するためには、どういった構造のデータを管理するかを決めます。   この構造のことをモデルと呼びます。

モデルをDBで使えるようにするには以下の手順で進めていきます。

  • 1.モデルの定義(models.py)
  • 2.マイグレーションファイルの作成
  • 3.マイグレーションの実行

モデルの定義

今回はブログ記事を保存するためのモデル(articleモデル)を定義します。

モデルを定義する前にまず、どういった情報を記事として保存するかを考えます。(要件定義・テーブル設計など)

簡単な記事を想定しているので、以下の情報を記事として保存することにします。

  • タイトル(title)
  • 本文(content)
  • 作成日時(created_at)
  • 更新日時(updated_at)
  • 作成者(author)
  • 公開状態(is_published)

ここでポイントとなるのは、authorという項目です。
これはログインして投稿したユーザーがここに紐づけされます。

よってユーザーテーブルと記事テーブルが紐づけられる関係になります。
今回の関係は一人のユーザーが複数の記事を投稿できるという関係なので、**1対多*という関係になります。

こういった関係をリレーションと呼びます。
専門的な図(ER図)で表すと以下のようになります。(フィールドなどは省略しています)

画像

では実際にモデルを定義していきます。
モデルは各アプリのmodels.pyに記述します。

今回はメイン機能なのでappアプリを使っていきます!
app/models.pyに書いていきます。

from django.db import models
from django.conf import settings 
 
 
class Article(models.Model): 
    title = models.CharField("タイトル", max_length=100) 
    content = models.TextField("内容") 
    created_at = models.DateTimeField("作成日時", auto_now_add=True) 
    updated_at = models.DateTimeField("更新日時", auto_now=True) 
    author = models.ForeignKey( 
        settings.AUTH_USER_MODEL,  
        verbose_name="投稿者", 
        on_delete=models.CASCADE
    ) 
    def __str__(self): 
        return self.title 

上のコードの解説をしていきます。

  • CharField‥文字列を保存するためのフィールド(文字数制限あり)
  • TextField‥文字列を保存するためのフィールド(文字数制限なし)
  • DateTimeField‥日時を保存するためのフィールド
    • auto_now_add=True‥初回作成時に自動で現在日時を設定
    • auto_now=True‥更新時に自動で現在日時を設定
  • ForeignKey‥他のモデルと紐づけられる部分(今回はUserと紐づけ)
    • settings.AUTH_USER_MODEL‥認証で使われているモデルを参照している
    • on_delete=models.CASCADE‥紐づけられたユーザーが削除された場合、紐づけられた記事も削除される

マイグレーション

モデルを定義したら、DBに読み込ませる(テーブルを作る)ためにマイグレーションを行います。

マイグレーションは以下のコマンドで行います。

python manage.py makemigrations

このコマンドを実行すると、migrationsフォルダにマイグレーションファイルが作成されます。 マイグレーションファイルは現在定義されているテーブルの状態を記録したファイル(設計図)。
作られたマイグレーションファイルはaccounts/migrationsフォルダに保存されます。

マイグレーションファイルが作成されたら、次にマイグレーションを実行してDBに反映させます。

python manage.py migrate

このコマンドによってDjangoはマイグレーションファイルを読み込み、DBにテーブルを作成します。

この作業はモデルを作成した時だけでなく、モデルを変更したときにも行います。

このマイグレーションファイルがDBの設計図の役割を果たしているのがDjangoの特徴的な部分です。このマイグレーションファイルを他の開発者に渡すだけで、 渡された開発者はマイグレーションファイルを読み込むだけで同じ環境のDB構造を実現できます。相手に渡すときはGitHub経由などで渡されます。

モデルの確認

マイグレーションが正常にできているかを確認するために、Djangoの管理画面からDBの状態確認してみます。
ですがその前に管理画面にArticleテーブルを登録させる必要があります。

管理画面に登録するためには、app/admin.pyに以下のように記述します。

from django.contrib import admin
from .models import Article 
 
admin.site.register(Article) 

これで管理画面にArticleモデルが登録されました。

次に管理画面にアクセスして、Articleモデルが登録されているかを確認します。 アクセスするためにpython manage.py runserverを実行し、http://127.0.0.1:8000/admin/にアクセスしてください。

画像

追加されているのが確認できます。 試しにいくつかの記事を管理画面から登録してみてください!

これで記事モデルの定義とDBへの登録が完了しました🚀
ここまでがDBを使うための基本的な流れです。


一覧ページ

記事の準備ができたので、次は記事の一覧ページを作成していきます。

一覧ページもメイン機能なのでappアプリ内に作っていきましょう!

新しいページの追加流れは

    1. HTMLファイルの作成
    1. ビューの作成
    1. URLの設定

HTMLファイルの作成

まずは新しいページ用のHTMLファイルを用意しましょう! app/templates/appフォルダの中にall_read.htmlを作成します。(index.htmlと同じ位置です)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
    rel="stylesheet"
    />
</head>
<body>
 
    <nav class="navbar bg-body-tertiary">
        <div class="container">
            <a class="navbar-brand" href="/">ブログサイト</a>
 
            <ul class="navbar-nav  flex-row gap-3">
                <li class="nav-item"><a class="nav-link" href="{% url 'signup' %}">新規登録</a></li>
                {% if user.is_authenticated  %}
                <li class="nav-item"><a class="nav-link" href="{% url 'mypage' user.username %}">マイページ</a></li>
                <li class="nav-item">
                    <form action="{% url 'logout' %}" method="post">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-link nav-link">ログアウト</button>
                    </form>
                </li>
                
                {% else %}
                <li class="nav-item"><a class="nav-link" href="{% url 'login' %}">ログイン</a></li>
                {% endif %}
            </ul>
        </div>
    </nav>
    
 
    <div class="container mt-5 px-5">
        <h1>記事一覧</h1>
    </div>
</body>
</html>

ビューの作成

HTMLの準備ができたので、次はビューを作成していきます。 ビューはapp/views.pyに記述します。 今までのapp/views.py一番下に新しく追加してください。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
 
 
def index(request):
    return render(request, "app/index.html")
 
def mypage(request, username):
    profile_user  = get_object_or_404(User, username=username)
    
    context = {"profile_user": profile_user }
    return render(request, "app/mypage.html", context=context)
 
def all_read(request): 
    return render(request, "app/all_read.html") 
    

一旦はHTMLを表示するだけのビューを作成しました。


URLの設定

次にURLの設定を行います。

URLの設定はapp/urls.pyに記述します。
urlpatternsリストの中に新しく一覧用のURLを追加します。

urlpatterns = [
    path("", views.index, name="index"),
    path("mypage/<str:username>/", views.mypage, name="mypage"),
    path("all_read/", views.all_read, name="all_read") 
]
リスト型なので追加するときは各データの間にカンマ(,)を忘れないようにしてください。

このページに移動しやすくするためにホームページから移動できるようにしておきましょう。 index.htmlの一番下にある記事の一覧をみるボタンを少し変更します。

‥‥
<div class="container mt-5">
        <div class="d-flex flex-column align-items-center text-center gap-4">
            <h1 class="fw-bold display-3 ">ブログサイトへようこそ</h1>
            <p class="fs-5 text-muted">このブログサイトはDjangoで作られているブログサイトです。<br/>ユーザーごとに自分の記事を投稿して全ユーザーに共有することができます。</p>
            <a href="{% url 'all_read' %}" class="btn btn-secondary mt-4">記事の一覧をみる</a>
        </div>
</div>
‥‥

これでホームページのボタンを押すと記事一覧ページに移動できるようになります。


記事データを取得する

記事一覧ページの準備ができました!
ですが今はまだ記事が表示されていません。

記事一覧を表示するためにはDBに保存されている記事データを取得して、HTMLに渡す必要があります。
DBのデータを取得してHTMLに渡すのはビューのお仕事です。

先ほどのapp/views.pyの一番上にインポート文all_readにDBを操作する命令を書いてください。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from .models import Article 
 
 
def index(request):
    return render(request, "app/index.html")
 
def mypage(request, username):
    profile_user  = get_object_or_404(User, username=username)
    
    context = {"profile_user": profile_user }
    return render(request, "app/mypage.html", context=context)
 
def all_read(request):
    articles = Article.objects.all().order_by("-created_at") 
    context = {"articles": articles} 
 
    return render(request, "app/all_read.html", context=context)
  • from .models import Article‥定義していたArticleのテーブルを読み込み
  • articles = Article.objects.all()‥DBに保存されている全てのArticleデータを取得して変数に格納
    • order_by("-created_at")‥取得したデータを**作成日時の降順(新しい順)**で並び替え(ソート)
DBのデータ取得などの操作には通常SQLという言語で命令文を記述してDBと通信します。ですがDjangoはORMという機能が標準で組み込まれています。 これは大雑把に言えばDBをPythonのコードをSQLに自動で変換してくれる機能です。なのでDjangoでは難しいSQL文を使用せずに慣れ親しんだPythonの簡単な記述だけでDBを操作できます。 実はテーブル作成なども今までPythonで書いていましたが本当はSQLで書く必要があったのです(ORMのおかげ)

記事一覧を表示する

ビューのおかげで記事データを取得できたので、あとはHTMLに表示するだけです。
すでにcontextによってall_read.htmlにデータは渡されています。

なのでHTML側で表示するためのコードを書きましょう! containerを以下のように変更します

    <div class="container mt-5 px-5">
        <h1>記事一覧</h1>
         {% for article in articles %} 
        <div class="card mb-4 mt-4 shadow-sm"> 
        <div class="card-body"> 
            <h4 class="card-title">{{ article.title }}</h4> 
            <p class="card-subtitle text-muted mt-1">投稿者: {{ article.author.username }} | 投稿日: {{ article.created_at|date:"Y年m月d日" }}</p> 
        </div> 
        </div> 
        {% empty %} 
            <p>記事が見つかりませんでした</p> 
        {% endfor %} 
    </div>
  • {% for article in articles %}for文です。Djangoの機能でHTMLの中でfor文が使えています。
    • articlesの中にあるデータを一つずつ取り出しcardとして表示させています。
  • {{ article.title }}‥記事データのtitleを取得して表示
  • {{ article.author.username }}‥記事データのauthorusernameを取得して表示
  • {{ article.created_at|date:"Y年m月d日" }}‥記事データのcreated_atY年m月d日の形式で表示(おまじない)
  • {% empty %}‥for文の中でデータが一つもない場合に表示される部分

for文の応用によって記事の一覧を表示させています。
慣れないうちは何をしているのかわかりずらいと思います。今は取得したデータを全て表示させるには*for文を使うということだけ覚えておくでもOKです!

また、繰り返しているのがHTMLのコードなので違和感があると思います。やっていることは以下のようなことです。

for article in articles:
    print(article.title)
    print(article.author.username)
    print(article.created_at)

このprintの部分をHTMLで書いているだけです。


詳細表示

記事の一覧表示ができました!
次はそれらの記事にアクセスして実際に記事を閲覧できるようにします。

ここで重要となるのは記事ごとに表示する内容を変えるということです。
これはユーザーのマイページと同じように動的なURLの設定で実現します。

ここでやることは

    1. 詳細表示用のHTMLファイルの作成(ひな形)
    1. URLの設定(urls.py)
    1. ビューの作成(views.py)

詳細表示用のHTMLファイルを作成する

まずは記事を表示するおおもとのHTMLファイルを作成します。
記事関連の機能はappアプリで実装しているのでapp/templates/app/read.htmlとして作成します。(index.htmlと同じ位置)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
    rel="stylesheet"
    />
</head>
<body>
 
    <nav class="navbar bg-body-tertiary">
        <div class="container">
            <a class="navbar-brand" href="/">ブログサイト</a>
 
            <ul class="navbar-nav  flex-row gap-3">
                <li class="nav-item"><a class="nav-link" href="{% url 'signup' %}">新規登録</a></li>
                {% if user.is_authenticated  %}
                <li class="nav-item"><a class="nav-link" href="{% url 'mypage' user.username %}">マイページ</a></li>
                <li class="nav-item">
                    <form action="{% url 'logout' %}" method="post">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-link nav-link">ログアウト</button>
                    </form>
                </li>
                
                {% else %}
                <li class="nav-item"><a class="nav-link" href="{% url 'login' %}">ログイン</a></li>
                {% endif %}
            </ul>
        </div>
    </nav>
    
 
    <div class="container mt-5 px-5">
    </div>
</body>
</html>

とりあえずは大枠だけ作成しました。詳しい実装は後で行います。


URLの設定

マイページの時と同様に記事を一意に特定できる要素を変数としてURLの中に入れます! 今回はIDフィールドを使用します。

このIDフィールドはDjangoが自動で作成してくれる主キーと呼ばれるフィールドです。(0からの連番で自動で振られる)

app/urls.pyに以下のようにURLを追加します。

from django.urls import path
from . import views
 
urlpatterns = [
    path("", views.index, name="index"),
    path("mypage/<str:username>/", views.mypage, name="mypage"),
    path("all_read/", views.all_read, name="all_read"),
    path("read/<int:article_id>/", views.read, name="read"), 
]

数字なのでint型で指定しています。
このIDフィールドは自分でもカスタマイズでき一般的にはUUIDなどが使用されます。

この段階ではまだビューでread関数を設定をしていないのでエラーが出るはずです!

ビューの作成

ビューを作成していきます。
ここでのビューのお仕事は

    1. URL内のIDの値を取得する
    1. そのIDに紐づく記事データをDBから取得する
    1. 取得した記事データをHTMLに渡す

app/views.pyに詳細用ページのビューを追加します。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from .models import Article
 
 
def index(request):
    return render(request, "app/index.html")
 
def mypage(request, username):
    profile_user  = get_object_or_404(User, username=username)
    
    context = {"profile_user": profile_user }
    return render(request, "app/mypage.html", context=context)
 
def all_read(request):
    articles = Article.objects.all().order_by("-created_at")
 
    context = {"articles": articles}
    return render(request, "app/all_read.html", context=context)
 
def read(request, article_id): 
    article = get_object_or_404(Article, id=article_id)  
    context = {"article": article}  
    return render(request, "app/read.html", context=context) 

やっていることはマイページの時と同じです!


詳細ページの表示

最後に記事のデータが渡されているのでHTML側で表示をしましょう!

app/templates/app/read.htmlに以下のように記述します。

‥‥
<div class="container mt-5 px-3">
    <div class="mx-auto" style="max-width: 800px;"> 
        <h1 class="mb-3">{{ article.title }}</h1> 
        <div class="text-muted small mb-4 d-flex flex-wrap gap-3"> 
            <div>作成日: {{ article.created_at|date:"Y年m月d日 H:i" }}</div> 
            <div>投稿者: {{ article.author.username }}</div> 
        </div> 
        <hr /> 
        <div class="article-content fs-5 lh-lg"> 
            {{ article.content|linebreaksbr }} 
        </div> 
    </div> 
</div>
‥‥

最後に記事一覧ページ(all_read.html)の各記事のタイトルをクリックしたときに詳細ページに移動できるようにします。 all_read.htmlを編集します。

各記事をタッチしたらその記事のIDでURLを生成するので、各記事のcardタグをaタグで囲ってしまえばOKです!

<div class="container mt-5 px-5">
    <h1>記事一覧</h1>
    {% for article in articles %}
    <a href="{% url 'read' article.id %}" class="text-decoration-none text-dark"> 
        <div class="card mb-4 mt-4 shadow-sm cursor-pointer">
            <div class="card-body">
                <h4 class="card-title">{{ article.title }}</h4>
                <p class="card-subtitle text-muted mt-1">
                    投稿者: {{ article.author.username }} |
                    投稿日: {{ article.created_at|date:"Y年m月d日" }}
                </p>
            </div>
        </div>
    </a> 
    {% empty %}
        <p>記事が見つかりませんでした</p>
    {% endfor %}
</div>

これで各記事の詳細ページにもアクセスできました🚀

今回はモデルの作成からDBを操作してデータの取得などを行いました!
次回は投稿や削除・更新などを行いたいと思います!


📚 参考資料

Django | クエリを作成する

https://docs.djangoproject.com/ja/5.2/topics/db/queries/

他のStringを探す