haku-maiのブログ

インフラエンジニアですが、アプリも作ります。

【Django3.1】Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整える。

本記事で行うこと

  • Djangoで作成したアプリケーションにBootstrap5を導入し、見た目を整える。
  • 見た目を整える前のサンプルアプリケーションは以下で作成したcodeを利用する。
    n-guitar.hatenablog.com

なお本記事のソースコードgithub上に公開しています。
GitHub - n-guitar/django-samples at feature/bootstrap5/sample.generic-view

モチベーション

  • Bootstrap5-bata1が出たので使って見たかった。
  • Djangoを教える時のサンプルとして利用したいため。

環境

$ sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69
$ python -V
Python 3.8.0

Bootstrap5導入

djangoでbootstrapの導入方法いくつかある。

  1. bootstrapモジュールをダウンロードし、staticディレクトリに格納しtemplateから参照する。
  2. CDNサイトを利用してtemplateから参照する。
  3. django-bootstrapを利用する。
    参考:https://github.com/zostera/django-bootstrap4
    ※当然Bootstrap5は2021/1/11時点でまだbataなのでdjango-bootstrap5は存在しない。

今回は1のやり方で導入を行う。

staticディレクトリの作成と設定

staticディレクトリをプロジェクトディレクトリ直下に以下の用に作成。

staticファイルの参照先を設定。

confug/settings.py

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    BASE_DIR / 'static',
)

staticファイルの扱い方の詳細は以下を参照。 - 公式document Managing static files (e.g. images, JavaScript, CSS) | Django documentation | Django

Bootstrap5のダウンロードとstaticファイルの配置

Bootstrap5のサイトからbootstrapモジュールをダウンロードする。
Download · Bootstrap v5.0

ダウンロードしたファイルのうち以下のモジュールを以下のように配置する。

baseテンプレートの作成

どのテンプレートにも共通で書かなければ行けない場合、Djangoにはベーステンプレートを作成することで、共通かでる。
templateディレクトリ配下に以下の様にbase.htmlを作成し、先程格納したbootstrapモジュールのリンクを記述する。

templates/base.html

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>

    <title>sample app</title>
</head>
<header>
    <h1>base.htmlのheader</h1>
</header>
<body>
    {% block Content %}
    <h1>base.htmlのbody</h1>
    {% endblock Content %}
</body>
<footer>
    <h1>base.htmlのfooter</h1>
</footer>
</html>

bodyタグに注目すると、{% block Content %} {% endblock Content %}と記載している。Contentというのは任意の名前で構わない。
このblockで挟まれた部分を他のテンプレートで上書きできる。

以下のようなイメージ。
blockは任意の複数書くことも出来る。

やって見たほうが早いのでlist.htmlを以下の用に修正する。

templates/list.html

{% extends 'base.html' %}
{% block Content %}
<h1>list Page</h1>
<ul>
    {% for item in object_list %}
        <li>
            {{ item.title }}: {{ item.memo }}
            <a href="{% url 'update' item.pk %}">編集する</a>
            <a href="{% url 'delete' item.pk %}">削除する</a>
        </li>
    {% endfor %}
</ul>
{% endblock Content %}

{% extends 'base.html' %}でbase.htmlを継承し、上書きしたい部分を{% block Content %} {% endblock Content %}で囲う。

この状態でrunserverして http://127.0.0.1:8000/samplecrud/list/ にアクセスすると、以下の用に変化したことがわかる。




list.htmlと同じ用に他のテンプレートも編集しておく。

templates/create.html

{% extends 'base.html' %}
{% block Content %}
<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
    {{ form.as_p}}
    <input type="submit" value="作成する">
</form>
{% endblock Content %}

templates/delete.html

{% extends 'base.html' %}
{% block Content %}
<h1>Delete Page</h1>
<form action="" method="POST"> {% csrf_token %}
    <p>
        {{ object.title }}{{ object.memo }}
        を本当に削除しますか?
    </p>
    <input type="submit" value="削除する">
</form>
{% endblock Content %}

templates/update.html

{% extends 'base.html' %}
{% block Content %}
<h1>Update Page</h1>
<form action="" method="POST"> {% csrf_token %}
    {{ form.as_p}}
    <input type="submit" value="更新する">
</form>
{% endblock Content %}

Bootstrapで見た目を整える。

※ここからはBootstrapの使い方になるのでDjangoとは少し遠い世界になる。

見た目といえばNavbar。
以下のsampleに習って作成する。
Navbar · Bootstrap v5.0

templates/base.html

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
    <script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>

    <title>sample app</title>
</head>
<header>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Todo Sample App</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
            <div class="navbar-nav">
              <a class="nav-link " aria-current="page" href="{% url 'list' %}">List</a>
              <a class="nav-link" href="{% url 'create' %}">Create</a>
            </div>
          </div>
        </div>
    </nav>
</header>
<body>
    {% block Content %}
    {% endblock Content %}
</body>
<footer>
    <div class="fixed-bottom">
        <p class="float-end m-2">create by haku-mai</p>
    </div>
</footer>
</html>

listとcreateページをリンクをNavbarに追加した。
以下の用になる。

レスポンシブデザインになっている。

余談だが、bootstrap4→5で.float-left、.float-rightは.float-startと.float-endに変更になっていた。

jumbotronとButtonの導入 (bootstrap5からjumbotronが削除されたので他のユーティリティを利用)

templates/list.html

{% extends 'base.html' %}
{% block Content %}
<div class="bg-light p-3 p-sm-5 mb-4 border-top">
    <div class="container">
      <h1 class="display-4">List Page</h1>
      <hr class="my-4">
      <p>djangoでいっぱい遊びましょう</p>
      <a class="btn btn-primary btn-lg" href="{% url 'create' %}" role="button">ToDoを作成する</a>
    </div>
</div>
<ul>
    {% for item in object_list %}
        <li>
            {{ item.title }}: {{ item.memo }}
            <a href="{% url 'update' item.pk %}">編集する</a>
            <a href="{% url 'delete' item.pk %}">削除する</a>
        </li>
    {% endfor %}
</ul>
{% endblock Content %}

だいぶwebサイトっぽくなってきましたね。

tableの導入

templates/list.html

{% extends 'base.html' %}
{% block Content %}
<div class="bg-light p-3 p-sm-5 mb-4 border-top">
    <div class="container">
      <h1 class="display-4">List Page</h1>
      <hr class="my-4">
      <p>djangoでいっぱい遊びましょう</p>
      <a class="btn btn-primary btn-lg" href="{% url 'create' %}" role="button">ToDoを作成する</a>
    </div>
</div>
<ul>
    <div class="container">
        <table class="table caption-top">
            <caption>Todo List</caption>
            <thead>
                <tr>
                <th scope="col">Title</th>
                <th scope="col">Memo</th>
                <th scope="col"></th>
                <th scope="col"></th>
                </tr>
            </thead>
            <tbody>
                {% for item in object_list %}
                <tr>
                <td>{{ item.title }}</td>
                <td>{{ item.memo }}</td>
                <td><a class="btn btn-secondary btn-sm" href="{% url 'update' item.pk %}">編集する</a></td>
                <td><a class="btn btn-danger btn-sm" href="{% url 'delete' item.pk %}">削除する</a></td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock Content %}

のを{% for item in object_list %} {% endfor %}で囲うのがポイント。

だいぶwebサイトっぽくなってきましたね。(2回目)

以上、Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整えるでした。

form導入について

まず、bootstrapのformを導入する前のcreate.htmlをもう一度見てみると、generic viewのCreateViewを使いDjangoの機能を最大限利用している関係で、 form文をほとんど書かなくても実装出来ている。

templates/create.html

{% extends 'base.html' %}
{% block Content %}
<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
    {{ form.as_p}}
    <input type="submit" value="作成する">
</form>
{% endblock Content %}

ほぼ{{ form.as_p}}しか書いていない。
実際にレンダリングされる時、{{ form.as_p}}は以下の用に展開されている。

<p><label for="id_title">Title:</label> <input type="text" name="title" maxlength="100" required="" id="id_title"></p>
<p><label for="id_memo">Memo:</label> <textarea name="memo" cols="40" rows="10" maxlength="255" required="" id="id_memo"></textarea></p>

今回のやり方でbootstrapを適応する場合はこのformを展開してやる必要がある。
formクラスは非常に簡単であるが、見た目を返上するにはやや面倒であり、直接form用のhtmlを記載する方法もあるが、 formクラスにはエラー処理やバリデーションの機能などを兼ね備えているため、できるだけ利用してbootstrapを反映させて見ます。

方法としてはformクラスを個別に展開します。
formクラスが持つフィールド詳細については以下を参照
- 公式document Working with forms | Django documentation | Django

上記を参照して個別展開すると以下のように表現できる。

{% extends 'base.html' %}
{% block Content %}
<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
    <p>
        <label for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
        <input type="text" name="{{ form.title.html_name }}" maxlength="100" required="" id="{{ form.title.id_for_label }}">
        {% for error in form.title.errors %}
            {{error}}
        {% endfor %}
    </p>
    <p>
        <label for="{{ form.memo.id_for_label }}">{{ form.memo.label }}</label>
        <textarea name="{{ form.memo.html_name }}" cols="40" rows="10" maxlength="255" required="" id="{{ form.memo.id_for_label }}"></textarea>
        {% for error in form.memo.errors %}
            {{error}}
        {% endfor %}
    </p>
    <input type="submit" value="作成する">
</form>
{% endblock Content %}

inputタグや、textareaタグは{{ form.title }}{{ form.memo }}で置き換えることが出来るが、bootstrapで加工したいため、利用しない。
この状態で http://127.0.0.1:8000/samplecrud/create/ にアクセスすると以下の用に表示される。

ここまでくればbootstrapの世界に持ってこれるので以下の用にする。

{% extends 'base.html' %}
{% block Content %}
<div class="container">
<form action="" method="POST"> {% csrf_token %}
    <legend>Todoを作成</legend>
    <div class="mb-3">
        <label class="form-label" for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
        <input class="form-control" type="text" name="{{ form.title.html_name }}" maxlength="100" required="" id="{{ form.title.id_for_label }}">
        {% for error in form.title.errors %}
            {{error}}
        {% endfor %}
    </div>
    <div class="mb-3">
        <label class="form-label" for="{{ form.memo.id_for_label }}">{{ form.memo.label }}</label>
        <textarea class="form-control" name="{{ form.memo.html_name }}" cols="40" rows="10" maxlength="255" required="" id="{{ form.memo.id_for_label }}"></textarea>
        {% for error in form.memo.errors %}
            {{error}}
        {% endfor %}
    </div>
    <input class="btn btn-primary" type="submit" value="作成する">
</form>
</div>
{% endblock Content %}

この状態で http://127.0.0.1:8000/samplecrud/create/ にアクセスすると以下の用に表示される。

これまたちょっとwebサイトっぽくなりましたね。

同じ用にupdateも更新する。 templates/update.html

{% extends 'base.html' %}
{% block Content %}
<div class="container">
<form action="" method="POST"> {% csrf_token %}
    <legend>Todoを更新</legend>
    <div class="mb-3">
        <label class="form-label" for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
        <input class="form-control" type="text" name="{{ form.title.html_name }}" value="{{ form.title.value }}" maxlength="100" required="" id="{{ form.title.id_for_label }}">
        {% for error in form.title.errors %}
            {{error}}
        {% endfor %}
    </div>
    <div class="mb-3">
        <label class="form-label" for="{{ form.memo.id_for_label }}">{{ form.memo.label }}</label>
        <textarea class="form-control" name="{{ form.memo.html_name }}" cols="40" rows="10" maxlength="255" required="" id="{{ form.memo.id_for_label }}">{{ form.memo.value }}</textarea>
        {% for error in form.memo.errors %}
            {{error}}
        {% endfor %}
    </div>
    <input class="btn btn-primary" type="submit" value="更新する">
</form>
</div>
{% endblock Content %}

updateベージは以下の用に表示される。

削除ページは戻るButtonを追加

{% extends 'base.html' %}
{% block Content %}
<div class="container">
    <h1 class="display-6">Todoを削除確認</h1>
    <form action="" method="POST"> {% csrf_token %}
        <p>
            {{ object.title }}{{ object.memo }}
            を本当に削除しますか?
        </p>
        <input class="btn btn-danger" type="submit" value="削除する">
        <a class="btn btn-secondary" href="{% url 'list' %}">戻る</a>
    </form>
</div>
{% endblock Content %}

daleteベージは以下の用に表示される。

以上、Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整える。でした。