ほぼPythonだけでサーバーレスアプリをつくろう をやってみた(5章)前編

IT

Webアプリの顔、フロントエンドを作る章です。

mosoメモ
mosoメモ

見た目が9割の世界。ここまでやっておかないとWebアプリの醍醐味は味わえませんね。楽しんでいこう♪

ほぼPythonだけでサーバーレスアプリをつくろう

Amazon

第5章 Transcryptで画面の実装をしよう(前編)

第4章までにバックエンドのWeb APIが完成したので、今回はフロントエンドの実装を行うという内容です。
前編は、データを表示するまでです。

【メモ】
Webアプリのフロントエンドを実装するには、HTML + CSS + JavaScript が必要です。本書では以下のように実装します。

  • HTML … そのまま書く。
  • CSS … Bootstrap(*1)を利用する。
  • JavaScript … PythonのコードをTranscryptでJavaScriptに変換する。

(*1)Bootstrapとは、CSSフレームワークでありこれを利用することで簡単に高性能なCSSが利用できます。Bootstrapにもいくつかテーマがあり、本書では”Honoka”というものを使っています。

HonokaはオリジナルBootstrapテーマです

第5章(前編)で実際に行った内容

index.htmlの作成

Webブラウザ画面はindex.htmlで作ります。こんな感じになります。

ちなみに、さらっとjQueryが利用されていますが本書の目的とは脱線するため詳細は割愛されています。

【私の見解】
jQueryはWebアプリを作る際には普通に使うライブラリとでも思っておけばOKだと思います。

CORSに対応する

少しややこしいことが書かれていました。
端的に言うと、APIがフロントエンドからの要求を受け付けられるようにするため、バックエンドプログラム(hobopy-backend/app.py)を修正します。

Cross-Origin Resource Sharing(CORS)という仕組みを利用しています。

cors=True を追加するだけです。

# ①すべてのToDoを取得する
@app.route('/todos', methods=['GET'], cors=True)

# ②指定されたIDのToDoを取得する
@app.route('/todos/{todo_id}', methods=['GET'], cors=True)

# ③指定されたデータを登録する
@app.route('/todos', methods=['POST'], cors=True)

# ④指定されたデータを更新する
@app.route('/todos/{todo_id}', methods=['PUT'], cors=True)

# ⑤指定されたデータを削除する
@app.route('/todos/{todo_id}', methods=['DELETE'], cors=True)

初期表示処理を実装する

画面の初期表示処理プログラムを作ります。

【メモ】
本書ではフロントエンドはMVPアーキテクチャ(Model、View、Presenter)で実装していきます。

MVPアーキテクチャについては、以下サイトに簡潔にかかれていました。MVCアーキテクチャよりも変更に強いメリットがありそうです。

「MVP」アーキテクチャ基礎解説

ここで作成するプログラムファイルは5つです。

  • 定数を定義するファイル(const.py)
  • スクリプトのエントリーポイント(hobopy-frontend.py)
  • Modelクラス(model.py)
  • Viewクラス(view.py)
  • Presenterクラス(presenter.py)

定数を定義するファイル(const.py)

APIのベースURLを定義するためのファイルとして作成しています。
これにより、後でURLを変更することができます。

BASE_URL = 'http://127.0.0.1:8000/'

スクリプトのエントリーポイント(hobopy-frontend.py)

最初に呼び出せるプログラムと思ってよいでしょう。
ここからPresenterクラスのstart() が呼び出されます。

# ①Transcryptのエイリアスを定義する
__pragma__('alias', 'S', '$')

from presenter import Presenter

presenter = Presenter()
S(presenter.start())

エイリアスの定義と書いている部分は、Transcryptにより Pythonコード内の ‘S’をJavaScriptに変換した時に’$’に置き換えられるという意味です。

Modelクラス(model.py)

内部データを処理するためのプログラムです。

__pragma__('alias', 'S', '$')

from const import BASE_URL

class Model:
    # コンストラクタ
    def __init__(self):
        self._todos = []

    # 指定されたIDのToDoを取得する
    def get_todo(self, todo_id):
        for todo in self._todos:
            if todo['id'] == todo_id:
                return todo
        return None

    # すべてのToDoを取得する
    def get_all_todos(self):
        return self._todos

    # 全件取得のAPIを呼び出す
    def load_all_todos(self):
        S.ajax({
            'url': f"{BASE_URL}todos",
            'type': 'GET',
        }).done(
            self._success_load_all_todos
        ).fail(
            lambda d: alert('サーバーとの通信に失敗しました。')
        )

    # load_all_todos()成功時の処理
    def _success_load_all_todos(self, data):
        self._todos = data
        S('body').trigger('todos-updated')

Viewクラス(view.py)

表示や入力処理を行うプログラムです。

__pragma__('alias', 'S', '$')

class View:
    # ToDoリストを描画する
    def render_todo_list(self, data):
        S('#todo-list').empty()
        for todo in data:
            S('#todo-list').append(self._create_todo_row(todo))

    # ToDoの明細行を生成する
    def _create_todo_row(self, todo):
        return f"""
            <tr>
                <td>
                    <input type='checkbox' class="toggle-checkbox"
                        id='check-{todo['id']}'
                        {'checked' if todo['completed'] else ''}>
                </td>
                <td>{todo['title']}</td>
                <td>{todo['memo']}</td>
                <td>{['低', '中', '高'][int(todo['priority']) - 1]}</td>
                <td>
                    <button class='btn btn-outline-primary update-button'
                        id='update-{todo['id']}' data-toggle='modal'
                        data-target='#input-form'>変更</button>
                </td>
                <td>
                    <button class='btn btn-outline-danger delete-button'
                        id='delete-{todo['id']}'>削除</button>
                </td>
            </tr>
        """

Presenterクラス(presenter.py)

ModelとViewのやり取りをするプログラムです。

__pragma__('alias', 'S', '$')

from model import Model
from view import View

class Presenter:
    # コンストラクタ
    def __init__(self):
        self._model = Model()
        self._view = View()
        self._bind()

    # イベントをバインドする
    def _bind(self):
        S('body').on('todos-updated', self._on_todos_updated)

    # 初期表示処理
    def start(self):
        self._model.load_all_todos()

    # todos-updated受信時の処理
    def _on_todos_updated(self, event):
        self._view.render_todo_list(self._model.get_all_todos())

【説明】初期画面表示の流れ、各クラスのやりとり

プログラムの遷移が激しく思えますが、Webアプリは大体こんな感じですね。クラスからクラスを呼ぶ感覚は慣れるしかない。

1.hobopy-frontend.py:Presenter.start()が呼ばれる

2.presenter.py:Model.load_all_todos()が呼ばれる

3.model.py:Model.load_all_todos()内で、APIにアクセスする
 $ajax っていう部分です。4章までで行ったhttpコマンドでデータベースアクセスしたコマンドをここで打ってる感じですね。

APIアクセスが成功すると、_success_load_all_todos()が呼ばれる

4.model.py:_success_load_all_todos()内で、todo-updatedイベントが実行される
 $(‘body’).trigger(‘todos-updated’)っていう部分です

すると、todo-updatedイベントにバインドバインドされている処理であるpresenter.py:_on_todos_updated()が呼ばれる。

5.presenter.py:_on_todos_updated()内で、_view.render_todo_list()が呼ばれる

6.view.py:render_todo_list()内で、Todoリストを描画する

★つまづきポイント
 オブジェクト指向プログラミングを知っていないとここでつまづくと思います。さらっとコンストラクタとか書かれていますしね。何やってるのか分からないまま、そっと本を閉じることになる可能性ありです。
もしさっぱりわからなければ、オブジェクト指向を知るための勉強に引き返す勇気も必要と思います。

PythonコードをTranscryptでJavaScriptに変換する

フロントエンド環境を実行した状態で以下コードを実行します。
エラーがでなければ、JavaScriptファイルができます。

(hobopy-frontend) C:\Users\Moso\hobopy\hobopy-frontend>transcrypt -b hobopy-frontend

Transcrypt (TM) Python to JavaScript Small Sane Subset Transpiler Version 3.7.16
Copyright (C) Geatec Engineering. License: Apache 2.0

Saving target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/org.transcrypt.__runtime__.js
Saving minified target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/org.transcrypt.__runtime__.js
Saving target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/view.js
Saving minified target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/view.js
Saving target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/const.js
Saving minified target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/const.js
Saving target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/model.js
Saving minified target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/model.js
Saving target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/presenter.js
Saving minified target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/presenter.js
Saving target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/hobopy-frontend.js
Saving minified target code in: C:/Users/Masanobu/hobopy/hobopy-frontend/__target__/hobopy-frontend.js

Ready

実際にフロントエンド画面をブラウザで表示する

事前に4章までに行っていたローカル作業を行いましょう。
これをしないと、もちろんデータは表示されませんよ。

  • ローカルでデータベース起動

    C:\Users\Moso\hobopy\DynamoDBLocal>java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb -port 8001
  • ローカルでバックエンドアプリ起動

    (hobopy-backend) C:\Users\Moso\hobopy\hobopy-backend>chalice local –stage dev

ここまでできたら、事前にインストールしていた Web Server for Chromeを起動しましょう。

①起動するポートNo に 8002を入力
②Webサーバ起動

動作確認を行う

ブラウザで http://127.0.0.1:8002/index.htmlにアクセスしてみましょう。
上手くいけば以下のような画面が表示されるはずです。

ここまでで、バックエンドとちゃんとやり取りを行いフロントエンド処理(ブラウザ表示)ができましたね!

長くなるので、5章はもう1回に分けます。

第5章(前編)の所要時間

約4時間です。

CSSフレームワーク(Bootstrap)、CORS、MVPモデルといったもの理解、Pythonコードを書いているだけで時間が刻々と過ぎていきました。あまり考えすぎない方がよかったかも。

第5章(前編)の感想

やはり実際にフロントエンド画面が表示されるとうれしいですね(*´ω`)。
それが、CSSフレームワークjQueryのチカラだとしてもそれらを組み合わせて画面を作り上げていることを実感できれば良い感じです。

「車が動く仕組みを詳しく知らなくても運転できる」ような感覚。
 …ちょっと大雑把かな。

まだ、新規データ登録、更新、削除などの処理が実装できていないので、次回はそこまで完成させようと思います。 いやはや時間がない。

コメント

タイトルとURLをコピーしました