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

[Mitama Advent Calendar](https://qiita.com/advent-calendar/2020/mitama)4日目です。

前回→『[MitamaでTodoアプリを作ってみる(2) - Modelを作りこむ](https://qiita.com/ta1g3n/items/d0bc09af8b82a4b4280a)』

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

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

```python
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という２つのコントローラーを作成します。

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

```python
view("/", HomeController),
view("/create", TodoController, 'create'),
view("/<id>/done", TodoController, 'done'),
view("/static/<path:path>", static_files()),
```
メソッド名を指定しない場合はhandleメソッドが呼び出されます。

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

```python
middlewares = [SessionMiddleware]
```

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

## Controllerを作る

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

```python
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

```python
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のようにフィルターを書けることができます。

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

### TodoController

```python
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を扱う上では注意する必要があります。

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

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

## HTMLテンプレートを書く

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

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

### templates/home.html

```jinja
<!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

```jinja
<!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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381826/2515b994-c33d-00ce-861e-1de213178216.png)

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

![2020-12-03-190322_1920x1080_scrot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381826/27c656b4-1c43-5cc5-591a-e5a92b29193a.png)

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

![2020-12-03-190808_1920x1080_scrot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381826/2275bf41-8fcf-65c5-2bf1-c6663a589769.png)

## 終わりに

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

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



### 追伸
よかったら[Mitamaのリポジトリ](https://github.com/mitama-org/mitama)にスター付けてください