Python (Flask) でベーシック認証とマークダウンによるサイトを公開する

# 概要

- 社内勉強会の1日の時間だけで何か作り上げることをテーマにしました
- 今回は`Python(Flask)`で以下の機能を実装したサイトを作りました
- 誰でも作れるように手順やソースコードの説明などをまとめておきます


## 実装した機能

- `Python(Flask)`で作成したベーシック認証付きのサイト
- コンテンツのデータソースはマークダウンファイルで作成
- マークダウンで記述された内容を`HTML`レンダリングして表示
- 今回は`CSS`のフレームワークを使用せず見出しのデザインなどを変更
- ヘッダーのメニューはレスポンシブに対応

## 参考にしたサイト

- [CSSのコピペだけ！おしゃれな見出しのデザイン例まとめ68選](https://saruwakakun.com/html-css/reference/h-design)
- [w3schools.com : CSS Horizontal Navigation Bar](https://www.w3schools.com/css/css_navbar_horizontal.asp)
- [Flaskで Basic認証、Digest認証](https://qiita.com/msrks/items/7de68cde6c3ab9d5e177)
- [Using Markdown as a Python Library](https://python-markdown.github.io/reference/#using-markdown-as-a-python-library)

## 成果物

- 実際に動作する[公開先のリンク](https://flask-basic-markup.glitch.me/)
  - username: user
  - password: user 
- プロジェクト（ソースコードなど）を[確認できるリンク](https://glitch.com/edit/#!/flask-basic-markup)

# 準備

- 以下の記事の「#準備」をご確認ください
- [Python (Flask) で形態素解析をするAPIを作ってGlitchで公開する#準備](https://qiita.com/tamoco/items/886937491fea97b88fdd#%E6%BA%96%E5%82%99)

# ディレクトリ構成とソースコード

## ディレクトリ構成

```
.
├── markdown
│       ├── index.md
│       └── skill.md
├── templates
│       ├── header.html
│       └── index.html
├── .env
├── README.md
├── glitch.json
├── requirements.txt
└── server.py
```

## ソースコードと説明

### requirements.txt

- ベーシック認証とマークダウンファイルを表示するため以下のライブラリを追加します
- `waitress`は開発が完了して本番公開だけしたいときにWebサーバーとして利用します

```
Flask
Flask-HTTPAuth
Flask-Markdown
waitress
```

### server.py

#### ベーシック認証

- `flaskc_httpauth`の`HTTPDigestAuth`でベーシック認証を設定しています
- `users`という辞書型のオブジェクトにユーザー認証時のユーザーを作成できます
  - ユーザーを`Key`に設定
  - パスワードを`Value`に設定
  - ソースを見られると丸見えになるので環境変数から取得するようにも可能
- 基本的に全てのエンドポイントに対して認証を必要とするようにしています
- 開発時は`app.run(debug=True)`でWebサーバーを起動します
- 開発が完了し公開するときは`serve(app, host='0.0.0.0', port=5000)`でWebサーバーを起動します
`
 

#### マークダウンファイルの読み込み

- `get_md_contents`でマークダウンのファイルを読み込みます
- `Markup`でマークダウンの内容から`HTML`のソースを生成します
- 生成された`HTML`のソースは`render_template`で`content`として渡してあげます
- 渡された`content`は`Jinja2`で`HTML`のソースとして表示します

```python
import os
import codecs
from waitress import serve
from markdown import markdown
from flask import Flask, request, render_template, Markup
from flask_httpauth import HTTPDigestAuth

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
auth = HTTPDigestAuth()

users = {
    "user1": os.environ['USER_ID1_PASS'],
    "user": "user"
}

@auth.get_password
def get_pw(username):
  if username in users:
    return users.get(username)
  return None

@app.route("/")
@auth.login_required
def index():  
  content = get_md_contents('markdown/index.md')
  return render_template('index.html', content=content, active_page='home')

@app.route("/skill")
@auth.login_required
def skill():
  content = get_md_contents('markdown/skill.md')
  return render_template('index.html', content=content, active_page='skill')

def get_md_contents(md_path: str):
  source = codecs.open(md_path ,'r', 'utf-8').read()
  return Markup(markdown(source, extensions=['tables']))

if __name__ == "__main__":
  # app.run()
  # app.run(debug=True)
  serve(app, host='0.0.0.0', port=5000)
```

### templates/header.html

- 今回は`CSS`のフレームワークを使わずヘッダーメニューを作成しました
- レスポンシブにも対応しています
  - `width`が`600px`以下になったときに縦並びのスマホ用のメニューに切り替わります
- メニューのアクティブの表示も切り替わるようにしています
  - `render_template`から`active_page`という変数でCSSのクラスの有効無効を切り替えています

#### Homeがアクティブのときのメニュー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/132f45fc-29cf-ce89-b157-d05c62de3d5d.png)

#### Skilがアクティブのときのメニュー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/1b48b7b8-0c1d-f694-9a0c-1cb4f4865209.png)


```html
<div>
  <ul class="topnav">
    <li><a class="{{'active' if active_page == 'home' }}" href="/">Home</a></li>
    <li class="right"><a class="{{'active' if active_page == 'skill' }}" href="/skill">Skill</a></li>
  </ul>
</div>

<style>
  body {margin: 0;}
  ul.topnav {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #c1c1c1;
  }

  ul.topnav li {
    float: left;
  }

  ul.topnav li a {
    display: block;
    color: white;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
  }

  ul.topnav li a:hover:not(.active) {
    background-color: #5d5d5d;
  }

  ul.topnav li a.active {
    background-color: #7db4e6;
  }

  ul.topnav li.right {
    float: right;
  }

  @media screen and (max-width: 600px) {
    ul.topnav li.right,
    ul.topnav li {
      float: none;
    }
  }

</style>
```

### templates/index.html

- `{{content}}`にマークダウンから作成された`HTML`のソースを元にコンテンツを表示します
- コンテンツの表示は`index.html`だけですが`Flask`から渡されるコンテンツで内容を切り替えます
- 単純にHTMLのソースだけ表示すると見た目が寂しいので`CSS`で適宜イイカンジに見た目を変えています

#### CSS適用前後 Before ⇒ After

![css_before_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412322/0b87a630-d1c1-fd9d-fd8e-cc3fa14bd39d.png)



```html
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Flask de MarkDown</title>
  </head>
  <body>
    {% include "header.html" %}
    <div class="body-space">
      {{content}}
    </div>
  </body>
  <style>
    .body-space {
      padding: 1rem;
    }

    h1 {
      padding: 0.4em 0.5em; /*文字の上下 左右の余白*/
      color: #494949; /*文字色*/
      background: #f4f4f4; /*背景色*/
      border-left: solid 8px #7db4e6; /*左線*/
      border-bottom: solid 3px #d7d7d7; /*下線*/
    }

    h2 {
      background: #dfefff;
      box-shadow: 0px 0px 0px 5px #dfefff;
      border: dashed 2px white;
      padding: 0.2em 0.5em;
    }

    h3 {
      color: #6594e0; /*文字色*/
      /*線の種類（点線）2px 線色*/
      border-bottom: dashed 2px #6594e0;
      padding-left: 0.5rem;
    }

    ul,
    ol {
      color: #1e366a;
      border: dotted #1e366a 1px; /*ドット 色 太さ*/
      padding: 1rem 0.8rem 1rem 2rem;
    }

    ul li,
    ol li {
      line-height: 1.5;
      padding: 0.2rem 0;
    }

    table {
      width: 100%;
      border-collapse: separate;
      border-spacing: 0;
    }

    table th:first-child {
      border-radius: 5px 0 0 0;
    }

    table th:last-child {
      border-radius: 0 5px 0 0;
      border-right: 1px solid #3c6690;
    }

    table th {
      text-align: left;
      color: white;
      background: linear-gradient(#829ebc, #225588);
      border-left: 1px solid #3c6690;
      border-top: 1px solid #3c6690;
      border-bottom: 1px solid #3c6690;
      box-shadow: 0px 1px 1px rgba(255, 255, 255, 0.3) inset;
      width: 25%;
      padding: 1rem;
    }

    table td {
      text-align: left;
      border-left: 1px solid #a8b7c5;
      border-bottom: 1px solid #a8b7c5;
      border-top: none;
      box-shadow: 0px -3px 5px 1px #eee inset;
      width: 25%;
      padding: 1rem;
    }

    table td:last-child {
      border-right: 1px solid #a8b7c5;
    }

    table tr:last-child td:first-child {
      border-radius: 0 0 0 5px;
    }
  </style>
</html>
```

### markdown/index.md

- 表示するコンテンツの元になる内容をマークダウン形式で記述します

```markdown
# 概要

- PythonのFlaskで作成したベーシック認証付きのサイトです
- コンテンツはマークダウンファイルで記述しています
- 簡易的なJAMStackのサイトのように利用できます

## サイトのコンテンツ

- 自分のスキルをマークダウンで記述しています
- 特定の人に見てもらうことができるため職務経歴書のようなことも追加できます
- 今回は[【スキルシート】](/skill)を別のマークダウンで作成しました

## このサイトで実装したこと

- PythonのFlaskでベーシック認証を設定
- マークダウンファイルを元にHTMLをレンダリング
- 見出しのデザインをCSSで変更
- レスポンシブに対応したNavMenu

### 使用したライブラリ

- Flask
- Flask-HTTPAuth
- Flask-Markdown
- Glitch

```

### markdown/skill.md

- `index.md`と同じですが`テーブル`の記述があります
- マークダウンで画像の表示もできます
  - `Glitch`では`Assets`としてアップロードしたファイルはCDNのパスとして読み込みます

```markdown
# 概要

- 使用できる言語やフレームワークなどを記載しています

![status](https://cdn.glitch.com/36955d26-9c1c-401e-95d7-cf4aa2218186%2Fbowser_stats.png?v=1615129207722)

# スキル

| 言語<br>[フレームワーク]          | 詳細                                                                                  |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| JavaScript<br>[Vue, Svelte]      | VanillaJS から始まり jQuery などの経験もあります。                                       |
| Python<br>[Flask, Django]         | 基本的には API の開発が主です。                                                         |
| Container<br>[Docker, Kubernetes] | 開発環境は docker-compose などで手早く準備します。k8s は chart の作成経験もあります。 |
| C#<br>[Windows Form, Unity] | Windows Formは業務開発経験があります。Unityは趣味でゲーム開発をしておりリリースした経験があります。 |
| VBA<br>[Excel, Access] | レガシーな環境でも作業効率化・改善するように対応しました。 |

# 大切にしてること

- 常に意識しているのは継続的改善を続けて行えるエンジニアであることです
- 新しい分野の技術を得る」「考える力を伸ばす」こういった活動を続けていきます

# 自分の強み

## 行動力

- よりよくなるための行動であるならばすぐに実行に移せるところです
- いわゆる「許可を求めるな謝罪せよ」という理念になります
- もちろん確認することを重要とするケースもあるためそこはバランスを取りながら行動に移しています
- 特に自分が大切にしているのは行動して失敗した場合はきちんと振り返って改善を行い次回の行動につなげるようにしています

## 展開力<br>（情報を共有する力）

- 自分の技術やナレッジを他の人に共有するところです
- 技術だけでなく会議の進め方などでフレームワークを使う手法
- そういったノウハウも自分に改良しながら他の人に展開するようにしています
- 特に考え方を整理する上で、様々なフレームワーク（マンダラチャートや MECE）を使います
- それらを用いて他の人にも理解してもらいつつ手法自体を社内に定着するように布教したりします
```

# ふりかえり

- マークダウンからサイトのコンテンツを作って表示することができました
- `JAMStack`のようにマークダウンからサイトを作るイメージができたと思います
  - URLごとにマークダウンファイルをその都度読み込んでいるところは課題です
  - マークダウンのコンテンツは一括で読み込んだ方がレスポンスは速くなるはずです
- `CSS`のフレームワークを使用しなくてもある程度は見た目を整うことができました
- ベーシック認証で単純な認証付きのサイトを作れるので応用できることはありそうです