Djangoで家計簿のWebアプリケーションを作っています。
ビューが汚い
ここまで様々な機能を実装してきました。その結果、views.pyの中身がだいぶ見づらくなってしまっています。
money/views.py
import calendar import datetime from django.shortcuts import render, redirect from django.utils import timezone import matplotlib.pyplot as plt import pytz from .models import Money from .forms import SpendingForm plt.rcParams['font.family'] = 'IPAPGothic' #日本語の文字化け防止 # Create your views here. TODAY = str(timezone.now()).split('-') def index(request, year=TODAY[0], month=TODAY[1]): money = Money.objects.filter(use_date__year=year, use_date__month=month).order_by('use_date') total = index_utils.calc_month_pay(money) index_utils.format_date(money) form = SpendingForm() next_year, next_month = get_next(year, month) prev_year, prev_month = get_prev(year, month) context = {'year' : year, 'month' : month, 'prev_year' : prev_year, 'prev_month' : prev_month, 'next_year' : next_year, 'next_month' : next_month, 'money' : money, 'total' : total, 'form' : form } draw_graph(year, month) if request.method == 'POST': data = request.POST use_date = data['use_date'] cost = data['cost'] detail = data['detail'] category = data['category'] 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) Money.objects.create( use_date = use_date, detail = detail, cost = int(cost), category = category, ) return redirect(to='/money/{}/{}'.format(year, month)) return render(request, 'money/index.html', context) #...
どうにかしましょう。
リファクタリング
ビューの機能とは無関係の部分を抽出する
まずはビュー本来の機能である、コンテクストをHTMLに送るということ以外のことをしている部分を探し出して抽出していきましょう。まずはその月の支出合計を計算する部分は抽出できそうです。同様にして、データベースから日付をとってきて表示用に整形する部分も抽出できそうです。
money/utils/index_utils.py
def calc_month_pay(money): total = 0 for m in money: total += m.cost return total def format_date(money): for m in money: date = str(m.use_date).split(' ')[0] m.use_date = '/'.join(date.split('-')[1:3]) return None
ついでに前月と次月を計算する関数もこちらに移してしまいましょう。
money/utils/index_utils.py
def calc_month_pay(money): total = 0 for m in money: total += m.cost return total def format_date(money): for m in money: date = str(m.use_date).split(' ')[0] m.use_date = '/'.join(date.split('-')[1:3]) return None def get_next(year, month): year = int(year) month = int(month) if month == 12: return str(year + 1), '1' else: return str(year), str(month + 1) def get_prev(year, month): year = int(year) month = int(month) if month == 1: return str(year - 1), '12' else: return str(year), str(month - 1)
money/views.py
def index(request, year=TODAY[0], month=TODAY[1]): money = Money.objects.filter(use_date__year=year, use_date__month=month).order_by('use_date') total = index_utils.calc_month_pay(money) index_utils.format_date(money) form = SpendingForm() next_year, next_month = index_utils.get_next(year, month) prev_year, prev_month = index_utils.get_prev(year, month) context = {'year' : year, 'month' : month, 'prev_year' : prev_year, 'prev_month' : prev_month, 'next_year' : next_year, 'next_month' : next_month, 'money' : money, 'total' : total, 'form' : form } draw_graph(year, month) if request.method == 'POST': data = request.POST use_date = data['use_date'] cost = data['cost'] detail = data['detail'] category = data['category'] 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) Money.objects.create( use_date = use_date, detail = detail, cost = int(cost), category = category, ) return redirect(to='/money/{}/{}'.format(year, month)) return render(request, 'money/index.html', context)
ビュークラスを使う
これで少しはすっきりしましたが、通常表示されるときに実行される部分と、postを受け取ったときに実行される部分が混ざってしまっています。これを解決するには、ビューをクラスとして書きます。
money/views.py
import calendar import datetime from django.shortcuts import render, redirect from django.utils import timezone from django.views import View import matplotlib.pyplot as plt import pytz from .models import Money from .forms import SpendingForm from .utils import index_utils plt.rcParams['font.family'] = 'IPAPGothic' TODAY = str(timezone.now()).split('-') class MainView(View): def get(self, request, year=TODAY[0], month=TODAY[1]): money = Money.objects.filter(use_date__year=year, use_date__month=month).order_by('use_date') total = index_utils.calc_month_pay(money) index_utils.format_date(money) form = SpendingForm() next_year, next_month = index_utils.get_next(year, month) prev_year, prev_month = index_utils.get_prev(year, month) context = {'year' : year, 'month' : month, 'prev_year' : prev_year, 'prev_month' : prev_month, 'next_year' : next_year, 'next_month' : next_month, 'money' : money, 'total' : total, 'form' : form } draw_graph(year, month) return render(request, 'money/index.html', context) def post(self, request, year=TODAY[0], month=TODAY[1]): data = request.POST use_date = data['use_date'] cost = data['cost'] detail = data['detail'] category = data['category'] 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) Money.objects.create( use_date = use_date, detail = detail, cost = int(cost), category = category, ) return redirect(to='/money/{}/{}'.format(year, month)) #...
ここでは最も一般的なdjango.views.View
をテンプレートビューとして使っています。この変更に伴って、urls.pyも少し変更する必要があります。
money/urls.py
from django.urls import path from . import views app_name = 'money' urlpatterns = [ path('', views.MainView.as_view(), name='index'), path('<int:year>/<int:month>', views.MainView.as_view(), name='index'), ]
このようにas_view
を付けることによってクラスをビューとして呼び出すことができます。