Replaced the function-based txn_sort view with the class-based TransactionSortView in the accounting application.

This commit is contained in:
依瑪貓 2020-08-16 22:38:35 +08:00
parent b829002c61
commit 8f6c8f3497
4 changed files with 78 additions and 59 deletions

View File

@ -18,8 +18,9 @@
"""The forms of the Mia core application. """The forms of the Mia core application.
""" """
import datetime
import re import re
from typing import Optional from typing import Optional, List, Dict
from django import forms from django import forms
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
@ -27,7 +28,7 @@ from django.db.models import Q, Max
from django.db.models.functions import Length from django.db.models.functions import Length
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from .models import Account, Record from .models import Account, Record, Transaction
from .validators import validate_record_account_code, validate_record_id from .validators import validate_record_account_code, validate_record_id
@ -342,6 +343,41 @@ class TransactionForm(forms.Form):
if "amount" in x.data and "amount" not in x.errors]) if "amount" in x.data and "amount" not in x.errors])
class TransactionSortForm(forms.Form):
"""A form to sort the transactions in a same day."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.date = None
self.txn_list: Optional[List[Transaction]] = None
self.txn_orders: List[TransactionSortForm.Order] = []
@staticmethod
def from_post(date: datetime.date, post: Dict[str, str]):
form = TransactionSortForm({})
form.date = date
post_orders: List[TransactionSortForm.Order] = []
for txn in Transaction.objects.filter(date=date).all():
key = F"transaction-{txn.pk}-ord"
if key not in post:
post_orders.append(form.Order(txn, 9999))
elif not re.match("^[0-9]+$", post[key]):
post_orders.append(form.Order(txn, 9999))
else:
post_orders.append(form.Order(txn, int(post[key])))
post_orders.sort(key=lambda x: (x.order, x.txn.ord))
form.txn_orders = []
for i in range(len(post_orders)):
form.txn_orders.append(form.Order(post_orders[i].txn, i + 1))
form.txn_list = [x.txn for x in form.txn_orders]
return form
class Order:
"""A transaction order"""
def __init__(self, txn: Transaction, order: int):
self.txn = txn
self.order = order
class AccountForm(forms.Form): class AccountForm(forms.Form):
"""An account form.""" """An account form."""
code = forms.CharField( code = forms.CharField(

View File

@ -27,7 +27,7 @@ First written: 2020/8/6
{% load accounting %} {% load accounting %}
{% block settings %} {% block settings %}
{% blocktrans asvar title with date=date|smart_date %}Reorder the Transactions in {{ date }}{% endblocktrans %} {% blocktrans asvar title with date=form.date|smart_date %}Reorder the Transactions in {{ date }}{% endblocktrans %}
{% setvar "title" title %} {% setvar "title" title %}
{% setvar "use_jqueryui" True %} {% setvar "use_jqueryui" True %}
{% static "accounting/css/report.css" as file %}{% add_css file %} {% static "accounting/css/report.css" as file %}{% add_css file %}
@ -50,12 +50,12 @@ First written: 2020/8/6
</div> </div>
<div id="txn-date" class="col-sm-10"> <div id="txn-date" class="col-sm-10">
{{ date|smart_date }} {{ form.date|smart_date }}
</div> </div>
</div> </div>
{% if txn_list|length > 1 %} {% if form.txn_list|length > 1 %}
<form action="{% url "accounting:transactions.sort" date as url %}{% url_keep_return url %}" method="post"> <form action="{% url "accounting:transactions.sort" form.date as url %}{% url_keep_return url %}" method="post">
{% csrf_token %} {% csrf_token %}
<table class="table general-journal-table"> <table class="table general-journal-table">
<thead> <thead>
@ -68,7 +68,7 @@ First written: 2020/8/6
</tr> </tr>
</thead> </thead>
<tbody id="transactions"> <tbody id="transactions">
{% for txn in txn_list %} {% for txn in form.txn_list %}
<tr id="transaction-{{ txn.pk }}" class="transaction {% if not txn.is_balanced %} table-danger {% endif %}"> <tr id="transaction-{{ txn.pk }}" class="transaction {% if not txn.is_balanced %} table-danger {% endif %}">
<td class="actions"> <td class="actions">
<div class="btn-group"> <div class="btn-group">

View File

@ -84,7 +84,7 @@ urlpatterns = [
path("transactions/<txn:txn>/delete", path("transactions/<txn:txn>/delete",
views.TransactionDeleteView.as_view(), name="transactions.delete"), views.TransactionDeleteView.as_view(), name="transactions.delete"),
path("transactions/sort/<date:date>", path("transactions/sort/<date:date>",
views.txn_sort, name="transactions.sort"), views.TransactionSortFormView.as_view(), name="transactions.sort"),
path("accounts", path("accounts",
views.AccountListView.as_view(), name="accounts"), views.AccountListView.as_view(), name="accounts"),
path("accounts/create", path("accounts/create",

View File

@ -18,7 +18,6 @@
"""The view controllers of the accounting application. """The view controllers of the accounting application.
""" """
import datetime
import json import json
import re import re
from typing import Dict, Optional from typing import Dict, Optional
@ -39,14 +38,13 @@ from django.utils.translation import gettext as _, gettext_noop
from django.views.decorators.http import require_GET, require_POST from django.views.decorators.http import require_GET, require_POST
from django.views.generic import RedirectView, ListView, DetailView from django.views.generic import RedirectView, ListView, DetailView
from mia_core import stored_post
from mia_core.digest_auth import login_required from mia_core.digest_auth import login_required
from mia_core.period import Period from mia_core.period import Period
from mia_core.utils import Pagination, get_multi_lingual_search, UrlBuilder, \ from mia_core.utils import Pagination, get_multi_lingual_search, \
strip_post, PaginationException PaginationException
from mia_core.views import DeleteView, FormView from mia_core.views import DeleteView, FormView
from . import utils from . import utils
from .forms import AccountForm, TransactionForm from .forms import AccountForm, TransactionForm, TransactionSortForm
from .models import Record, Transaction, Account from .models import Record, Transaction, Account
@ -882,59 +880,44 @@ class TransactionDeleteView(DeleteView):
return self.request.GET.get("r") or reverse("accounting:home") return self.request.GET.get("r") or reverse("accounting:home")
@login_required @method_decorator(login_required, name="dispatch")
def txn_sort(request: HttpRequest, date: datetime.date) -> HttpResponse: class TransactionSortFormView(FormView):
"""The view for the form to sort the transactions in a same day. """The form to sort the transactions in a same day."""
template_name = "accounting/transaction-sort.html"
form_class = TransactionSortForm
not_modified_message = gettext_noop(
"The transaction orders were not modified.")
success_message = gettext_noop(
"The transaction orders were saved successfully.")
Args: def get_form(self, **kwargs):
request: The request. """Returns the form for the template."""
date: The day. form = super().get_form()
if form.txn_list is None:
form.date = self.kwargs["date"]
form.txn_list = Transaction.objects.filter(date=form.date)\
.order_by("ord").all()
if len(form.txn_list) < 2:
raise Http404
return form
Returns: def make_form_from_post(self, post: Dict[str, str]) -> TransactionSortForm:
The response. """Creates and returns the form from the POST data."""
return TransactionSortForm.from_post(self.kwargs["date"], post)
Raises:
Http404: When there are less than two transactions in this day.
"""
transactions = Transaction.objects.filter(date=date).order_by("ord")
if len(transactions) < 2:
raise Http404
if request.method != "POST":
return render(request, "accounting/transaction-sort.html", {
"txn_list": transactions,
"date": date,
})
else:
post = request.POST.dict()
errors = {}
for txn in transactions:
key = F"transaction-{txn.pk}-ord"
if key not in post:
errors[key] = gettext_noop("Invalid arguments.")
elif not re.match("^[1-9][0-9]*", post[key]):
errors[key] = gettext_noop("Invalid order.")
if len(errors) > 0:
return stored_post.error_redirect(
request, reverse("accounting:transactions.sort"), post)
keys = [F"transaction-{x.pk}-ord" for x in transactions]
keys.sort(key=lambda x: int(post[x]))
for i in range(len(keys)):
post[keys[i]] = i + 1
modified = [[x, post[F"transaction-{x.pk}-ord"]] for x in transactions
if x.ord != post[F"transaction-{x.pk}-ord"]]
def form_valid(self, form: TransactionSortForm) -> HttpResponseRedirect:
"""Handles the action when the POST form is valid."""
modified = [x for x in form.txn_orders if x.txn.ord != x.order]
if len(modified) == 0: if len(modified) == 0:
message = gettext_noop("The transaction orders were not modified.") message = self.get_not_modified_message(form.cleaned_data)
else: else:
with transaction.atomic(): with transaction.atomic():
for x in modified: for x in modified:
Transaction.objects.filter(pk=x[0].pk).update(ord=x[1]) Transaction.objects.filter(pk=x.txn.pk).update(ord=x.order)
message = gettext_noop( message = self.get_success_message(form.cleaned_data)
"The transaction orders were saved successfully.") messages.success(self.request, message)
messages.success(request, message) return redirect(self.request.GET.get("r")
return redirect(request.GET.get("r") or reverse("accounting:home")) or reverse("accounting:home"))
@method_decorator(require_GET, name="dispatch") @method_decorator(require_GET, name="dispatch")