Compare commits
27 Commits
0d8cf85ec0
...
50f8f06687
Author | SHA1 | Date | |
---|---|---|---|
50f8f06687 | |||
cd5b1b97fd | |||
b7dd53d2f9 | |||
b07b0e3be4 | |||
e7fb2288ce | |||
17ba7659b6 | |||
2c8d5e7c8a | |||
e2f707f696 | |||
b5c0d0b7b3 | |||
7fe2bb6135 | |||
4d870f1dcc | |||
16b2eb1c93 | |||
fd63149066 | |||
a7a432914d | |||
1a44f08b90 | |||
3e68cfe690 | |||
809f2b6df3 | |||
c286aa8b8b | |||
1326d9538c | |||
b9cecf343a | |||
3d9e6c10da | |||
5090e59bb1 | |||
62697fb782 | |||
8c462e7b2c | |||
90a8229db9 | |||
8be44ccf5f | |||
511328a0bd |
@ -39,6 +39,17 @@ def gettext(string, **variables) -> str:
|
|||||||
return domain.gettext(string, **variables)
|
return domain.gettext(string, **variables)
|
||||||
|
|
||||||
|
|
||||||
|
def pgettext(context, string, **variables) -> str:
|
||||||
|
"""A replacement of the Babel gettext() function..
|
||||||
|
|
||||||
|
:param context: The context.
|
||||||
|
:param string: The message to translate.
|
||||||
|
:param variables: The variable substitution.
|
||||||
|
:return: The translated message.
|
||||||
|
"""
|
||||||
|
return domain.pgettext(context, string, **variables)
|
||||||
|
|
||||||
|
|
||||||
def lazy_gettext(string, **variables) -> LazyString:
|
def lazy_gettext(string, **variables) -> LazyString:
|
||||||
"""A replacement of the Babel lazy_gettext() function..
|
"""A replacement of the Babel lazy_gettext() function..
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ First written: 2023/1/30
|
|||||||
#}
|
#}
|
||||||
{% extends "accounting/base.html" %}
|
{% extends "accounting/base.html" %}
|
||||||
|
|
||||||
{% block header %}{% block title %}{{ A_("Account Management") }}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{% if "q" in request.args %}{{ A_("Search Result for \"%(query)s\"", query=request.args["q"]) }}{% else %}{{ A_("Account Management") }}{% endif %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ First written: 2023/1/26
|
|||||||
#}
|
#}
|
||||||
{% extends "accounting/base.html" %}
|
{% extends "accounting/base.html" %}
|
||||||
|
|
||||||
{% block header %}{% block title %}{{ A_("Base Account Managements") }}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{% if "q" in request.args %}{{ A_("Search Result for \"%(query)s\"", query=request.args["q"]) }}{% else %}{{ A_("Base Account Managements") }}{% endif %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ pagination.html: The pagination navigation bar.
|
|||||||
Author: imacat@mail.imacat.idv.tw (imacat)
|
Author: imacat@mail.imacat.idv.tw (imacat)
|
||||||
First written: 2023/1/26
|
First written: 2023/1/26
|
||||||
#}
|
#}
|
||||||
{% if pagination.is_needed %}
|
{% if pagination.is_paged %}
|
||||||
<nav aria-label="Page navigation">
|
<nav aria-label="Page navigation">
|
||||||
<ul class="pagination">
|
<ul class="pagination">
|
||||||
{% for link in pagination.page_links %}
|
{% for link in pagination.pages %}
|
||||||
{% if link.uri is none %}
|
{% if link.uri is none %}
|
||||||
<li class="page-item disabled {% if not link.is_for_mobile %} d-none d-md-inline {% endif %}">
|
<li class="page-item disabled {% if not link.is_for_mobile %} d-none d-md-inline {% endif %}">
|
||||||
<span class="page-link">
|
<span class="page-link">
|
||||||
@ -42,7 +42,7 @@ First written: 2023/1/26
|
|||||||
{{ pagination.page_size }}
|
{{ pagination.page_size }}
|
||||||
</div>
|
</div>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{% for link in pagination.page_sizes %}
|
{% for link in pagination.page_size_options %}
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item {% if link.is_current %} active {% endif %}" href="{{ link.uri }}">
|
<a class="dropdown-item {% if link.is_current %} active {% endif %}" href="{{ link.uri }}">
|
||||||
{{ link.text }}
|
{{ link.text }}
|
||||||
|
@ -8,8 +8,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Mia! Accounting Flask 0.0.0\n"
|
"Project-Id-Version: Mia! Accounting Flask 0.0.0\n"
|
||||||
"Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n"
|
"Report-Msgid-Bugs-To: imacat@mail.imacat.idv.tw\n"
|
||||||
"POT-Creation-Date: 2023-02-03 10:15+0800\n"
|
"POT-Creation-Date: 2023-02-06 09:47+0800\n"
|
||||||
"PO-Revision-Date: 2023-02-03 10:16+0800\n"
|
"PO-Revision-Date: 2023-02-06 09:48+0800\n"
|
||||||
"Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n"
|
"Last-Translator: imacat <imacat@mail.imacat.idv.tw>\n"
|
||||||
"Language: zh_Hant\n"
|
"Language: zh_Hant\n"
|
||||||
"Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n"
|
"Language-Team: zh_Hant <imacat@mail.imacat.idv.tw>\n"
|
||||||
@ -33,7 +33,7 @@ msgid "Please fill in the title"
|
|||||||
msgstr "請填上標題。"
|
msgstr "請填上標題。"
|
||||||
|
|
||||||
#: src/accounting/account/query.py:50
|
#: src/accounting/account/query.py:50
|
||||||
#: src/accounting/templates/accounting/account/detail.html:88
|
#: src/accounting/templates/accounting/account/detail.html:90
|
||||||
#: src/accounting/templates/accounting/account/list.html:62
|
#: src/accounting/templates/accounting/account/list.html:62
|
||||||
msgid "Offset needed"
|
msgid "Offset needed"
|
||||||
msgstr "逐筆核銷"
|
msgstr "逐筆核銷"
|
||||||
@ -81,36 +81,36 @@ msgstr "回上頁"
|
|||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "設定"
|
msgstr "設定"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:40
|
#: src/accounting/templates/accounting/account/detail.html:41
|
||||||
msgid "Order"
|
msgid "Order"
|
||||||
msgstr "次序"
|
msgstr "次序"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:44
|
#: src/accounting/templates/accounting/account/detail.html:46
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "刪除"
|
msgstr "刪除"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:67
|
#: src/accounting/templates/accounting/account/detail.html:69
|
||||||
msgid "Delete Account Confirmation"
|
msgid "Delete Account Confirmation"
|
||||||
msgstr "科目刪除確認"
|
msgstr "科目刪除確認"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:71
|
#: src/accounting/templates/accounting/account/detail.html:73
|
||||||
msgid "Do you really want to delete this account?"
|
msgid "Do you really want to delete this account?"
|
||||||
msgstr "你確定要刪掉這個科目嗎?"
|
msgstr "你確定要刪掉這個科目嗎?"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:74
|
#: src/accounting/templates/accounting/account/detail.html:76
|
||||||
#: src/accounting/templates/accounting/account/include/form.html:111
|
#: src/accounting/templates/accounting/account/include/form.html:111
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "取消"
|
msgstr "取消"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:75
|
#: src/accounting/templates/accounting/account/detail.html:77
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "確定"
|
msgstr "確定"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:92
|
#: src/accounting/templates/accounting/account/detail.html:94
|
||||||
msgid "Created"
|
msgid "Created"
|
||||||
msgstr "建檔"
|
msgstr "建檔"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/detail.html:93
|
#: src/accounting/templates/accounting/account/detail.html:95
|
||||||
msgid "Updated"
|
msgid "Updated"
|
||||||
msgstr "更新"
|
msgstr "更新"
|
||||||
|
|
||||||
@ -119,6 +119,12 @@ msgstr "更新"
|
|||||||
msgid "%(account)s Settings"
|
msgid "%(account)s Settings"
|
||||||
msgstr "%(account)s設定"
|
msgstr "%(account)s設定"
|
||||||
|
|
||||||
|
#: src/accounting/templates/accounting/account/list.html:24
|
||||||
|
#: src/accounting/templates/accounting/base-account/list.html:24
|
||||||
|
#, python-format
|
||||||
|
msgid "Search Result for \"%(query)s\""
|
||||||
|
msgstr "「%(query)s」搜尋結果"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/list.html:24
|
#: src/accounting/templates/accounting/account/list.html:24
|
||||||
msgid "Account Management"
|
msgid "Account Management"
|
||||||
msgstr "科目管理"
|
msgstr "科目管理"
|
||||||
@ -134,6 +140,7 @@ msgid "Search"
|
|||||||
msgstr "搜尋"
|
msgstr "搜尋"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/list.html:68
|
#: src/accounting/templates/accounting/account/list.html:68
|
||||||
|
#: src/accounting/templates/accounting/account/order.html:81
|
||||||
#: src/accounting/templates/accounting/base-account/list.html:51
|
#: src/accounting/templates/accounting/base-account/list.html:51
|
||||||
msgid "There is no data."
|
msgid "There is no data."
|
||||||
msgstr "沒有資料。"
|
msgstr "沒有資料。"
|
||||||
@ -144,7 +151,7 @@ msgid "The Accounts of %(base)s"
|
|||||||
msgstr "%(base)s下的科目"
|
msgstr "%(base)s下的科目"
|
||||||
|
|
||||||
#: src/accounting/templates/accounting/account/include/form.html:75
|
#: src/accounting/templates/accounting/account/include/form.html:75
|
||||||
#: src/accounting/templates/accounting/account/order.html:61
|
#: src/accounting/templates/accounting/account/order.html:62
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "儲存"
|
msgstr "儲存"
|
||||||
|
|
||||||
@ -189,11 +196,13 @@ msgstr "科目"
|
|||||||
msgid "Base Accounts"
|
msgid "Base Accounts"
|
||||||
msgstr "基本科目"
|
msgstr "基本科目"
|
||||||
|
|
||||||
#: src/accounting/utils/pagination.py:146
|
#: src/accounting/utils/pagination.py:206
|
||||||
|
msgctxt "Pagination|"
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
msgstr "前一頁"
|
msgstr "上一頁"
|
||||||
|
|
||||||
#: src/accounting/utils/pagination.py:194
|
#: src/accounting/utils/pagination.py:255
|
||||||
|
msgctxt "Pagination|"
|
||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "下一頁"
|
msgstr "下一頁"
|
||||||
|
|
||||||
|
@ -26,11 +26,11 @@ from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \
|
|||||||
from flask import request
|
from flask import request
|
||||||
from werkzeug.routing import RequestRedirect
|
from werkzeug.routing import RequestRedirect
|
||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext, pgettext
|
||||||
|
|
||||||
|
|
||||||
class PageLink:
|
class Link:
|
||||||
"""A link in the pagination."""
|
"""A link."""
|
||||||
|
|
||||||
def __init__(self, text: str, uri: str | None = None,
|
def __init__(self, text: str, uri: str | None = None,
|
||||||
is_current: bool = False, is_for_mobile: bool = False):
|
is_current: bool = False, is_for_mobile: bool = False):
|
||||||
@ -59,15 +59,14 @@ class Redirection(RequestRedirect):
|
|||||||
"""The HTTP code."""
|
"""The HTTP code."""
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PAGE_SIZE: int = 10
|
||||||
|
"""The default page size."""
|
||||||
|
|
||||||
T = t.TypeVar("T")
|
T = t.TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Pagination(t.Generic[T]):
|
class Pagination(t.Generic[T]):
|
||||||
"""The pagination utilities"""
|
"""The pagination utility."""
|
||||||
AVAILABLE_PAGE_SIZES: list[int] = [10, 100, 200]
|
|
||||||
"""The available page sizes."""
|
|
||||||
DEFAULT_PAGE_SIZE: int = 10
|
|
||||||
"""The default page size."""
|
|
||||||
|
|
||||||
def __init__(self, items: list[T], is_reversed: bool = False):
|
def __init__(self, items: list[T], is_reversed: bool = False):
|
||||||
"""Constructs the pagination.
|
"""Constructs the pagination.
|
||||||
@ -75,36 +74,80 @@ class Pagination(t.Generic[T]):
|
|||||||
:param items: The items.
|
:param items: The items.
|
||||||
:param is_reversed: True if the default page is the last page, or False
|
:param is_reversed: True if the default page is the last page, or False
|
||||||
otherwise.
|
otherwise.
|
||||||
|
:raise Redirection: When the pagination parameters are malformed.
|
||||||
"""
|
"""
|
||||||
|
pagination: AbstractPagination[T] = EmptyPagination[T]() \
|
||||||
|
if len(items) == 0 \
|
||||||
|
else NonEmptyPagination[T](items, is_reversed)
|
||||||
|
self.is_paged: bool = pagination.is_paged
|
||||||
|
"""Whether there should be pagination."""
|
||||||
|
self.list: list[T] = pagination.list
|
||||||
|
"""The items shown in the list"""
|
||||||
|
self.pages: list[Link] = pagination.pages
|
||||||
|
"""The pages."""
|
||||||
|
self.page_size: int = pagination.page_size
|
||||||
|
"""The number of items in a page."""
|
||||||
|
self.page_size_options: list[Link] = pagination.page_size_options
|
||||||
|
"""The options to the number of items in a page."""
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractPagination(t.Generic[T]):
|
||||||
|
"""An abstract pagination."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Constructs an empty pagination."""
|
||||||
|
self.page_size: int = DEFAULT_PAGE_SIZE
|
||||||
|
"""The number of items in a page."""
|
||||||
|
self.is_paged: bool = False
|
||||||
|
"""Whether there should be pagination."""
|
||||||
|
self.list: list[T] = []
|
||||||
|
"""The items shown in the list"""
|
||||||
|
self.pages: list[Link] = []
|
||||||
|
"""The pages."""
|
||||||
|
self.page_size_options: list[Link] = []
|
||||||
|
"""The options to the number of items in a page."""
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyPagination(AbstractPagination[T]):
|
||||||
|
"""The pagination from empty data."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NonEmptyPagination(AbstractPagination[T]):
|
||||||
|
"""The pagination with real data."""
|
||||||
|
PAGE_SIZE_OPTIONS: list[int] = [10, 100, 200]
|
||||||
|
"""The page size options."""
|
||||||
|
|
||||||
|
def __init__(self, items: list[T], is_reversed: bool = False):
|
||||||
|
"""Constructs the pagination.
|
||||||
|
|
||||||
|
:param items: The items.
|
||||||
|
:param is_reversed: True if the default page is the last page, or False
|
||||||
|
otherwise.
|
||||||
|
:raise Redirection: When the pagination parameters are malformed.
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
self.__current_uri: str = request.full_path if request.query_string \
|
self.__current_uri: str = request.full_path if request.query_string \
|
||||||
else request.path
|
else request.path
|
||||||
"""The current URI."""
|
"""The current URI."""
|
||||||
self.__items: list[T] = items
|
|
||||||
"""All the items."""
|
|
||||||
self.__is_reversed: bool = is_reversed
|
self.__is_reversed: bool = is_reversed
|
||||||
"""Whether the default page is the last page."""
|
"""Whether the default page is the last page."""
|
||||||
self.page_size: int = self.__get_page_size()
|
self.page_size = self.__get_page_size()
|
||||||
"""The number of items in a page."""
|
self.__total_pages: int = int((len(items) - 1) / self.page_size) + 1
|
||||||
self.__total_pages: int = 0 if len(items) == 0 \
|
|
||||||
else int((len(items) - 1) / self.page_size) + 1
|
|
||||||
"""The total number of pages."""
|
"""The total number of pages."""
|
||||||
self.is_needed: bool = self.__total_pages > 1
|
self.is_paged = self.__total_pages > 1
|
||||||
"""Whether there should be pagination."""
|
self.__default_page_no: int = self.__total_pages \
|
||||||
self.__default_page_no: int = 0
|
if self.__is_reversed else 1
|
||||||
"""The default page number."""
|
"""The default page number."""
|
||||||
self.page_no: int = 0
|
self.__page_no: int = self.__get_page_no()
|
||||||
"""The current page number."""
|
"""The current page number."""
|
||||||
self.list: list[T] = []
|
lower_bound: int = (self.__page_no - 1) * self.page_size
|
||||||
"""The items shown in the list"""
|
upper_bound: int = lower_bound + self.page_size
|
||||||
if self.__total_pages > 0:
|
if upper_bound > len(items):
|
||||||
self.__set_list()
|
upper_bound = len(items)
|
||||||
self.__base_uri_params: tuple[list[str], list[tuple[str, str]]] \
|
self.list = items[lower_bound:upper_bound]
|
||||||
= self.__get_base_uri_params()
|
self.pages = self.__get_pages()
|
||||||
"""The base URI parameters."""
|
self.page_size_options = self.__get_page_size_options()
|
||||||
self.page_links: list[PageLink] = self.__get_page_links()
|
|
||||||
"""The pagination links."""
|
|
||||||
self.page_sizes: list[PageLink] = self.__get_page_sizes()
|
|
||||||
"""The links to switch the number of items in a page."""
|
|
||||||
|
|
||||||
def __get_page_size(self) -> int:
|
def __get_page_size(self) -> int:
|
||||||
"""Returns the page size.
|
"""Returns the page size.
|
||||||
@ -113,29 +156,14 @@ class Pagination(t.Generic[T]):
|
|||||||
:raise Redirection: When the page size is malformed.
|
:raise Redirection: When the page size is malformed.
|
||||||
"""
|
"""
|
||||||
if "page-size" not in request.args:
|
if "page-size" not in request.args:
|
||||||
return self.DEFAULT_PAGE_SIZE
|
return DEFAULT_PAGE_SIZE
|
||||||
try:
|
try:
|
||||||
return int(request.args["page-size"])
|
page_size: int = int(request.args["page-size"])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Redirection(self.__uri_set("page-size", None))
|
raise Redirection(self.__uri_set("page-size", None))
|
||||||
|
if page_size == DEFAULT_PAGE_SIZE or page_size < 1:
|
||||||
def __set_list(self) -> None:
|
raise Redirection(self.__uri_set("page-size", None))
|
||||||
"""Sets the items to show in the list.
|
return page_size
|
||||||
|
|
||||||
:return: None.
|
|
||||||
"""
|
|
||||||
self.__default_page_no = self.__total_pages if self.__is_reversed \
|
|
||||||
else 1
|
|
||||||
self.page_no = self.__get_page_no()
|
|
||||||
if self.page_no < 1:
|
|
||||||
self.page_no = 1
|
|
||||||
if self.page_no > self.__total_pages:
|
|
||||||
self.page_no = self.__total_pages
|
|
||||||
lower_bound: int = (self.page_no - 1) * self.page_size
|
|
||||||
upper_bound: int = lower_bound + self.page_size
|
|
||||||
if upper_bound > len(self.__items):
|
|
||||||
upper_bound = len(self.__items)
|
|
||||||
self.list = self.__items[lower_bound:upper_bound]
|
|
||||||
|
|
||||||
def __get_page_no(self) -> int:
|
def __get_page_no(self) -> int:
|
||||||
"""Returns the page number.
|
"""Returns the page number.
|
||||||
@ -149,6 +177,8 @@ class Pagination(t.Generic[T]):
|
|||||||
page_no: int = int(request.args["page-no"])
|
page_no: int = int(request.args["page-no"])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise Redirection(self.__uri_set("page-no", None))
|
raise Redirection(self.__uri_set("page-no", None))
|
||||||
|
if page_no == self.__default_page_no:
|
||||||
|
raise Redirection(self.__uri_set("page-no", None))
|
||||||
if page_no < 1:
|
if page_no < 1:
|
||||||
if not self.__is_reversed:
|
if not self.__is_reversed:
|
||||||
raise Redirection(self.__uri_set("page-no", None))
|
raise Redirection(self.__uri_set("page-no", None))
|
||||||
@ -160,6 +190,108 @@ class Pagination(t.Generic[T]):
|
|||||||
str(self.__total_pages)))
|
str(self.__total_pages)))
|
||||||
return page_no
|
return page_no
|
||||||
|
|
||||||
|
def __get_pages(self) -> list[Link]:
|
||||||
|
"""Returns the page links in the pagination navigation.
|
||||||
|
|
||||||
|
:return: The page links in the pagination navigation.
|
||||||
|
"""
|
||||||
|
if not self.is_paged:
|
||||||
|
return []
|
||||||
|
uri: str | None
|
||||||
|
links: list[Link] = []
|
||||||
|
|
||||||
|
# The previous page.
|
||||||
|
uri = None if self.__page_no == 1 \
|
||||||
|
else self.__uri_page(self.__page_no - 1)
|
||||||
|
links.append(Link(pgettext("Pagination|", "Previous"), uri,
|
||||||
|
is_for_mobile=True))
|
||||||
|
|
||||||
|
# The first page.
|
||||||
|
if self.__page_no > 1:
|
||||||
|
links.append(Link("1", self.__uri_page(1)))
|
||||||
|
|
||||||
|
# The eclipse of the previous pages.
|
||||||
|
if self.__page_no - 3 == 2:
|
||||||
|
links.append(Link(str(self.__page_no - 3),
|
||||||
|
self.__uri_page(self.__page_no - 3)))
|
||||||
|
elif self.__page_no - 3 > 2:
|
||||||
|
links.append(Link("…"))
|
||||||
|
|
||||||
|
# The previous two pages.
|
||||||
|
if self.__page_no - 2 > 1:
|
||||||
|
links.append(Link(str(self.__page_no - 2),
|
||||||
|
self.__uri_page(self.__page_no - 2)))
|
||||||
|
if self.__page_no - 1 > 1:
|
||||||
|
links.append(Link(str(self.__page_no - 1),
|
||||||
|
self.__uri_page(self.__page_no - 1)))
|
||||||
|
|
||||||
|
# The current page.
|
||||||
|
links.append(Link(str(self.__page_no), self.__uri_page(self.__page_no),
|
||||||
|
is_current=True))
|
||||||
|
|
||||||
|
# The next two pages.
|
||||||
|
if self.__page_no + 1 < self.__total_pages:
|
||||||
|
links.append(Link(str(self.__page_no + 1),
|
||||||
|
self.__uri_page(self.__page_no + 1)))
|
||||||
|
if self.__page_no + 2 < self.__total_pages:
|
||||||
|
links.append(Link(str(self.__page_no + 2),
|
||||||
|
self.__uri_page(self.__page_no + 2)))
|
||||||
|
|
||||||
|
# The eclipse of the next pages.
|
||||||
|
if self.__page_no + 3 == self.__total_pages - 1:
|
||||||
|
links.append(Link(str(self.__page_no + 3),
|
||||||
|
self.__uri_page(self.__page_no + 3)))
|
||||||
|
elif self.__page_no + 3 < self.__total_pages - 1:
|
||||||
|
links.append(Link("…"))
|
||||||
|
|
||||||
|
# The last page.
|
||||||
|
if self.__page_no < self.__total_pages:
|
||||||
|
links.append(Link(str(self.__total_pages),
|
||||||
|
self.__uri_page(self.__total_pages)))
|
||||||
|
|
||||||
|
# The next page.
|
||||||
|
uri = None if self.__page_no == self.__total_pages \
|
||||||
|
else self.__uri_page(self.__page_no + 1)
|
||||||
|
links.append(Link(pgettext("Pagination|", "Next"), uri,
|
||||||
|
is_for_mobile=True))
|
||||||
|
|
||||||
|
return links
|
||||||
|
|
||||||
|
def __uri_page(self, page_no: int) -> str:
|
||||||
|
"""Returns the URI of a page.
|
||||||
|
|
||||||
|
:param page_no: The page number.
|
||||||
|
:return: The URI of the page.
|
||||||
|
"""
|
||||||
|
if page_no == self.__page_no:
|
||||||
|
return self.__current_uri
|
||||||
|
if page_no == self.__default_page_no:
|
||||||
|
return self.__uri_set("page-no", None)
|
||||||
|
return self.__uri_set("page-no", str(page_no))
|
||||||
|
|
||||||
|
def __get_page_size_options(self) -> list[Link]:
|
||||||
|
"""Returns the page size options.
|
||||||
|
|
||||||
|
:return: The page size options.
|
||||||
|
"""
|
||||||
|
if not self.is_paged:
|
||||||
|
return []
|
||||||
|
return [Link(str(x), self.__uri_size(x),
|
||||||
|
is_current=x == self.page_size)
|
||||||
|
for x in self.PAGE_SIZE_OPTIONS]
|
||||||
|
|
||||||
|
def __uri_size(self, page_size: int) -> str:
|
||||||
|
"""Returns the URI of a page size.
|
||||||
|
|
||||||
|
:param page_size: The page size.
|
||||||
|
:return: The URI of the page size.
|
||||||
|
"""
|
||||||
|
if page_size == self.page_size:
|
||||||
|
return self.__current_uri
|
||||||
|
if page_size == DEFAULT_PAGE_SIZE:
|
||||||
|
return self.__uri_set("page-size", None)
|
||||||
|
return self.__uri_set("page-size", str(page_size))
|
||||||
|
|
||||||
def __uri_set(self, name: str, value: str | None) -> str:
|
def __uri_set(self, name: str, value: str | None) -> str:
|
||||||
"""Raises current URI with a parameter set.
|
"""Raises current URI with a parameter set.
|
||||||
|
|
||||||
@ -179,128 +311,11 @@ class Pagination(t.Generic[T]):
|
|||||||
params = params[:i] + params[i + 1:]
|
params = params[:i] + params[i + 1:]
|
||||||
continue
|
continue
|
||||||
params[i] = (name, value)
|
params[i] = (name, value)
|
||||||
|
is_found = True
|
||||||
i = i + 1
|
i = i + 1
|
||||||
|
if not is_found and value is not None:
|
||||||
|
params.append((name, value))
|
||||||
|
|
||||||
parts: list[str] = list(uri_p)
|
parts: list[str] = list(uri_p)
|
||||||
parts[4] = urlencode(params)
|
parts[4] = urlencode(params)
|
||||||
return urlunparse(parts)
|
return urlunparse(parts)
|
||||||
|
|
||||||
def __get_base_uri_params(self) -> tuple[list[str], list[tuple[str, str]]]:
|
|
||||||
"""Returns the base URI and its parameters, with the "page-no" and
|
|
||||||
"page-size" parameters removed.
|
|
||||||
|
|
||||||
:return: The URI parts and the cleaned-up query parameters.
|
|
||||||
"""
|
|
||||||
uri_p: ParseResult = urlparse(self.__current_uri)
|
|
||||||
params: list[tuple[str, str]] = parse_qsl(uri_p.query)
|
|
||||||
params = [x for x in params if x[0] not in ["page-no", "page-size"]]
|
|
||||||
parts: list[str] = list(uri_p)
|
|
||||||
return parts, params
|
|
||||||
|
|
||||||
def __get_page_links(self) -> list[PageLink]:
|
|
||||||
"""Returns the page links in the pagination navigation.
|
|
||||||
|
|
||||||
:return: The page links in the pagination navigation.
|
|
||||||
"""
|
|
||||||
if self.__total_pages < 2:
|
|
||||||
return []
|
|
||||||
uri: str | None
|
|
||||||
links: list[PageLink] = []
|
|
||||||
|
|
||||||
# The previous page.
|
|
||||||
uri = None if self.page_no == 1 else self.__uri_page(self.page_no - 1)
|
|
||||||
links.append(PageLink(gettext("Previous"), uri, is_for_mobile=True))
|
|
||||||
|
|
||||||
# The first page.
|
|
||||||
if self.page_no > 1:
|
|
||||||
links.append(PageLink("1", self.__uri_page(1)))
|
|
||||||
|
|
||||||
# The eclipse of the previous pages.
|
|
||||||
if self.page_no - 3 == 2:
|
|
||||||
links.append(PageLink(str(self.page_no - 3),
|
|
||||||
self.__uri_page(self.page_no - 3)))
|
|
||||||
elif self.page_no - 3 > 2:
|
|
||||||
links.append(PageLink("…"))
|
|
||||||
|
|
||||||
# The previous two pages.
|
|
||||||
if self.page_no - 2 > 1:
|
|
||||||
links.append(PageLink(str(self.page_no - 2),
|
|
||||||
self.__uri_page(self.page_no - 2)))
|
|
||||||
if self.page_no - 1 > 1:
|
|
||||||
links.append(PageLink(str(self.page_no - 1),
|
|
||||||
self.__uri_page(self.page_no - 1)))
|
|
||||||
|
|
||||||
# The current page.
|
|
||||||
links.append(PageLink(str(self.page_no), self.__uri_page(self.page_no),
|
|
||||||
is_current=True))
|
|
||||||
|
|
||||||
# The next two pages.
|
|
||||||
if self.page_no + 1 < self.__total_pages:
|
|
||||||
links.append(PageLink(str(self.page_no + 1),
|
|
||||||
self.__uri_page(self.page_no + 1)))
|
|
||||||
if self.page_no + 2 < self.__total_pages:
|
|
||||||
links.append(PageLink(str(self.page_no + 2),
|
|
||||||
self.__uri_page(self.page_no + 2)))
|
|
||||||
|
|
||||||
# The eclipse of the next pages.
|
|
||||||
if self.page_no + 3 == self.__total_pages - 1:
|
|
||||||
links.append(PageLink(str(self.page_no + 3),
|
|
||||||
self.__uri_page(self.page_no + 3)))
|
|
||||||
elif self.page_no + 3 < self.__total_pages - 1:
|
|
||||||
links.append(PageLink("…"))
|
|
||||||
|
|
||||||
# The last page.
|
|
||||||
if self.page_no < self.__total_pages:
|
|
||||||
links.append(PageLink(str(self.__total_pages),
|
|
||||||
self.__uri_page(self.__total_pages)))
|
|
||||||
|
|
||||||
# The next page.
|
|
||||||
uri = None if self.page_no == self.__total_pages \
|
|
||||||
else self.__uri_page(self.page_no + 1)
|
|
||||||
links.append(PageLink(gettext("Next"), uri, is_for_mobile=True))
|
|
||||||
|
|
||||||
return links
|
|
||||||
|
|
||||||
def __uri_page(self, page_no: int) -> str:
|
|
||||||
"""Returns the URI of a page.
|
|
||||||
|
|
||||||
:param page_no: The page number.
|
|
||||||
:return: The URI of the page.
|
|
||||||
"""
|
|
||||||
params: list[tuple[str, str]] = []
|
|
||||||
if page_no != self.__default_page_no:
|
|
||||||
params.append(("page-no", str(page_no)))
|
|
||||||
if self.page_size != self.DEFAULT_PAGE_SIZE:
|
|
||||||
params.append(("page-size", str(self.page_size)))
|
|
||||||
return self.__uri_set_params(params)
|
|
||||||
|
|
||||||
def __get_page_sizes(self) -> list[PageLink]:
|
|
||||||
"""Returns the available page sizes.
|
|
||||||
|
|
||||||
:return: The available page sizes.
|
|
||||||
"""
|
|
||||||
return [PageLink(str(x), self.__uri_size(x),
|
|
||||||
is_current=x == self.page_size)
|
|
||||||
for x in self.AVAILABLE_PAGE_SIZES]
|
|
||||||
|
|
||||||
def __uri_size(self, page_size: int) -> str:
|
|
||||||
"""Returns the URI of a page size.
|
|
||||||
|
|
||||||
:param page_size: The page size.
|
|
||||||
:return: The URI of the page size.
|
|
||||||
"""
|
|
||||||
if page_size == self.page_size:
|
|
||||||
return self.__current_uri
|
|
||||||
return self.__uri_set_params([("page-size", str(page_size))])
|
|
||||||
|
|
||||||
def __uri_set_params(self, params: list[tuple[str, str]]) -> str:
|
|
||||||
"""Returns the URI with the query parameters set.
|
|
||||||
|
|
||||||
:param params: The query parameters.
|
|
||||||
:return: The URI with the query parameters set.
|
|
||||||
"""
|
|
||||||
cur_params: list[tuple[str, str]] = self.__base_uri_params[1].copy()
|
|
||||||
cur_params.extend(params)
|
|
||||||
parts: list[str] = self.__base_uri_params[0].copy()
|
|
||||||
parts[4] = urlencode(cur_params)
|
|
||||||
return urlunparse(parts)
|
|
||||||
|
@ -34,11 +34,22 @@ def parse_query_keywords(q: str | None) -> list[str]:
|
|||||||
if q == "":
|
if q == "":
|
||||||
return []
|
return []
|
||||||
keywords: list[str] = []
|
keywords: list[str] = []
|
||||||
while q is not None:
|
while True:
|
||||||
m: re.Match = re.match(r"(?:\"([^\"]+)\"|(\S+))(?:\s+(.+)|)$", q)
|
m: re.Match
|
||||||
if m.group(1) is not None:
|
m = re.match(r"\"([^\"]+)\"\s+(.+)$", q)
|
||||||
|
if m is not None:
|
||||||
keywords.append(m.group(1))
|
keywords.append(m.group(1))
|
||||||
else:
|
q = m.group(2)
|
||||||
keywords.append(m.group(2))
|
continue
|
||||||
q = m.group(3)
|
m = re.match(r"\"([^\"]+)\"?$", q)
|
||||||
|
if m is not None:
|
||||||
|
keywords.append(m.group(1))
|
||||||
|
break
|
||||||
|
m = re.match(r"(\S+)\s+(.+)$", q)
|
||||||
|
if m is not None:
|
||||||
|
keywords.append(m.group(1))
|
||||||
|
q = m.group(2)
|
||||||
|
continue
|
||||||
|
keywords.append(q)
|
||||||
|
break
|
||||||
return keywords
|
return keywords
|
||||||
|
@ -24,10 +24,9 @@ import httpx
|
|||||||
from flask import Flask, request
|
from flask import Flask, request
|
||||||
|
|
||||||
from accounting.utils.next_url import append_next, inherit_next, or_next
|
from accounting.utils.next_url import append_next, inherit_next, or_next
|
||||||
from accounting.utils.pagination import Pagination
|
from accounting.utils.pagination import Pagination, DEFAULT_PAGE_SIZE
|
||||||
from accounting.utils.query import parse_query_keywords
|
from accounting.utils.query import parse_query_keywords
|
||||||
from test_site import create_app
|
from test_site import create_app, csrf
|
||||||
from testlib import get_csrf_token
|
|
||||||
|
|
||||||
|
|
||||||
class NextUriTestCase(unittest.TestCase):
|
class NextUriTestCase(unittest.TestCase):
|
||||||
@ -42,6 +41,7 @@ class NextUriTestCase(unittest.TestCase):
|
|||||||
target: str = "/target"
|
target: str = "/target"
|
||||||
|
|
||||||
@app.route("/test-next", methods=["GET", "POST"])
|
@app.route("/test-next", methods=["GET", "POST"])
|
||||||
|
@csrf.exempt
|
||||||
def test_next_view() -> str:
|
def test_next_view() -> str:
|
||||||
"""The test view with the next URI."""
|
"""The test view with the next URI."""
|
||||||
current_uri: str = request.full_path if request.query_string \
|
current_uri: str = request.full_path if request.query_string \
|
||||||
@ -56,6 +56,7 @@ class NextUriTestCase(unittest.TestCase):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@app.route("/test-no-next", methods=["GET", "POST"])
|
@app.route("/test-no-next", methods=["GET", "POST"])
|
||||||
|
@csrf.exempt
|
||||||
def test_no_next_view() -> str:
|
def test_no_next_view() -> str:
|
||||||
"""The test view without the next URI."""
|
"""The test view without the next URI."""
|
||||||
current_uri: str = request.full_path if request.query_string \
|
current_uri: str = request.full_path if request.query_string \
|
||||||
@ -69,22 +70,19 @@ class NextUriTestCase(unittest.TestCase):
|
|||||||
client: httpx.Client = httpx.Client(app=app,
|
client: httpx.Client = httpx.Client(app=app,
|
||||||
base_url="https://testserver")
|
base_url="https://testserver")
|
||||||
client.headers["Referer"] = "https://testserver"
|
client.headers["Referer"] = "https://testserver"
|
||||||
csrf_token: str = get_csrf_token(self, client, "/login")
|
|
||||||
response: httpx.Response
|
response: httpx.Response
|
||||||
|
|
||||||
# With the next URI
|
# With the next URI
|
||||||
response = client.get("/test-next?next=/next&q=abc&page-no=4")
|
response = client.get("/test-next?next=/next&q=abc&page-no=4")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
response = client.post("/test-next", data={"csrf_token": csrf_token,
|
response = client.post("/test-next", data={"next": "/next",
|
||||||
"next": "/next",
|
|
||||||
"name": "viewer"})
|
"name": "viewer"})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Without the next URI
|
# Without the next URI
|
||||||
response = client.get("/test-no-next?q=abc&page-no=4")
|
response = client.get("/test-no-next?q=abc&page-no=4")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
response = client.post("/test-no-next", data={"csrf_token": csrf_token,
|
response = client.post("/test-no-next", data={"name": "viewer"})
|
||||||
"name": "viewer"})
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
@ -100,16 +98,21 @@ class QueryKeywordParserTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(parse_query_keywords("coffee tea"), ["coffee", "tea"])
|
self.assertEqual(parse_query_keywords("coffee tea"), ["coffee", "tea"])
|
||||||
self.assertEqual(parse_query_keywords("\"coffee\" \"tea cake\""),
|
self.assertEqual(parse_query_keywords("\"coffee\" \"tea cake\""),
|
||||||
["coffee", "tea cake"])
|
["coffee", "tea cake"])
|
||||||
|
self.assertEqual(parse_query_keywords("\"coffee tea\" cheese "
|
||||||
|
"\"cake candy\" sugar"),
|
||||||
|
["coffee tea", "cheese", "cake candy", "sugar"])
|
||||||
|
|
||||||
def test_malformed(self) -> None:
|
def test_malformed(self) -> None:
|
||||||
"""Tests the malformed query.
|
"""Tests the malformed query.
|
||||||
|
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
|
self.assertEqual(parse_query_keywords("coffee \"tea cake"),
|
||||||
|
["coffee", "tea cake"])
|
||||||
self.assertEqual(parse_query_keywords("coffee te\"a ca\"ke"),
|
self.assertEqual(parse_query_keywords("coffee te\"a ca\"ke"),
|
||||||
["coffee", "te\"a", "ca\"ke"])
|
["coffee", "te\"a", "ca\"ke"])
|
||||||
self.assertEqual(parse_query_keywords("coffee \"tea cake"),
|
self.assertEqual(parse_query_keywords("coffee\" tea cake\""),
|
||||||
["coffee", "\"tea", "cake"])
|
["coffee\"", "tea", "cake\""])
|
||||||
|
|
||||||
def test_empty(self) -> None:
|
def test_empty(self) -> None:
|
||||||
"""Tests the empty query.
|
"""Tests the empty query.
|
||||||
@ -127,18 +130,18 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
"""The testing parameters."""
|
"""The testing parameters."""
|
||||||
|
|
||||||
def __init__(self, items: list[int], is_reversed: bool | None,
|
def __init__(self, items: list[int], is_reversed: bool | None,
|
||||||
result: list[int], is_needed: bool):
|
result: list[int], is_paged: bool):
|
||||||
"""Constructs the expected pagination.
|
"""Constructs the expected pagination.
|
||||||
|
|
||||||
:param items: All the items in the list.
|
:param items: All the items in the list.
|
||||||
:param is_reversed: Whether the default page is the last page.
|
:param is_reversed: Whether the default page is the last page.
|
||||||
:param result: The expected items on the page.
|
:param result: The expected items on the page.
|
||||||
:param is_needed: Whether the pagination is needed.
|
:param is_paged: Whether the pagination is needed.
|
||||||
"""
|
"""
|
||||||
self.items: list[int] = items
|
self.items: list[int] = items
|
||||||
self.is_reversed: bool | None = is_reversed
|
self.is_reversed: bool | None = is_reversed
|
||||||
self.result: list[int] = result
|
self.result: list[int] = result
|
||||||
self.is_needed: bool = is_needed
|
self.is_paged: bool = is_paged
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
"""Sets up the test.
|
"""Sets up the test.
|
||||||
@ -154,11 +157,11 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
"""The test view with the pagination."""
|
"""The test view with the pagination."""
|
||||||
pagination: Pagination
|
pagination: Pagination
|
||||||
if self.params.is_reversed is not None:
|
if self.params.is_reversed is not None:
|
||||||
pagination = Pagination(self.params.items,
|
pagination = Pagination[int](
|
||||||
is_reversed=self.params.is_reversed)
|
self.params.items, is_reversed=self.params.is_reversed)
|
||||||
else:
|
else:
|
||||||
pagination = Pagination(self.params.items)
|
pagination = Pagination[int](self.params.items)
|
||||||
self.assertEqual(pagination.is_needed, self.params.is_needed)
|
self.assertEqual(pagination.is_paged, self.params.is_paged)
|
||||||
self.assertEqual(pagination.list, self.params.result)
|
self.assertEqual(pagination.list, self.params.result)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -166,14 +169,14 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
self.client.headers["Referer"] = "https://testserver"
|
self.client.headers["Referer"] = "https://testserver"
|
||||||
|
|
||||||
def __test_success(self, query: str, items: range,
|
def __test_success(self, query: str, items: range,
|
||||||
result: range, is_needed: bool = True,
|
result: range, is_paged: bool = True,
|
||||||
is_reversed: bool | None = None) -> None:
|
is_reversed: bool | None = None) -> None:
|
||||||
"""Tests the pagination.
|
"""Tests the pagination.
|
||||||
|
|
||||||
:param query: The query string.
|
:param query: The query string.
|
||||||
:param items: The original items.
|
:param items: The original items.
|
||||||
:param result: The expected page content.
|
:param result: The expected page content.
|
||||||
:param is_needed: Whether the pagination is needed.
|
:param is_paged: Whether the pagination is needed.
|
||||||
:param is_reversed: Whether the list is reversed.
|
:param is_reversed: Whether the list is reversed.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
@ -181,7 +184,7 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
if query != "":
|
if query != "":
|
||||||
target = f"{target}?{query}"
|
target = f"{target}?{query}"
|
||||||
self.params = self.Params(list(items), is_reversed,
|
self.params = self.Params(list(items), is_reversed,
|
||||||
list(result), is_needed)
|
list(result), is_paged)
|
||||||
response: httpx.Response = self.client.get(target)
|
response: httpx.Response = self.client.get(target)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
@ -234,12 +237,12 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
# Empty list
|
# Empty list
|
||||||
self.__test_success("", range(0, 0), range(0, 0), is_needed=False)
|
self.__test_success("", range(0, 0), range(0, 0), is_paged=False)
|
||||||
# A list that fits in one page
|
# A list that fits in one page
|
||||||
self.__test_success("", range(1, 4), range(1, 4), is_needed=False)
|
self.__test_success("", range(1, 4), range(1, 4), is_paged=False)
|
||||||
# A large page size that fits in everything
|
# A large page size that fits in everything
|
||||||
self.__test_success("page-size=1000", range(1, 687), range(1, 687),
|
self.__test_success("page-size=1000", range(1, 687), range(1, 687),
|
||||||
is_needed=False)
|
is_paged=False)
|
||||||
|
|
||||||
def test_reversed(self) -> None:
|
def test_reversed(self) -> None:
|
||||||
"""Tests the default page on a reversed list.
|
"""Tests the default page on a reversed list.
|
||||||
@ -271,9 +274,23 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
# A malformed page size
|
# A malformed page size
|
||||||
self.__test_malformed("q=word&page-size=100a&page-no=37&next=%2F",
|
self.__test_malformed("q=word&page-size=100a&page-no=37&next=%2F",
|
||||||
range(1, 691), "q=word&page-no=37&next=%2F")
|
range(1, 691), "q=word&page-no=37&next=%2F")
|
||||||
|
# A default page size
|
||||||
|
self.__test_malformed(f"q=word&page-size={DEFAULT_PAGE_SIZE}"
|
||||||
|
"&page-no=37&next=%2F",
|
||||||
|
range(1, 691), "q=word&page-no=37&next=%2F")
|
||||||
|
# An invalid page size
|
||||||
|
self.__test_malformed("q=word&page-size=0&page-no=37&next=%2F",
|
||||||
|
range(1, 691), "q=word&page-no=37&next=%2F")
|
||||||
# A malformed page number
|
# A malformed page number
|
||||||
self.__test_malformed("q=word&page-size=15&page-no=37a&next=%2F",
|
self.__test_malformed("q=word&page-size=15&page-no=37a&next=%2F",
|
||||||
range(1, 691), "q=word&page-size=15&next=%2F")
|
range(1, 691), "q=word&page-size=15&next=%2F")
|
||||||
|
# A default page number
|
||||||
|
self.__test_malformed("q=word&page-size=15&page-no=1&next=%2F",
|
||||||
|
range(1, 691), "q=word&page-size=15&next=%2F")
|
||||||
|
# A default page number, on a reversed list
|
||||||
|
self.__test_malformed("q=word&page-size=15&page-no=46&next=%2F",
|
||||||
|
range(1, 691), "q=word&page-size=15&next=%2F",
|
||||||
|
is_reversed=True)
|
||||||
# A page number beyond the last page
|
# A page number beyond the last page
|
||||||
self.__test_malformed("q=word&page-size=15&page-no=100&next=%2F",
|
self.__test_malformed("q=word&page-size=15&page-no=100&next=%2F",
|
||||||
range(1, 691),
|
range(1, 691),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user