Django Sprint #4 トップページの作成 (Docker Toolbox向け)

この記事はDocker Toolbox (≒ Windows 10 Home) ユーザー向けです。Docker (Mac OS, Windows 10 Professional / Enterprise) ユーザーの方は[Django Sprint #4 トップページの作成](https://qiita.com/tmgauss/private/889793b2d7c6c89d6863)へ。

## 目次
1. [HTML, CSS, JavaScript](#HTMLとCSS)
2. [トップページの作成の準備](#トップページの作成の準備)
3. [トップページの作成](#トップページの作成)
4. [コードの再利用](#コードの再利用)

## HTML, CSS, JavaScript
ここからは、フロントエンドの内容にも触れながら進めていきます。実際に目に見える変化が大きくなるので、作っていく感覚を今まで以上に得られると思います。

テンプレートはブラウザとビューの架け橋になると[Django Sprint #3 ユーザーモデルのカスタマイズ (Docker Toolbox向け)](https://qiita.com/Chrg/private/3dde9ee45623a1664734)で述べましたが、不正確を承知で端的に言うと、テンプレートはHTMLファイルそのものと考えてもらって構いません。ではHTMLファイルとCSSとはどのようなものでしょうか？

#### HTML
HTMLとはブラウザに表示するページの「骨格」を規定するものです。タグと呼ばれるものを使って、構造と意味を与えます。試しにブラウザ上で好きなサイトにアクセスし、適当な場所で右クリック→ページのソースを表示（ブラウザにより多少表記が異なる）をクリックしてみましょう。それで表示されるのがHTMLファイルの中身です。インターネット上のほぼあらゆるページはHTMLによって記述されていると言っても過言ではありません。

#### CSS
HTMLだけでは見た目がかなり簡素なものとなってしまいます。（授業のサイトとかでたまに見る？）CSSはHTMLの見た目を整える役割があります。HTMLを骨と例えるなら、CSSは筋肉です。具体的には、ボタンなどのオブジェクトとその配置やフォントなどを規定しています。

#### JavaScript
JavaScriptはページの見た目を動的に変えるときに使います。例えば、ハンバーガーメニュー、スライドショー、ボタンをクリックしたときのエフェクトなどに使います。

### その他の知識
#### レスポンシブデザイン
最近はスマートフォンやタブレットなど、通常のパソコンだけでなくあらゆるデバイスを想定しなければなりません。デバイスの画面幅によってスタイルを変えるようなデザインをレスポンシブデザインと言います。これをスクラッチでCSSで記述するのはかなりの量のコードを書かなければなりません。そこで登場するのが、CSSフレームワークです。

#### CSSフレームワーク
CSSフレームワークを使うことで、一からCSSファイルを書くことなく、スタイルを実装することができます。用意されたパーツを組み立てていくイメージです。多くのモダンなフレームワークではレスポンシブデザインをサポートしています。

- [Bootstrap](https://getbootstrap.com/)
- [Bulma](https://bulma.io/)
- [UIkit](https://getuikit.com/)
- [Pure.css](https://purecss.io/)

代表的なものを上に挙げました。

本チュートリアルではBulmaを使うことにします。具体的な使い方に関しては、これから実際に使っていく中で理解できると思います。



## トップページの作成の準備
さて、早速トップページを作っていきましょう。データの出し入れがなく、ただ情報が載っているだけのページを静的なページ（static pages）と呼ぶことがあります。静的なページの作り方を学ぶ前に、いくつか準備をしましょう。

#### template用ディレクトリの作成
templateファイル（HTMLファイル）を置いておくディレクトリを「cms」直下に作成します。

```
code
├─ cms
│  ├─ migrations
│  ├─ templates
│  │  └─ cms
...
```
「templates」と「cms」はディレクトリです。このような二重の構造をとる理由はアプリケーションの再利用を容易にするためです。このcms直下にhtmlファイルを保存していきます。

#### 静的ファイル用ディレクトリの作成
静的ファイルとは画像ファイルやCSSファイルのことです。本チュートリアルではフレームワークを利用し、一切のcssファイルを書きませんが、一応作っておきましょう。

```
code
├─ cms
│  ├─ migrations
│  ├─ static
│  │  ├─ css
│  │  └─ images
...
```

#### アプリへのルーティング
アプリ内のviews.pyを使うためには、アプリ内にもurls.pyを作らなければなりません。**cms**ディレクトリ直下にurls.pyを作成し、中身を以下のように書き換えます。

```python:/cms/urls.py
from django.urls import path

from . import views

app_name = 'cms'

urlpatterns = [
    
]
```
これに、元々の**config**ディレクトリ内のurls.pyを結びつけます。

```python:/config/urls.py
...
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('cms.urls')),
]
```

## トップページの作成

#### 静的ページの作成フロー
さあ準備が整いました！具体的な手順は以下の通りです。

1. urls.pyを編集
2. views.pyを編集
3. HTMLファイルを編集

#### URL
例えば、

https://github.co.jp/features

と言うURLを見てみましょう。これは、次のように分けられます。

https + github.co.jp + features

「https」の部分は通信のルール（プロトコル）を表しています。「github.co.jp」の部分はホスト名と言い、サーバー等の「住所」のようなものです。最後に「features」の部分はパス名と言い、「その家の部屋」を表しているイメージです。

ホスト名は別途プロジェクト全体で決めるものなので、urls.pyには基本的にパス名の部分だけを記述すれば大丈夫です。

#### urls.py
先ほどの設定で「config/urls.py」→「cms/urls.py」の流れを設定できました。なので、基本的にはこれからは「cms/urls.py」をいじることになります。

```python:cms/urls.py
...
urlpatterns = [
    path('', views.TopView.as_view(), name='top'),
]
```
こうすると、パス名がない（トップページはこのような形が多い）ときにviews.pyのTopViewというクラスを参照することになります。オプション引数```name```は主にurl関数に渡す際に使います。静的なページではURLが定まっていますが、例えば、ユーザーIDに応じて詳細ページをURLを使って振り分けたいときにurl関数が「気を利かせて」くれるので便利です。

#### views.py
次に、urls.pyがバトンを渡すTopViewクラスを作成します。

```python:/cms/views.py
from django.views.generic.base import TemplateView


class TopView(TemplateView):
    template_name = 'cms/top.html'
```
ビューは通常関数の形で書かれます。しかし、Djangoでは汎用ビュー（generic view）と言ってよく使うようなビューは全て用意されていて、それを継承するためにクラスを使います。これをクラスベースビューと言います。urls.pyのところで、```TopView.as_view()```と```as_view```メソッドを付け加えたのはクラスを無理やり関数の形に変えるためです。汎用ビューについて詳しくは次の #5 で扱うことにします。

この自作の TopView は TemplateView という汎用ビューを継承しています。Template Viewでは```template_name```という変数にテンプレートファイル（HTMLファイル）へのパスの文字列を代入することで、そのファイルへバトンを渡すことができます。ここでは、cmsディレクトリの中のtop.htmlを探すことになります。

#### テンプレート
「cms/templates/cms」の直下に、top.htmlを作りましょう。試しに、「Hello, world!!」と表示させましょう。

```html:/cms/templates/cms/top.html
<html>
<body>
    <p>Hello, world!!</p>
</body>
</html>
```
ここまでこれば、サーバーを立ち上げてブラウザで http://192.168.99.100:8000 を確認してみましょう。

```terminal
> docker-compose up
```
うまく表示されましたか？

#### CSSによる装飾
今のままでは簡素すぎるので、フレームワークを使って装飾を加えてみましょう。top.htmlを次のように書き換えて、bulmaを読み込んでみます。

```html:/cms/templates/cms/top.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
    <p class="title">Hello, world!!</p>
</body>
</html>
```

```<link>```タグを用いて、bulmaをインポートしています。さらに、アイコンなどを使用するために [font awesome](https://fontawesome.com/) も併せてインポートしています。

先ほどの```<p>```タグに```class="title"```を付け加えると先ほどの部分がタイトルのように変化します。```class="subtitle"```にするとどのように変化するでしょうか。各自確認してみましょう。

このようにタグに適当なclass（HTML）を付与することで、装飾します。実際は、

- [bulma | Documentation](https://bulma.io/documentation/)
- [fontawesome | icons](https://fontawesome.com/icons)

を見てパーツを探してきて、サンプルコードごと付け足してしまえば良いです。

#### 「ほぼ」完成品
実際にはこんな陳腐なページでは嫌なので、次のように書き換えてください。

```html:/cms/templates/cms/top.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Django Sprint</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
    <!-- NAVIGATION BAR -->
    <header>
        <nav class="navbar" role="navigation" aria-label="main navigation">
            <div class="navbar-brand">
                <!-- LOGO -->
                <a class="navbar-item" href="/">
                    <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
                </a>
                <!-- HAMBURGER MENU -->
                <a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="my-menu">
                    <span aria-hidden="true"></span>
                    <span aria-hidden="true"></span>
                    <span aria-hidden="true"></span>
                </a>
            </div>
            <div class="navbar-menu" id="my-menu">
                <div class="navbar-end">
                    <a class="navbar-item is-grey" href="#">About</a>
                    <div class="buttons">
                        <a class="button is-info navbar-item" href="#"><strong>Sign up</strong></a>
                        <a class="button is-light navbar-item" href="#"><strong>Log in</strong></a>
                    </div>
                </div>
            </div>
        </nav>
    </header>
    <!-- MESSAGE -->
    <div></div>
    <!-- MAIN -->
    <main>
        <section class="hero is-medium is-info is-bold">
            <div class="hero-body">
                <div class="container">
                    <h1 class="title">Title</h1>
                    <h2 class="subtitle">Subtitle</h2>
                    <a href="#">
                        <button class="button is-info is-inverted is-outlined">
                            <strong>More Info</strong>
                        </button>
                    </a>
                </div>
            </div>
        </section>
        <section class="section is-medium">
            <div class="container">
                <div class="has-text-centered">
                    <h1 class="title">Section</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
                </div>
            </div>
        </section>
    </main>
    <!-- FOOTER -->
    <footer class="footer">
        <div class="container">
            <div class="content has-text-centered">
                2020 <strong>UTokyo Project Sprint</strong>
            </div>
            <div class="content has-text-centered">
                <a href="#" class="has-text-dark">About</a> |
                <a href="#" class="has-text-dark">Contact</a> |
                <a href="#" class="has-text-danger">Report an issue</a>
            </div>
        </div>
    </footer>

    <!-- FOR JAVASCRIPT -->
    <script>
        <!-- FOR HAMBURGER MENU -->
        for (const burder of document.getElementsByClassName('navbar-burger')) {
            const menuId = burder.dataset.target;
            const menu = document.getElementById(menuId);
            burder.addEventListener('click', e => {
                burder.classList.toggle('is-active');
                menu.classList.toggle('is-active');
            });
        }
    </script>
</body>
</html>
```


## コードの再利用
例えば、ナビゲーションバーやフッターなどは全ページで共通しています。ここに変更があったとすると、毎回全てのページについて変更するのは至極困難かつ脆弱です。そこで、「因数分解」のように共通するブロックを切り出します。

#### base.html
共通因子をbase.htmlというファイルにまとめます。

```html:/cms/templates/cms/base.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}{% endblock %}Django Sprint</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
    <!-- NAVIGATION BAR -->
    <header>
        <nav class="navbar" role="navigation" aria-label="main navigation">
            <div class="navbar-brand">
                <!-- LOGO -->
                <a class="navbar-item" href="/">
                    <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
                </a>
                <!-- HAMBURGER MENU -->
                <a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="my-menu">
                    <span aria-hidden="true"></span>
                    <span aria-hidden="true"></span>
                    <span aria-hidden="true"></span>
                </a>
            </div>
            <div class="navbar-menu" id="my-menu">
                <div class="navbar-end">
                    <a class="navbar-item is-grey" href="#">About</a>
                    <div class="buttons">
                        <a class="button is-info navbar-item" href="#"><strong>Sign up</strong></a>
                        <a class="button is-light navbar-item" href="#"><strong>Log in</strong></a>
                    </div>
                </div>
            </div>
        </nav>
    </header>
    <!-- MESSAGE -->
    <div></div>
    <!-- MAIN -->
    <main>
        {% block hero %}{% endblock %}
        {% block tab %}{% endblock %}
        {% block content %}{% endblock %}
    </main>
    <!-- FOOTER -->
    <footer class="footer">
        <div class="container">
            <div class="content has-text-centered">
                2020 <strong>UTokyo Project Sprint</strong>
            </div>
            <div class="content has-text-centered">
                <a href="#" class="has-text-dark">About</a> |
                <a href="#" class="has-text-dark">Contact</a> |
                <a href="#" class="has-text-danger">Report an issue</a>
            </div>
        </div>
    </footer>

    <!-- FOR JAVASCRIPT -->
    <script>
        <!-- FOR HAMBURGER MENU -->
        for (const burder of document.getElementsByClassName('navbar-burger')) {
            const menuId = burder.dataset.target;
            const menu = document.getElementById(menuId);
            burder.addEventListener('click', e => {
                burder.classList.toggle('is-active');
                menu.classList.toggle('is-active');
            });
        }
        {% block extrajs %}{% endblock %}
    </script>
</body>
</html>
```

この```{% block XXX %}{% endblock %}```のところに色々と当てはめていきます。

```html:/cms/templates/cms/top.html
{% extends 'cms/base.html' %}

{% block title %}Home | {% endblock %}

{% block hero %}
<section class="hero is-medium is-info is-bold">
    <div class="hero-body">
        <div class="container">
            <h1 class="title">Title</h1>
            <h2 class="subtitle">Subtitle</h2>
            <a href="#">
                <button class="button is-info is-inverted is-outlined">
                    <strong>More Info</strong>
                </button>
            </a>
        </div>
    </div>
</section>
{% endblock %}

{% block content %}
<section class="section is-medium">
    <div class="container">
        <div class="has-text-centered">
            <h1 class="title">Section</h1>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
        </div>
    </div>
</section>
{% endblock %}
```

はじめの ```{% extends 'cms/base.html' %}``` によって、base.htmlを読み込んでいます。もう一度ブラウザに戻って確認してみましょう。


#### 静的ファイルの読み込み
現在のナビゲーションメニューのところにはbulmaのロゴが当てはめられていますが、これをカスタマイズしてみましょう。適当な画像ファイル（.svgを推奨）を cms/static/images に配置します。

続いて、base.html にstaticファイルを読み込むことを伝えましょう。

```html:/cms/templates/cms/base.html
{% load static %}

<!DOCTYPE html>
...
```
最後に画像（今回はacademia.svgという画像を使用した）入れるところをこのように書き換えます。

```html:/cms/templates/cms/base.html
...
<!-- LOGO -->
<a class="navbar-item" href="/">
    <img src="{% static 'images/academia.svg' %}" width="112" height="28">
</a>
...
```

これでうまく表示されたら成功です！

## 参照
#### 次の記事
[Django Sprint #5 汎用ビューとCRUD 前編](https://qiita.com/tmgauss/private/0664cb98ca95e9737091)

#### 前の記事
[Django Sprint #3 ユーザーモデルのカスタマイズ (Docker Toolbox向け)](https://qiita.com/Chrg/private/3dde9ee45623a1664734)