0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

MitamaでTodoアプリを作ってみる(3) - ControllerとViewを作る

Mitama Advent Calendar4日目です。

前回→『MitamaでTodoアプリを作ってみる(2) - Modelを作りこむ

木瓜丸です。今回も引き続きTodoアプリを組んでみましょう。

ルーティング

まずはControllerに配信先のパスを与えます。ルーティング周りはmain.pyに書いてあるので、次の様に書き換えてください。

from mitama.app import App, Router
from mitama.utils.controllers import static_files
from mitama.app.method import view
from mitama.utils.middlewares import SessionMiddleware

from .controller import HomeController, TodoController


class App(App):
    # name = 'MyApp'
    # description = 'This is my App.'
    router = Router(
        [
            view("/", HomeController),
            view("/create", TodoController, 'create'),
            view("/<id>/done", TodoController, 'done'),
            view("/static/<path:path>", static_files()),
        ],
        middlewares = [SessionMiddleware]
    )

今回はHomeControllerとTodoControllerという2つのコントローラーを作成します。

viewという関数にパス、コントローラークラス、メソッド名を与えると、GETとPOSTのリクエストを受け付けるルーティング先を作成できます。

view("/", HomeController),
view("/create", TodoController, 'create'),
view("/<id>/done", TodoController, 'done'),
view("/static/<path:path>", static_files()),

メソッド名を指定しない場合はhandleメソッドが呼び出されます。

middlewaresにミドルウェアの配列を入れておくと、順番にリクエストがミドルウェアを経由します

middlewares = [SessionMiddleware]

上記のようにSessionMiddlewareをimportして与えると、ログインしているかを確認し、勝手にログイン画面に飛ばしてくれます。

Controllerを作る

ルーティング先を設定したら、次にコントローラーを作りましょう。controller.pyを次のように書き換えてください。

from mitama.app import Controller
from mitama.app.http import Response
from .model import Todo
import dateutil.parser


class HomeController(Controller):
    def handle(self, request):
        template = self.view.get_template("home.html")
        todos = Todo.list()
        return Response.render(template, {
            'todos': todos
        })

class TodoController(Controller):
    def create(self, request):
        template = self.view.get_template("create.html")
        if request.method == 'POST':
            post = request.post()
            try:
                todo = Todo()
                todo.subject = post['subject']
                todo.node = request.user
                todo.dead = dateutil.parser.parse(post['dead'])
                todo.done = False
                todo.create()
                return Response.redirect(self.app.convert_url('/'))
            except KeyError as err:
                return Response.render(template, {
                    'err': err
                })
        return Response.render(template)
    def done(self, request):
        todo = Todo.retrieve(int(request.params['id']))
        todo.done = True
        todo.update()
        return Response.redirect(self.app.convert_url('/'))

先程のルーティング先に設定したクラスとメソッドを作り込みます。

HomeController

class HomeController(Controller):
    def handle(self, request):
        template = self.view.get_template("home.html")
        todos = Todo.list()
        return Response.render(template, {
            'todos': todos
        })

前回作成したTodoモデルをすべて取得して表示しています。ModelClass.list()と書くことですべて取得することができます。

ちなみに、検索をしたい場合はFlask SQLAlchemyのようにフィルターを書けることができます。

done_todos = Todo.query.filter(Todo.done == False).all()

TodoController

class TodoController(Controller):
    def create(self, request):
        template = self.view.get_template("create.html")
        if request.method == 'POST':
            post = request.post()
            try:
                todo = Todo()
                todo.subject = post['subject']
                todo.node = request.user
                todo.dead = dateutil.parser.parse(post['dead'])
                todo.done = False
                todo.create()
                return Response.redirect(self.app.convert_url('/'))
            except KeyError as err:
                return Response.render(template, {
                    'err': err
                })
        return Response.render(template)
    def done(self, request):
        todo = Todo.retrieve(int(request.params['id']))
        todo.done = True
        todo.update()
        return Response.redirect(self.app.convert_url('/'))

POSTされた内容を取得するには、request.post()メソッドを使用します。

また、ログインしているMitamaのユーザーの情報を取得したい場合は、request.userプロパティにアクセスすることで簡単に取得することができます。

前回作成したモデルで、Todo.nodeの型にNodeを指定したため、この中に直接Userを入れることができます。

Mitamaではmitama.jsonに指定したpathによって配信されるURLが変わってしまうので、URLを扱う上では注意する必要があります。

return Response.redirect(self.app.convert_url('/'))

Controller.app.convert_uriを実行することで、URLのパスの先頭を調整することができます。

HTMLテンプレートを書く

最後に、HTMLテンプレートを書いてみましょう。
templatesの中に先程のコントローラー内で指定したファイルを作成し、書き込んでください。

今回はエラーの表示やCSRF対策は省略します。

templates/home.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
    </head>
    <body>
        <div>
            <a href='{{url('/create')}}'>Todoを作成</a>
        </div>
        <div>
        {% for todo in todos %}
            <div>
                <h3>{{todo.subject}}</h3>
                <div>{{todo.node.name}}</div>
                <div>期日: {{todo.dead}}</div>
                {% if todo.done %}
                    <div>完了済み</div>
                {% else %}
                    <form action='{{url('/'+todo._id|string+'/done')}}' method='POST'>
                        <button>完了</button>
                    </form>
                {% endif %}
            </div>
        {% endfor %}
        </div>
    </body>
</html>

テンプレート内部でも、URLのパスの先頭はurl関数を使って調整することができます。

templates/create.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
    </head>
    <body>
        <form method='POST'>
            <dl>
                <dt>題名</dt>
                <dd>
                    <input type='text' name='subject' required/>
                </dd>
                <dt>期日</dt>
                <dd>
                    <input type='datetime-local' name='dead' required/>
                </dd>
            </dl>
            <button>作成</button>
        </form>
    </body>
</html>

動かしてみる

mitama runを実行し、アクセスしてみましょう。まず、ログインを求められると思います。
2020-12-03-190133_1920x1080_scrot.png

ログインしてアプリを開くと、Todoを作成するというリンクだけの画面がでてきます。そこのリンクをクリックし、フォームを埋めて送信し、Todoができているか確認しましょう。

2020-12-03-190322_1920x1080_scrot.png

完了を押して表示が切り替われば成功です。

2020-12-03-190808_1920x1080_scrot.png

終わりに

ここまでで一通り動くものを作ることができましたね!ユーザーの認証やユーザーを扱うモデルの作り込みは振る舞いを決めてしまっているので、内製システムなどは簡単に作り込むことができます。

これからアドベントカレンダーの中で様々なお便利機能を紹介したいと思います。是非見ていってください(^^)

追伸

よかったらMitamaのリポジトリにスター付けてください

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
0
Help us understand the problem. What are the problem?