DjangoでTodoアプリを作る

2021年06月09日

こんにちは!
今日はPythonのWebフレームワーク「Django」でTodoアプリ作ります。ハンズオン的な内容なので細かな話は抜きにして雰囲気をお伝えできればと思います。

目次

Djangoをインストールする

$ pip install django

プロジェクトを作る

$ django-admin startproject sample_todo

プロジェクトの雛形を作ります。

$ cd sample_todo
$ django-admin startapp todoapp

プロジェクトに「todoapp」というWebアプリを作ります。

Webアプリを作成したら設定ファイルのINSTALLED_APPSに登録します。

sample_todo/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todoapp',
]

データベースの初期値を作る

$ python manage.py migrate

db.sqlite3 というファイルが出来上がります。これがDBです。

データベーステーブルを作る

todoapp/models.py
class Todo(models.Model):
    memo = models.TextField()
    is_done = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

todoapp/models.py にModelを1つ作成します。Todoのメモと完了フラグを管理するだけのシンプルなModelとします。

$ python manage.py makemigrations todoapp
Migrations for 'todoapp':
  todoapp/migrations/0001_initial.py
    - Create model Todo

定義したModelからfixtureを作成します。

$ python manage.py migrate todoapp
Operations to perform:
  Apply all migrations: todoapp
Running migrations:
  Applying todoapp.0001_initial... OK

DBにテーブルを作成しました。
念の為テーブルが出来ているかsqlite3コマンドで確認しておきます。

sqlite> .schema todoapp_todo
CREATE TABLE IF NOT EXISTS "todoapp_todo" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "memo" text NOT NULL, "is_done" bool NOT NULL, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);

Viewを作る

View(MVCだとControllerと呼ばれる層)を作ります。 todoappを作成した時点でdjango-adminがviewファイル todoapp/views.py を自動生成してくれています。こちらに実装を入れていきます。

todoapp/views.py
from django.shortcuts import render, redirect
from django.views import View
from todoapp.models import Todo


class IndexView(View):
    '''トップ画面のView'''

    def get(self, request):
        '''GETリクエストを受け付けたとき'''
        # DBから更新日時降順でTODOを取得してテンプレートに渡す
        todos = Todo.objects.all().order_by('-updated_at')
        return render(request, 'todoapp/index.html', {
            'todos': todos
        })

    def post(self, request):
        '''POSTリクエストでTODO登録がsubmitされたとき'''
        # フォームから渡ってくるリクエストボディの内容をDBに記録する
        memo = request.POST['memo']
        todo = Todo(memo=memo)
        todo.save()
        return redirect('/')

画面アクセスはGETリクエストで受け付けて、同URLパスのPOSTでテキストをフォームから受け取ります。Class basedなView実装にしました。

getメソッド実装におけるポイントはrender関数です。第二引数にHTMLテンプレートのファイルパス、第三引数にテンプレートに変数として渡したいデータを辞書形式で指定します。

Templateを作る

プレゼンテーション層となるHTMLテンプレートを作成します。 まずはテンプレートを格納するためのディレクトリを作成します。

$ mkdir -p todoapp/templates/todoapp

このディレクトリの中にindex.htmlを作成します。

todoapp/templates/todoapp/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
</head>
<body>

<!-- 新しいTODOを登録するフォーム -->
<div>
  <form action="/" method="post">{% csrf_token %}
    <input type="text" name="memo" />
    <input type="submit" value="送信" />
  </form>
</div>

<!-- 登録されたTODOを表示するリスト -->
<ul>
{% for todo in todos %}
<li>
  <p>
  <span>{{ todo.memo }}</span>
  <span>{{ todo.updated_at }}</span>
  </p>
</li>
{% endfor %}
</ul>

</body>
</html>

さきほど作ったViewのGETリクエストのハンドラでは、DBからtodoを読みだしてModelの配列をテンプレートに渡しています。 forループで順にリスト表示する際にテンプレート変数として todo を利用していますね!(以下の部分)

{% for todo in todos %}
<li>
  <p>
  <span>{{ todo.memo }}</span>
  <span>{{ todo.updated_at }}</span>
  </p>
</li>
{% endfor %}

URLのルーティングを定義する

仕上げです。作成したViewをURLにマッピングしてブラウザでアクセスできるようにします。 sample_todo/urls.py を以下のように修正します。

sample_todo/urls.py
from django.contrib import admin
from django.urls import path
from todoapp.views import IndexView

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

    # これを追加
    path('', IndexView.as_view()),
]

ブラウザで動作確認

Djangoに標準バンドルされているデバッグサーバを立ち上げます。

$ python manage.py runserver 8001

サーバが立ち上がったら http://localhost:8001 でアクセスできます。

フォームから適当なテキストを入力して送信してみましょう。 POSTされた内容がフォーム下に一覧表示されました。

Djangoで作成したTodoアプリの画面

まとめ

Djangoで簡単なWebアプリを作ってみました。フレームワークが提供する各機能コンポーネントのエッセンスが伝われば幸いです。

Djangoはフルスタック系フレームワークで認知されているので、Flaskなどの軽量系フレームワークと比べると学習コストが高いと言われますが、個人的にはそれほど敷居高くないと思います。

拡張ライブラリも多く、様々なユースケースに対応できる柔軟なフレームワークなので、ぜひ周りに布教いただければと思います!

それでは、良きPythonライフを!


Web系エンジニアでPython好き。バックエンド/フロントエンド問わずマルチな方面でエンジニアリングしています。