pyhaya’s diary

プログラミング、特にPythonについての記事を書きます。Djangoや機械学習などホットな話題をわかりやすく説明していきたいと思います。

Djangoで家計簿のWebアプリケーションを作ってみる

Djangoで家計簿アプリを作ります。完成イメージは、次のような感じです。

  • 月ごとの支出の様子がテーブルで見られる
  • 日付と金額、用途を入力してデータを登録できる
  • 日ごとの支出の推移をグラフで可視化

Money Fowardでいいじゃんという突込みが聞こえてきそうですが、無視します。今回は上の2つをやってみたいと思います。

Djangoにあまり慣れていない方は、過去にチュートリアルっぽいものも書いているのでぜひ読んでみてください(アーカイブで2018年10月のところあたりにいくつかあります)。

開発環境

準備作業

プロジェクトを作る

まずはお決まりのコマンドです。プロジェクトの名前はあまり深く考えずに家計簿を意味する「housekeeping book」にしました。

bash

mkdir housekeeping_book
cd housekeeping_book
django-admin startproject config .    #おおもとのフォルダと、プロジェクトのsettingとかが入ってるフォルダの名前を同じにしたくなかった
python manage.py startapp money

初期設定

config/settings.py

#...
INSTALLED_APPS = [
    'money.apps.MoneyConfig',    # <-これを追加(多分33行目あたり)
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
#...
LANGUAGE_CODE = 'ja'    #多分107行目あたりにある
TIME_ZONE = 'Asia/Tokyo'
#...

アプリの中身を作る

データベース(モデル)を作る

データベースにどんな情報が必要か考える。ひとまずは次のものが必要だろう。

  • 出費があった日付
  • 支出額
  • 使い道

あとでこれはほぼ確実に増えるけど、まずはこれで作ってみる。

money/models.py

from django.db import models

# Create your models here.
class Money(models.Model):
    use_date = models.DateTimeField('日付')
    detail = models.CharField(max_length=200)
    cost = models.IntegerField(default=0)

    def __str__(self):
        return self.detail + ' ¥' + str(self.cost)

管理者画面でMoneyを見たときには使い道と費用が見えるようにしておいた(def __str__(self))。

ではマイグレーション

python manage.py makemigrations
python manage.py migrate

URLの設定

moneyアプリケーション用のURLの設定をする。これもお決まりのコード。

money/urls.py

from django.urls import path
from . import views

app_name = 'money'
urlpatterns = [
        path('', views.index, name='index'),    #views.indexはまだ作ってないからあとで作る
        ]

config/urls.py

from django.contrib import admin
from django.urls import path, include

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

ページを作る(ただの動作確認)

とりあえず簡単なページを作ってここまで問題ないか確認しておく。

money/views.py

from django.shortcuts import render, redirect
from django.utils import timezone

def index(request):
    today = str(timezone.now()).split('-')
    context = {
        'year' : today[0],
        'month' : today[1],
    }

    return render(request, 'money/index.html', context)

timezone.now()で得られる日付データは無駄にデータが多いので、strに変換してからのsplitで年と月だけ取り出せるようにしておいた。

templatesフォルダーを作って、

money/templates/money/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>HousekeepingBook</title>
        <link rel="stylesheet" type="text/css"
        href="{% static 'money/style.css' %}">
    </head>
    <body>
        <h1>{{ year }}年{{ month }}月</h1>
    </body>
</html>    

これでpython manage.py runserver
http://127.0.0.1:8000/money
にアクセスするとページの上部に2018年11月(執筆段階で)とでかでかと表示される。

月の支出を全部表示させる

ここから機能を実装していく。まずは、データベースに登録されているデータを全部表示させる。

money/views.py

from django.shortcuts import render, redirect 
from django.utils import timezone

from .models import Money

def index(request):
    today = str(timezone.now()).split('-')
    money = Money.objects.all()
    for m in money:
        date = str(m.use_date).split(' ')[0]
        m.use_date = '/'.join(date.split('-')[1:3])

    context = {'year' : today[0],
            'month' : today[1],
            'money' : money,
    }

    return render(request, 'money/index.html', context)     

money/templates/money/index.html

<!DOCTYPE html>
{% load static %}
<html>
    <head>
        <meta charset="utf-8">
        <title>HousekeepingBook</title>
        <link rel="stylesheet" type="text/css"
        href="{% static 'money/style.css' %}">
    </head>
    <body>
        <h1>{{ year }}年{{ month }}月</h1>
        <table>
            <tr>
                <th>日付</th>
                <th>用途</th>
                <th>金額</th>
            </tr>
            {% for m in money %}
            <tr>
                <td>{{ m.use_date }}</td>
                <td>{{ m.detail }}</td>
                <td>{{ m.cost }}円</td>
            </tr>
            {% endfor %}
    </body>
</html>

表示させてみたらあまりにしょぼかったのでちょっとCSS使いました。

money/static/money/style.css

table{
  border-collapse:collapse;
  margin:0 0;
}
th{
  color:#005ab3;
}
td{
  border-bottom:1px dashed #999;
}
th,tr:last-child td{
  border-bottom:2px solid #005ab3;
}
td,th{
  padding:10px;

データベースにはまだ何も登録してないので何も表示されません。手っ取り早く表示を確認したい人はpython manage.py createsuperuserで管理者アカウント作ってconfig/admin.pyにモデルを登録して、管理者サイトからモデルをいくつか登録してみるといいと思います。

支出を登録する

次はフォームを作ります。必要な入力欄は3つ、日付と金額、そして用途です。

money/forms.py

from django import forms

from .models import Money

class SpendingForm(forms.Form):
    use_date = forms.DateTimeField(label='日付')
    cost = forms.IntegerField(label='金額')
    detail = forms.CharField(
            max_length=200,
            label='用途'
            )

money/views.py

from django.shortcuts import render, redirect
from django.utils import timezone
import pytz
import datetime

from .models import Money
from .forms import SpendingForm
# Create your views here.
def index(request):
    today = str(timezone.now()).split('-')
    money = Money.objects.all()
    for m in money:
        date = str(m.use_date).split(' ')[0]
        m.use_date = '/'.join(date.split('-')[1:3])

    form = SpendingForm()    #フォームを読み込む
    context = {'year' : today[0],
            'month' : today[1],
            'money' : money,
            'form' : form
            }

    if request.method == 'POST':    # フォームでデータが送られてきたら
        data = request.POST
        use_date = data['use_date']
        cost = data['cost']
        detail = data['detail']

        use_date = timezone.datetime.strptime(use_date, "%Y/%m/%d")
        tokyo_timezone = pytz.timezone('Asia/Tokyo')    #タイムゾーンを設定
        use_date = tokyo_timezone.localize(use_date)
        use_date += datetime.timedelta(hours=9)    #時間を9時間遅らせる

        Money.objects.create(    # データベースにデータを入れる
                use_date = use_date,
                detail = detail,
                cost = int(cost),
                )
        return redirect(to='/money/')    #再び/money/を読み込む


    return render(request, 'money/index.html', context)      

タイムゾーンを'Asia/Tokyo'に設定してもなぜか世界標準時になってしまうのでdatetime.timedeltaで9時間遅らせています。ここら辺に詳しい人がいたらなぜこのようになるのか教えてくださるとうれしいです。

ここでの変更をHTMLにも反映させます。

money/templates/money/index.html

<!DOCTYPE html>
{% load static %}
<html>
    <head>
        <meta charset="utf-8">
        <title>HousekeepingBook</title>
        <link rel="stylesheet" type="text/css"
        href="{% static 'money/style.css' %}">
    </head>
    <body>
        <h1>{{ year }}年{{ month }}月</h1>
        <form action="/money/" method="post">
            {% csrf_token %}
            {{ form.as_table }}
            <input type="submit" value="送信">
        </form>

        <table>
            <tr>
                <th>日付</th>
                <th>用途</th>
                <th>金額</th>
            </tr>
            {% for m in money %}
            <tr>
                <td>{{ m.use_date }}</td>
                <td>{{ m.detail }}</td>
                <td>{{ m.cost }}円</td>
            </tr>
            {% endfor %}
        </table>
    </body>
</html>        

ここまでの成果

ここまでのコードを走らせると次のようになります。
f:id:pyhaya:20181104175733p:plain

形はできましたが、日付順に並べたり、11月だけのものを表示させたりという機能を実装する必要もあります。それは次回行いたいと思います。