Python DjangoとMDBで作る株取引ノートシリーズの6つ目の記事です。
今回は取引モデルを画面から追加できるようにしていきましょう。
urls.pyに追加します。
# note/urls.py
...
urlpatterns = [
...
path('transaction_create/', views.TransactionCreate.as_view(), name='transaction_create'), # 追加
]
次に作成用のフォームをforms.pyに追記します。
# note/forms.py
from django import forms
from .models import History, Transaction # 追加
...
class TransactionCreateForm(forms.ModelForm):
"""取引追加フォーム"""
class Meta:
model = Transaction
fields = '__all__'
widgets = {
'reason': forms.Textarea(attrs={'rows': '3'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs["class"] = "form-control"
field.widget.attrs["autocomplete"] = "off"
reason部分のみこのようにしているのは、行数を指定しないと、枠が大きくなりすぎてしまうからです。
widgets = {
'reason': forms.Textarea(attrs={'rows': '3'}),
}
次にviewの追記です。
# note/views.py
from django.views import generic
from .models import Transaction, History
from .forms import TransactionSearchForm, HistoryCreateForm, TransactionCreateForm # 追加
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
...
class TransactionCreate(generic.CreateView):
model = Transaction
template_name = 'note/transaction_create.html'
form_class = TransactionCreateForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['breadcrumbs_list'] = [{'name': 'Transaction Create',
'url': ''}]
return context
def get_success_url(self):
return reverse_lazy('note:transaction_list')
ヘッダーにリンクを貼りましょう。
<!-- note/templates/note/components/header.html -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
...
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/admin">Admin</a>
</li>
<!-- 追加 -->
<li class="nav-item">
<a class="nav-link" href="{% url 'note:transaction_create' %}">
<i class="fa fa-plus"></i>
New
</a>
</li>
</ul>
</div>
</div>
</nav>
transaction_create.htmlを作成します。
<!-- note/templtes/note/transaction_create.html -->
{% extends "note/base.html" %}
{% block content %}
<form class="mt-4" method="POST">
{% csrf_token %}
<div class="row mt-2">
<div class="col-md-2">
<label class="form-label" for="id_status">Status</label>
{{ form.status }}
</div>
<div class="col-md-2">
<label class="form-label" for="id_result">Result</label>
{{ form.result }}
</div>
</div>
<div class="row mt-2">
<div class="col-md-2">
<label class="form-label" for="id_date_entry">DateEntry</label>
{{ form.date_entry }}
</div>
<div class="col-md-2">
<label class="form-label" for="id_date_close">DateClose</label>
{{ form.date_close }}
</div>
</div>
<div class="row mt-2">
<div class="col-md-2">
<label class="form-label" for="id_benefit">Benefit</label>
{{ form.benefit }}
</div>
</div>
<div class="row">
<div class="col-md-2">
<div class="mt-2">
<label class="form-label" for="id_ticker_code">TickerCode</label>
{{ form.ticker_code }}
</div>
</div>
<div class="col-md-10">
<div class="mt-2">
<label class="form-label" for="id_ticker_name">TickerName</label>
{{ form.ticker_name }}
</div>
</div>
</div>
<div class="mt-3">
<label class="form-label" for="id_reason">Reason</label>
{{ form.reason }}
</div>
<div class="mt-2">
<label class="form-label" for="id_memo">Memo</label>
{{ form.memo }}
</div>
<button class="btn btn-primary mt-5" type="submit">Send</button>
</form>
{% endblock %}
{% block extrajs %}
<!-- date picker -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript">
const config = {
dateFormat: 'yy-mm-dd',
firstDay: 1,
dayNamesMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
monthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
showOtherMonths: true,
selectOtherMonths: true
}
$(function() {
$('#id_date_entry').datepicker(config);
$('#id_date_close').datepicker(config);
})
</script>
{% endblock %}
入力フォームとしては以下の写真ような見た目になります。
次に更新処理を実装していきます。
urls.pyに更新用のパターンを追記をします。
# note/urls.py
urlpatterns = [
...
path('transaction_create/', views.TransactionCreate.as_view(), name='transaction_create'),
path('transaction_update/<int:pk>/', views.TransactionUpdate.as_view(), name='transaction_update'), # 追加
]
次にviewにTransactionUpdateクラスを作ります。
作成処理で使用したformとテンプレートを使いまわすようにしましょう。
# note/views.py
...
class TransactionUpdate(generic.UpdateView):
model = Transaction
template_name = 'note/transaction_create.html'
form_class = TransactionCreateForm
def get_success_url(self):
view_name = 'note:detail'
pk = self.object.pk
return reverse_lazy(view_name, kwargs={'pk': pk})
遷移元ですが、取引一覧ページと詳細ページどちらからでもアクセスできるようにします。
まずtransaction_table.htmlを編集して以下のように更新用アイコンの列を追加しましょう。
<!-- note/templates/note/components/transaction_table.html -->
{% load humanize %}
<table class="table mt-4">
<thead>
<tr>
...
<th scope="col">Benefit</th>
<!-- 最後に列を追加 -->
<th scope="col">Edit</th>
</tr>
</thead>
<tbody>
{% for t in transaction_list %}
<tr>
...
<!-- 最後に追加 -->
<th>
<a href="{% url 'note:transaction_update' t.pk %}"><i class="fas fa-edit fs-5"></i></a>
</th>
</tr>
{% endfor %}
</tbody>
</table>
このような見た目になります。
詳細ページの方は以下のようにリンクを設定します。
<!-- note/templates/note/detail.html -->
{% extends "note/base.html" %}
{% block content %}
<h1 class="mt-2">
<!-- リンク追加 -->
<a href="{% url 'note:transaction_update' transaction.pk %}">
{{ transaction.ticker_code }} {{ transaction.ticker_name }}
</a>
</h1>
...
{% endblock %}
...
次にパンくずリストの設定ですが、今回はトップページと詳細ページから遷移するパターンに分かれています。
なので、パンくずリストも動的に遷移元が分かるようにしていきます。
Djangoでは遷移元のページのurlは、request.environ.get('HTTP_REFERER')
とすると取得できます。
なのでここの文字列で処理を分岐させてパンくずリストを生成するようにします。
TransactionUpdateにget_context_dataを追記していきます。
# note/views.py
...
class TransactionUpdate(generic.UpdateView):
model = Transaction
template_name = 'note/transaction_create.html'
form_class = TransactionCreateForm
# 追加
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = self.kwargs.get('pk')
# 前のページのURL
referer = self.request.environ.get('HTTP_REFERER')
# detailページから来た場合
if 'detail' in referer:
context['breadcrumbs_list'] = [
{'name': f'#{pk} {self.object.ticker_name}',
'url': f'/detail/{pk}/'},
{'name': 'Transaction Update',
'url': ''}
]
# トップページから来た場合
else:
context['breadcrumbs_list'] = [
{'name': 'Transaction Update',
'url': ''}
]
benefit = 0
for history in History.objects.filter(target=pk):
if history.trading_category == 'Buy':
benefit -= history.amount
else:
benefit += history.amount
context['benefit'] = benefit
return context
def get_success_url(self):
view_name = 'note:detail'
pk = self.object.pk
return reverse_lazy(view_name, kwargs={'pk': pk})
...
contextに加えているbenefitは売買履歴から算出した損益です。
取引ノートの使い方として、以下のような流れを想定しています。
取引をクローズする際に損益をその都度計算するのは面倒です。
なので、入力の補助として更新画面で履歴から算出された損益を表示しておくことにします。
transaction_create.htmlの以下の部分に追記しておきましょう。
<!-- note/templtes/note/transaction_create.html -->
...
<div class="col-md-2">
<label class="form-label" for="id_benefit">Benefit</label>
{{ form.benefit }}
<!-- 追加 -->
{% if benefit %}
<p class="text-secondary mt-2">{{ benefit }}</p>
{% endif %}
</div>
このようにBenefitフォームの下に数値が入ります。
これで入力も簡単です。
次回は取引モデルのMemoの部分をリッチエディタに対応させていきます。