Pythonでwebサイトの構築　Part2 アプリケーションの作成

[前回の記事](https://qiita.com/T_gdmuy/items/3a33867bdc3c339aa4a2)では「Dockerによる環境構築~プロジェクトの立ち上げ」までを行いました。
この記事ではブログサイトに必要な各機能を実装するためのアプリケーションの作成とURLディスパッチャについての簡単な説明を行います。

<h2>1, ブログサイトに必要な機能について</h2>
ブログサイト構築にあたり必要な機能は以下になります。

```Python:ブログサイトに必要な機能
・記事一覧
・記事詳細
・カテゴリごとでの記事
・トップページ(必ずしも必要ではない)
・管理人サイト(adminサイト)
```

<h2>2, アプリケーションの作成とurls.pyについて</h2>
<h4>2-1, アプリケーションの作成</h4>
ここではアプリケーションの作成を行います。
プロジェクトディレクトリと同じ階層でターミナルを新たに立ち上げ以下のコマンドを入力してください。
そうすることでコンテナ内に入ることができ「python　manage.py ~」を利用できます。

```Python:ターミナル
C:\....\blogapp> docker exec -it [コンテナ名]
コンテナ名は前回の説明でコンテナ作成した際に作成されたコンテナの名称です。
【確認方法】
1, dockerアプリケーションのContainer/Appsより「Running」と書かれたコンテナのドロップダウン内を見て確認。
2, docker-psで確認

上記コマンドを入力したあと、以下のようにしてアプリケーションを作成します。
root@XXXXXX:/code# python manage.py startapp [アプリ名]
これによりプロジェクトと同じ階層に以下のようなアプリケーションが作成されます。

blog_app/
        ├プロジェクト
        ├ manage.py
        └ polls(app_name)/
            ├ __init__.py
            ├ apps.py
            ├ models.py
            ├ tests.py
            ├ views.py
            └ urls.py #アプリケーション作成後追加してください
```
アプリケーションの作成後はまずsettings.pyを編集します。

```Python:settings.py
import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

DEBUG = True

ALLOWED_HOSTS = []

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls.app.Polls.Config',#追加(appsの中のclassと同じ名称にしてください。)
]

X_FRAME_OPTIONS = 'SAMEORIGIN'

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'blog.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],#書き換え
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'polls.context.common',
            ],
        },
    },
]

WSGI_APPLICATION = 'blog.wsgi.application'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

#以下追加
STATIC_URL = '/static/'

STATICFILES_DIRS = (
    [os.path.join(BASE_DIR, 'static'),]
)

MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
MEDIA_URL = '/media/'
```
随分と多いですが変更箇所は少ないです。
ここで出てきたTemplateやStaticについては後々解説します。

<h4>2-2, urls.pyについて</h4>
先ほどアプリケーション内にurls.pyを追加してもらったと思います。

しかし前回プロジェクトを作成した際にurls.pyが自動で追加されたと思います。
つまりプロジェクトとアプリケーションは両方urls.pyを持つ必要があるということになります。
実際はアプリケーション内に追加しなくてもよいですが機能が増えるにつれてわかりずらくなるため分割することをお勧めします。
まず、それぞれのurls.pyの中身を見てみましょう。

```Python:プロジェクト内のurls.py
from django.contrib import admin
from django.urls import path

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

```Python:アプリケーション内のurls.py
何も書いていない。
```

例えばトップページを表示したい場合にはurls.pyを以下のように編集します。
トップページurl:http://0.0.0.0:8000/polls/top/の場合。

```Python:プロジェクト内のurls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(polls.urls), #pollsのurls.pyを参照しURL:http://0.0.0.0:8000/[urls.pyのURL]/にできる。
```

```Python:アプリケーション内のurls.py
from django.urls import path
app_name = 'polls'#アプリ名
urlpatterns = [
    path('top/',[views.pyで設定するビュー]),
]
```

このようにすれば上記URLでトップページを表示することができます。
urls.pyは簡単に説明すると「逆引きで指定されたURLを探す作業」になります。
どういうことかはMVTの概念やHTTPレスポンスの話など基本ですが少し難しい話になるので、別の記事で解説します。

ここではアプリケーションurls.pyで各機能に対応するURLを指定するといった感覚で大丈夫です。

<h2>3, 静的ページの表示について</h2>
ここでは静的ページであるトップページの表示を行います。

まず静的ページとは「いつも同じページが表示されるページ」ということです。
逆に動的ページとは「ユーザーの入力などによりページ内容が変わるもの」です。
今回は静的ページの表示を行います。(解説はこちらの記事で紹介しています。)

今回は以下のようなページを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402159/105c8bbe-8bd6-d5c5-d6a5-e396f4fa7c1c.png)
今回のブログサイトはプログラミング日記のような位置づけにしました。
このページでは静的サイトなのでデータベースに必要なmodels.pyは編集しません。

まずはこの見た目であるフロントエンドの編集から行います。
Djangoではtemplateと呼ばれる概念があり、これを使うことで簡単にフロントエンドの実装を行えます。
まずアプリケーション内にtemplatesフォルダを作成しその中に以下のファイルを作成してください。

```Python:Template
templates/
        ├ top.html#トップページのhtml
        ├ header.html#共通のheader
        └ base.html#今後のアプリの全体での共通テンプレート
```
さらにデザインの部分を担うCSSは以下に配置してください。

```Python:css
blog_app/
        ├ プロジェクト
        ├ アプリケーション
        └ static/css/style.css#デザイン
```
上記の位置に配置したのはsettings.pyで編集した位置と一致します。
そのためsettings.pyが間違えているとこの位置に配置してもwebページが表示されません。

テンプレートファイルの中身は以下になります。

```Python:base.html
{% load static %}
<!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">
    <link rel = 'stylesheet' href = '{% static 'css/style.css' %}'>
    <title>{% block title %}ブログ{% endblock %}</title>
</head>
<body>
    #header.htmlの内容を読み込みます。
    {% include 'header.html' %}
    
    <main>
        {% block content %}{% endblock %}
    </main>
    #footerは今回用意していませんが、会社情報や免責事項などがよく盛り込まれています。
    {% include 'footer.html' %}
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
```
headタグ内でcssとbootstrapを追加することでサイトの見た目を構築しました。
bodyタグ内にはjavascriptファイルを追加することでこのサイトではドロップダウンの実装を行いました。
(詳細はヘッダーで説明)

```Python:header.html

##aタグhrefの{% url '' %}に記載されている事項については今後の機能追加で解説するため、ここではhref = '#'としてください。

<header>
    <nav class="navbar navbar-expand-md navbar-dark bg-info fixed-top">
        <a class="navbar-brand" href="{% url 'polls:top' %}">ブログ</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarsExampleDefault">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link" href="{% url 'polls:index' %}">投稿一覧</a>
                </li>
                <li class="nav-item">
＃－－－－－－－－－－－－－－－－ドロップダウンー－－－－－－－－－－－－－－－－－－－－－－－－－－－ー－－－－－－－－－－－－
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle"  id="navbarDarkDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                          カテゴリー
                        </a>
                        <ul class="dropdown-menu" aria-labelledby="navbarDarkDropdownMenuLink">
                            <li>サンプル１</li>
                            <li>サンプル２</li>                           
                            {% endfor %}
                        </ul>
                      </li>
＃－－－－－－－－－－－－－－－－ー－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－
                </li>

            </ul>
        </div>
    </nav>
</header>
```
ヘッダーではナビゲーションメニューの実装を行いました。こちらはこれから紹介する前ページ共通になります。
主にbootstrap公式を参考に構築しました。

ここまでで全ページ共通のbase.htmlとheader.htmlが実装できました。

<h2>4, トップページの表示について</h2>
トップページは静的なページのためビューとテンプレート(html)、urls.pyを編集するだけで実装できます。
<h3>4-1, ビューとテンプレート</h3>

```Python:urls.py
from django.urls import path
from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.TopView.as_view(), name = 'top'),
]
```

```Python:views.py
from django.shortcuts import render
from django.views.generic import TemplateView#追加

# Create your views here.
class TopView(TemplateView):
    template_name = 'top.html'
```
templatesにtop.htmlを追加

```Python:top.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class = 'container'>
<h1>実務未経験からの学習記録</h1>
<h5>
    このブログでは、実務未経験からプログラマーになるまでに行ったことを
    投稿しています。
    <br></br>
    私のポートフォリオ作品は<a href = '#' target = '_blank'>ここから</a>見れます！
    <br></br>
    また当ブログ開発のオープンソースは<a href = '#' target = '_blank'>「GitHub」</a>と
    <a href = '#' target = '_blank'>「Qiita」</a>
    に掲載しているので興味ある方は見てください！
    <br></br>

    <a href = '{% url 'polls:index' %}'>投稿一覧はこちら</a>
</h5>
</div>
{% endblock %}
```

```Python:style.css
header {
    padding-bottom: 100px;
}

header .navbar-brand {
    margin-left: 50px;
}

.container {
    width: 1000px;
    margin-left: auto;
    margin-right: auto;
}

.container h1 {
    text-align: center;
    margin-bottom:30px;
}
```
HTML&CSSの細かい書き方については省略します。

ここまで来たらブラウザでhttp://0.0.0.8000/polls/を入力してください。
トップページが表示されるはずです。

<h3>4-2, 4-1の解説</h3>
上記のviews.pyではTemplateViewというクラスビューを利用しました。
その中でテンプレートとしてtop.htmlを参照しろという命令を出します。

またurls.pyでviews.TopView.as_view()としましたが、views.py内のTopViewを参照するという意味です。
つまり、以下の流れになります。

```Python:ページ表示の流れ
１、ブラウザにURLを入力
２、そのURL の存在するビューをurls.pyを通して探す。
３、ビューからテンプレートを探して、ブラウザに表示
```
Djangoはこのような逆引きの検索を行うことで指定のページを表示できるようになっています。

ここまででページの表示をしました次回はDBの取り扱いと[adminサイトの作成](https://qiita.com/Taku_nakahara/items/a221ca689edcf4fce113)を行います。