From fa7416d0f3b0c1deb6a3af710c9972f9e3ea90ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Sat, 11 Jul 2020 17:23:38 +0800 Subject: [PATCH] Replaced the CashReportView list view with the cash view function, to simplify. Moved the pagination query parameter parser from the view to Pagination. --- accounting/urls.py | 2 +- accounting/views/__init__.py | 155 +++++++++-------------------------- mia_core/utils.py | 111 ++++++++++++++++--------- 3 files changed, 110 insertions(+), 158 deletions(-) diff --git a/accounting/urls.py b/accounting/urls.py index c23adf5..4c373a0 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -43,7 +43,7 @@ urlpatterns = [ path("", views.home, name="home"), path("cash", views.cash_home, name="cash.home"), path("cash//", - views.CashReportView.as_view(), name="cash"), + views.cash, name="cash"), path("cash-summary", mia_core_views.todo, name="cash-summary.home"), path("cash-summary/", diff --git a/accounting/views/__init__.py b/accounting/views/__init__.py index aa2859d..5c914d0 100644 --- a/accounting/views/__init__.py +++ b/accounting/views/__init__.py @@ -18,24 +18,20 @@ """The view controllers of the accounting application. """ -from datetime import date - -from django.http import HttpResponseRedirect, HttpResponse +from django.http import HttpResponseRedirect +from django.shortcuts import render from django.urls import reverse from django.utils import dateformat -from django.utils.decorators import method_decorator from django.utils.timezone import localdate from django.utils.translation import get_language, pgettext -from django.views import generic from django.views.decorators.http import require_GET from accounting.models import Record, Transaction, Subject -from mia_core.period import Period from mia import settings from mia_core.digest_auth import digest_login_required -from mia_core.utils import UrlBuilder, Pagination, \ - PageNoOutOfRangeException +from mia_core.period import Period +from mia_core.utils import Pagination @require_GET @@ -65,101 +61,24 @@ def cash_home(request): reverse("accounting:cash", args=(subject_code, period_spec))) -@method_decorator(digest_login_required, name='dispatch') -class BaseReportView(generic.ListView): - """A base account report. - - Attributes: - page_no (int): The specified page number - page_size (int): The specified page size - period (Period): The template period helper - subject (Subject): The currently-specified subject - """ - page_no = None - page_size = None - pagination = None - period = None - subject = None - - def get(self, request, *args, **kwargs): - """Adds object_list to the context. - - Args: - request (HttpRequest): The request. - args (list): The remaining arguments. - kwargs (dict): The keyword arguments. - - Returns: - The response - """ - if request.user.is_anonymous: - return HttpResponse(status=401) - try: - self.page_size = int(request.GET["page-size"]) - if self.page_size < 1: - return HttpResponseRedirect( - str(UrlBuilder(request.get_full_path()) - .del_param("page-size"))) - except KeyError: - self.page_size = None - except ValueError: - return HttpResponseRedirect( - str(UrlBuilder(request.get_full_path()) - .del_param("page-size"))) - try: - self.page_no = int(request.GET["page"]) - if self.page_no < 1: - return HttpResponseRedirect( - str(UrlBuilder(request.get_full_path()) - .del_param("page"))) - except KeyError: - self.page_no = None - except ValueError: - return HttpResponseRedirect( - str(UrlBuilder(request.get_full_path()) - .del_param("page"))) - try: - r = super(BaseReportView, self) \ - .get(request, *args, **kwargs) - except PageNoOutOfRangeException: - return HttpResponseRedirect( - str(UrlBuilder(request.get_full_path()) - .del_param("page"))) - return r - - def get_context_data(self, **kwargs): - data = super(BaseReportView, self).get_context_data(**kwargs) - data["period"] = self.period - data["subject"] = self.subject - data["pagination"] = self.pagination - return data - - -class CashReportView(BaseReportView): - """The accounting cash report.""" - http_method_names = ["get"] - template_name = "accounting/cash.html" - context_object_name = "records" - - def get_queryset(self): - """Return the accounting records for the cash report. - - Returns: - List[Record]: The accounting records for the cash report - """ - first_txn = Transaction.objects.order_by("date").first() - data_start = first_txn.date if first_txn is not None else None - last_txn = Transaction.objects.order_by("-date").first() - data_end = last_txn.date if last_txn is not None else None - self.period = Period( - get_language(), data_start, data_end, - self.kwargs["period_spec"]) - if self.kwargs["subject_code"] == "0": - self.subject = Subject(code="0") - self.subject.title_zhtw = pgettext( - "Accounting|", "Current assets and liabilities") - records = Record.objects.raw( - """SELECT r.* +@require_GET +@digest_login_required +def cash(request, subject_code, period_spec): + """The cash account report.""" + first_txn = Transaction.objects.order_by("date").first() + data_start = first_txn.date if first_txn is not None else None + last_txn = Transaction.objects.order_by("-date").first() + data_end = last_txn.date if last_txn is not None else None + period = Period( + get_language(), data_start, data_end, + period_spec) + # The list data + if subject_code == "0": + subject = Subject(code="0") + subject.title_zhtw = pgettext( + "Accounting|", "Current Assets And Liabilities") + records = Record.objects.raw( + """SELECT r.* FROM accounting_records AS r INNER JOIN (SELECT t1.sn AS sn, @@ -188,12 +107,11 @@ ORDER BY t.ord, CASE WHEN is_credit THEN 1 ELSE 2 END, r.ord""", - [self.period.start, self.period.end]) - else: - self.subject = Subject.objects.filter( - code=self.kwargs["subject_code"]).first() - records = Record.objects.raw( - """SELECT r.* + [period.start, period.end]) + else: + subject = Subject.objects.filter(code=subject_code).first() + records = Record.objects.raw( + """SELECT r.* FROM accounting_records AS r INNER JOIN (SELECT t1.sn AS sn, @@ -216,11 +134,14 @@ ORDER BY t.ord, CASE WHEN is_credit THEN 1 ELSE 2 END, r.ord""", - [self.period.start, - self.period.end, - self.subject.code + "%", - self.subject.code + "%"]) - self.pagination = Pagination( - self.request.get_full_path(), records, - self.page_no, self.page_size, True) - return self.pagination.records + [period.start, + period.end, + subject.code + "%", + subject.code + "%"]) + pagination = Pagination(request, records, True) + return render(request, "accounting/cash.html", { + "records": pagination.records, + "pagination": pagination, + "subject": subject, + "period": period, + }) diff --git a/mia_core/utils.py b/mia_core/utils.py index 07f6c6f..8cfb623 100644 --- a/mia_core/utils.py +++ b/mia_core/utils.py @@ -46,10 +46,10 @@ class UrlBuilder: return self.base_path = start_url[:pos] self.params = [] - for piece in start_url[pos+1:].split("&"): + for piece in start_url[pos + 1:].split("&"): pos = piece.find("=") name = urllib.parse.unquote(piece[:pos]) - value = urllib.parse.unquote(piece[pos+1:]) + value = urllib.parse.unquote(piece[pos + 1:]) self.params.append(self.Param(name, value)) def add_param(self, name, value): @@ -143,14 +143,11 @@ class UrlBuilder: urllib.parse.quote(self.value)) -DEFAULT_PAGE_SIZE = 10 - - class Pagination: """The pagination. Args: - current_url (str): The current URL + request (HttpRequest): The request records (list[Model]): All the records page_no (int): The specified page number page_size (int): The specified number of records per page @@ -158,8 +155,7 @@ class Pagination: page first Raises: - PageNoOutOfRangeError: if the specified page number is out - of range or is redundant. + PaginationException: With invalid pagination parameters Attributes: is_reversed (bool): Whether we should display the last @@ -182,30 +178,57 @@ class Pagination: page_no = None records = None - def __init__(self, current_url, records, page_no, - page_size, is_reversed=False): + DEFAULT_PAGE_SIZE = 10 + + def __init__(self, request, records, is_reversed=False): + current_url = request.get_full_path() self._current_url = current_url - self._base_url = UrlBuilder(current_url).del_param("page") self.is_reversed = is_reversed - self.page_size = page_size \ - if page_size is not None \ - else DEFAULT_PAGE_SIZE + + # The page size + try: + self.page_size = int(request.GET["page-size"]) + if self.page_size == self.DEFAULT_PAGE_SIZE: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page-size"))) + if self.page_size < 1: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page-size"))) + except KeyError: + self.page_size = self.DEFAULT_PAGE_SIZE + except ValueError: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page-size"))) self.total_pages = int( (len(records) - 1) / self.page_size) + 1 + default_page_no = 1 if not is_reversed else self.total_pages self.is_paged = self.total_pages > 1 + + # The page number + try: + self.page_no = int(request.GET["page"]) + if not self.is_paged: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page"))) + if self.page_no == default_page_no: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page"))) + if self.page_no < 1: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page"))) + if self.page_no > self.total_pages: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page"))) + except KeyError: + self.page_no = default_page_no + except ValueError: + raise PaginationException(str( + UrlBuilder(current_url).del_param("page"))) + if not self.is_paged: self.page_no = 1 self.records = records - self._links = [] return - default_page = 1 if not is_reversed else self.total_pages - if page_no == default_page: - raise PageNoOutOfRangeException() - self.page_no = page_no \ - if page_no is not None \ - else default_page - if self.page_no > self.total_pages: - raise PageNoOutOfRangeException() start_no = self.page_size * (self.page_no - 1) self.records = records[start_no:start_no + self.page_size] @@ -294,7 +317,7 @@ class Pagination: link.url = str(base_url) else: link.url = str(base_url.clone().add_param( - "page", str(self.total_pages))) + "page", str(self.total_pages))) else: link.url = str(base_url.clone().add_param( "page", str(self.page_no + 1))) @@ -321,9 +344,24 @@ class Pagination: def page_size_options(self): base_url = UrlBuilder(self._current_url).del_param( "page").del_param("page-size") - return [self.PageSizeOption(x, _page_size_url(base_url, x)) + return [self.PageSizeOption(x, self._page_size_url(base_url, x)) for x in [10, 100, 200]] + @staticmethod + def _page_size_url(base_url, size): + """Returns the URL for a new page size. + + Args: + base_url (UrlBuilder): The base URL builder. + size (int): The new page size. + + Returns: + str: The URL for the new page size. + """ + if size == Pagination.DEFAULT_PAGE_SIZE: + return str(base_url) + return str(base_url.clone().add_param("page-size", str(size))) + class PageSizeOption: """A page size option. @@ -343,23 +381,16 @@ class Pagination: self.url = url -def _page_size_url(base_url, size): - """Returns the URL for a new page size. +class PaginationException(Exception): + """The exception thrown with invalid pagination parameters. Args: - base_url (UrlBuilder): The base URL builder. - size (int): The new page size. + url (str): The canonical URL to redirect to. - Returns: - str: The URL for the new page size. + Attributes: + url (str): The canonical URL to redirect to. """ - if size == DEFAULT_PAGE_SIZE: - return str(base_url) - return str(base_url.clone().add_param("page-size", str(size))) + url = None - -class PageNoOutOfRangeException(Exception): - """The error thrown when the specified page number is out of - range. - """ - pass + def __init__(self, url): + self.url = url