Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
aea9dcae79 | |||
40278eaf06 | |||
e00c14f277 | |||
f20c462685 | |||
80ae4bd91c | |||
6ee3ee76ea | |||
2bfcc8b889 | |||
99564c02d0 | |||
25d9904180 | |||
1cf83adf87 | |||
8e3d1f11b5 | |||
0ab14aa34d | |||
e0ed81ad1f | |||
ece7481e9e | |||
50d4526e0b | |||
3f0a0b4227 | |||
dcc9626b23 | |||
79eb077129 | |||
d5719ad223 | |||
eb3fa8f414 | |||
937908717b | |||
0104fa4c21 |
@ -38,3 +38,4 @@ python:
|
|||||||
install:
|
install:
|
||||||
- method: pip
|
- method: pip
|
||||||
path: .
|
path: .
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
|
@ -59,7 +59,7 @@ Refer to the `change log`_.
|
|||||||
Copyright
|
Copyright
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Copyright (c) 2023 imacat.
|
Copyright (c) 2023-2024 imacat.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
1
docs/requirements.txt
Normal file
1
docs/requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
sphinx_rtd_theme
|
@ -2,6 +2,71 @@ Change Log
|
|||||||
==========
|
==========
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.6.1
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released 2024/12/3
|
||||||
|
|
||||||
|
Fix test cases for compatibility with httpx 0.28.0.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.6.0
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released 2024/6/4
|
||||||
|
|
||||||
|
* Updated Python version to 3.12.
|
||||||
|
* Revised the calculation of "today" to use the client's timezone instead of
|
||||||
|
the server's timezone.
|
||||||
|
* Updated the Bootstrap, FontAwesome, and Tempus-Dominus versions in the test
|
||||||
|
site.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.5.11
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released 2023/12/26
|
||||||
|
|
||||||
|
Bug fix.
|
||||||
|
|
||||||
|
* Refined to enable the selection of the 3351-001 Accumulated Profit or Loss
|
||||||
|
account.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.5.10
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released 2023/11/28
|
||||||
|
|
||||||
|
Bug fix.
|
||||||
|
|
||||||
|
* Fixed the form validator to enable the selection of Accumulated Profit or
|
||||||
|
Loss accounts other than 3351-001.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.5.9
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2023/11/28
|
||||||
|
|
||||||
|
Bug fix.
|
||||||
|
|
||||||
|
* Refined to enable the selection of Accumulated Profit or Loss accounts other
|
||||||
|
than 3351-001, facilitating the consolidation of existing balances.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.5.8
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Released 2023/10/24
|
||||||
|
|
||||||
|
Bug fix.
|
||||||
|
|
||||||
|
* Fixed an icon in the detail of the cash receipt journal entry.
|
||||||
|
|
||||||
|
Released at Jaipur, India on vacation.
|
||||||
|
|
||||||
|
|
||||||
Version 1.5.7
|
Version 1.5.7
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2022/8/21
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 imacat.
|
# Copyright (c) 2022-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -20,7 +20,7 @@ name = "mia-accounting"
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
description = "A Flask accounting module."
|
description = "A Flask accounting module."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.12"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "imacat", email = "imacat@mail.imacat.idv.tw"},
|
{name = "imacat", email = "imacat@mail.imacat.idv.tw"},
|
||||||
]
|
]
|
||||||
@ -33,7 +33,7 @@ classifiers = [
|
|||||||
"Topic :: Office/Business :: Financial :: Accounting",
|
"Topic :: Office/Business :: Financial :: Accounting",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flask",
|
"Flask",
|
||||||
"SQLAlchemy >= 2",
|
"SQLAlchemy >= 2",
|
||||||
"Flask-SQLAlchemy",
|
"Flask-SQLAlchemy",
|
||||||
"Flask-WTF",
|
"Flask-WTF",
|
||||||
@ -42,9 +42,8 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
test = [
|
devel = [
|
||||||
"unittest",
|
"httpx >= 0.20.0",
|
||||||
"httpx",
|
|
||||||
"OpenCC",
|
"OpenCC",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -24,7 +24,7 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
|
|
||||||
from accounting.utils.user import UserUtilityInterface
|
from accounting.utils.user import UserUtilityInterface
|
||||||
|
|
||||||
VERSION: str = "1.5.7"
|
VERSION: str = "1.6.1"
|
||||||
"""The package version."""
|
"""The package version."""
|
||||||
db: SQLAlchemy = SQLAlchemy()
|
db: SQLAlchemy = SQLAlchemy()
|
||||||
"""The database instance."""
|
"""The database instance."""
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/30
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -27,7 +27,7 @@ from accounting import db
|
|||||||
from accounting.models import BaseAccount, Account, AccountL10n
|
from accounting.models import BaseAccount, Account, AccountL10n
|
||||||
from accounting.utils.user import get_user_pk
|
from accounting.utils.user import get_user_pk
|
||||||
|
|
||||||
AccountData = tuple[int, str, int, str, str, str, bool]
|
type AccountData = tuple[int, str, int, str, str, str, bool]
|
||||||
"""The format of the account data, as a list of (ID, base account code, number,
|
"""The format of the account data, as a list of (ID, base account code, number,
|
||||||
English, Traditional Chinese, Simplified Chinese, is-need-offset) tuples."""
|
English, Traditional Chinese, Simplified Chinese, is-need-offset) tuples."""
|
||||||
|
|
||||||
|
@ -71,7 +71,6 @@ class IsDebitAccount:
|
|||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
if re.match(r"^(?:[1235689]|7[5678])", field.data) \
|
if re.match(r"^(?:[1235689]|7[5678])", field.data) \
|
||||||
and not field.data.startswith("3351-") \
|
|
||||||
and not field.data.startswith("3353-"):
|
and not field.data.startswith("3353-"):
|
||||||
return
|
return
|
||||||
raise ValidationError(self.__message)
|
raise ValidationError(self.__message)
|
||||||
@ -92,7 +91,6 @@ class IsCreditAccount:
|
|||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
if re.match(r"^(?:[123489]|7[1234])", field.data) \
|
if re.match(r"^(?:[123489]|7[1234])", field.data) \
|
||||||
and not field.data.startswith("3351-") \
|
|
||||||
and not field.data.startswith("3353-"):
|
and not field.data.startswith("3353-"):
|
||||||
return
|
return
|
||||||
raise ValidationError(self.__message)
|
raise ValidationError(self.__message)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"""
|
"""
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import TypeVar, Generic, Type
|
from typing import Type
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask_babel import LazyString
|
from flask_babel import LazyString
|
||||||
@ -308,11 +308,7 @@ class JournalEntryForm(FlaskForm):
|
|||||||
return db.session.scalar(select)
|
return db.session.scalar(select)
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=JournalEntryForm)
|
class LineItemCollector[T: JournalEntryForm](ABC):
|
||||||
"""A journal entry form variant."""
|
|
||||||
|
|
||||||
|
|
||||||
class LineItemCollector(Generic[T], ABC):
|
|
||||||
"""The line item collector."""
|
"""The line item collector."""
|
||||||
|
|
||||||
def __init__(self, form: T, obj: JournalEntry):
|
def __init__(self, form: T, obj: JournalEntry):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -33,6 +33,7 @@ from accounting.utils.flash_errors import flash_form_errors
|
|||||||
from accounting.utils.journal_entry_types import JournalEntryType
|
from accounting.utils.journal_entry_types import JournalEntryType
|
||||||
from accounting.utils.next_uri import inherit_next, or_next
|
from accounting.utils.next_uri import inherit_next, or_next
|
||||||
from accounting.utils.permission import has_permission, can_view, can_edit
|
from accounting.utils.permission import has_permission, can_view, can_edit
|
||||||
|
from accounting.utils.timezone import get_tz_today
|
||||||
from accounting.utils.user import get_current_user_pk
|
from accounting.utils.user import get_current_user_pk
|
||||||
from .forms import sort_journal_entries_in, JournalEntryReorderForm
|
from .forms import sort_journal_entries_in, JournalEntryReorderForm
|
||||||
from .template_filters import with_type, to_transfer, format_amount_input, \
|
from .template_filters import with_type, to_transfer, format_amount_input, \
|
||||||
@ -67,7 +68,7 @@ def show_add_journal_entry_form(journal_entry_type: JournalEntryType) -> str:
|
|||||||
form.validate()
|
form.validate()
|
||||||
else:
|
else:
|
||||||
form = journal_entry_op.form()
|
form = journal_entry_op.form()
|
||||||
form.date.data = dt.date.today()
|
form.date.data = get_tz_today()
|
||||||
return journal_entry_op.render_create_template(form)
|
return journal_entry_op.render_create_template(form)
|
||||||
|
|
||||||
|
|
||||||
|
@ -304,7 +304,6 @@ class Account(db.Model):
|
|||||||
cls.base_code.startswith("78"),
|
cls.base_code.startswith("78"),
|
||||||
cls.base_code.startswith("8"),
|
cls.base_code.startswith("8"),
|
||||||
cls.base_code.startswith("9")),
|
cls.base_code.startswith("9")),
|
||||||
cls.base_code != "3351",
|
|
||||||
cls.base_code != "3353")\
|
cls.base_code != "3353")\
|
||||||
.order_by(cls.base_code, cls.no).all()
|
.order_by(cls.base_code, cls.no).all()
|
||||||
|
|
||||||
@ -326,7 +325,6 @@ class Account(db.Model):
|
|||||||
cls.base_code.startswith("74"),
|
cls.base_code.startswith("74"),
|
||||||
cls.base_code.startswith("8"),
|
cls.base_code.startswith("8"),
|
||||||
cls.base_code.startswith("9")),
|
cls.base_code.startswith("9")),
|
||||||
cls.base_code != "3351",
|
|
||||||
cls.base_code != "3353")\
|
cls.base_code != "3353")\
|
||||||
.order_by(cls.base_code, cls.no).all()
|
.order_by(cls.base_code, cls.no).all()
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -24,6 +24,7 @@ import datetime as dt
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
from accounting.models import JournalEntry
|
from accounting.models import JournalEntry
|
||||||
|
from accounting.utils.timezone import get_tz_today
|
||||||
from .period import Period
|
from .period import Period
|
||||||
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
|
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
|
||||||
LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod
|
LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod
|
||||||
@ -80,7 +81,7 @@ class PeriodChooser:
|
|||||||
"""The available years."""
|
"""The available years."""
|
||||||
|
|
||||||
if self.has_data:
|
if self.has_data:
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
self.has_last_month = start < dt.date(today.year, today.month, 1)
|
self.has_last_month = start < dt.date(today.year, today.month, 1)
|
||||||
self.has_last_year = start.year < today.year
|
self.has_last_year = start.year < today.year
|
||||||
self.has_yesterday = start < today
|
self.has_yesterday = start < today
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -20,6 +20,7 @@
|
|||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
|
from accounting.utils.timezone import get_tz_today
|
||||||
from .month_end import month_end
|
from .month_end import month_end
|
||||||
from .period import Period
|
from .period import Period
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ from .period import Period
|
|||||||
class ThisMonth(Period):
|
class ThisMonth(Period):
|
||||||
"""The period of this month."""
|
"""The period of this month."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
this_month_start: dt.date = dt.date(today.year, today.month, 1)
|
this_month_start: dt.date = dt.date(today.year, today.month, 1)
|
||||||
super().__init__(this_month_start, month_end(today))
|
super().__init__(this_month_start, month_end(today))
|
||||||
self.is_default = True
|
self.is_default = True
|
||||||
@ -43,7 +44,7 @@ class ThisMonth(Period):
|
|||||||
class LastMonth(Period):
|
class LastMonth(Period):
|
||||||
"""The period of this month."""
|
"""The period of this month."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
year: int = today.year
|
year: int = today.year
|
||||||
month: int = today.month - 1
|
month: int = today.month - 1
|
||||||
if month < 1:
|
if month < 1:
|
||||||
@ -63,7 +64,7 @@ class LastMonth(Period):
|
|||||||
class SinceLastMonth(Period):
|
class SinceLastMonth(Period):
|
||||||
"""The period of this month."""
|
"""The period of this month."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
year: int = today.year
|
year: int = today.year
|
||||||
month: int = today.month - 1
|
month: int = today.month - 1
|
||||||
if month < 1:
|
if month < 1:
|
||||||
@ -82,7 +83,7 @@ class SinceLastMonth(Period):
|
|||||||
class ThisYear(Period):
|
class ThisYear(Period):
|
||||||
"""The period of this year."""
|
"""The period of this year."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
year: int = dt.date.today().year
|
year: int = get_tz_today().year
|
||||||
start: dt.date = dt.date(year, 1, 1)
|
start: dt.date = dt.date(year, 1, 1)
|
||||||
end: dt.date = dt.date(year, 12, 31)
|
end: dt.date = dt.date(year, 12, 31)
|
||||||
super().__init__(start, end)
|
super().__init__(start, end)
|
||||||
@ -97,7 +98,7 @@ class ThisYear(Period):
|
|||||||
class LastYear(Period):
|
class LastYear(Period):
|
||||||
"""The period of last year."""
|
"""The period of last year."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
year: int = dt.date.today().year
|
year: int = get_tz_today().year
|
||||||
start: dt.date = dt.date(year - 1, 1, 1)
|
start: dt.date = dt.date(year - 1, 1, 1)
|
||||||
end: dt.date = dt.date(year - 1, 12, 31)
|
end: dt.date = dt.date(year - 1, 12, 31)
|
||||||
super().__init__(start, end)
|
super().__init__(start, end)
|
||||||
@ -112,7 +113,7 @@ class LastYear(Period):
|
|||||||
class Today(Period):
|
class Today(Period):
|
||||||
"""The period of today."""
|
"""The period of today."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
super().__init__(today, today)
|
super().__init__(today, today)
|
||||||
self.is_today = True
|
self.is_today = True
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ class Today(Period):
|
|||||||
class Yesterday(Period):
|
class Yesterday(Period):
|
||||||
"""The period of yesterday."""
|
"""The period of yesterday."""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
yesterday: dt.date = dt.date.today() - dt.timedelta(days=1)
|
yesterday: dt.date = get_tz_today() - dt.timedelta(days=1)
|
||||||
super().__init__(yesterday, yesterday)
|
super().__init__(yesterday, yesterday)
|
||||||
self.is_yesterday = True
|
self.is_yesterday = True
|
||||||
|
|
||||||
|
37
src/accounting/static/js/timezone.js
Normal file
37
src/accounting/static/js/timezone.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* The Mia! Accounting Project
|
||||||
|
* timezone.js: The JavaScript for the timezone
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Copyright (c) 2024 imacat.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Author: imacat@mail.imacat.idv.tw (imacat)
|
||||||
|
* First written: 2024/6/4
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Initializes the page JavaScript.
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
setTimeZone();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time zone.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function setTimeZone() {
|
||||||
|
document.cookie = `accounting-tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}; SameSite=Strict`;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -24,6 +24,7 @@ from typing import Any
|
|||||||
from flask_babel import get_locale
|
from flask_babel import get_locale
|
||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
|
from accounting.utils.timezone import get_tz_today
|
||||||
|
|
||||||
|
|
||||||
def format_amount(value: Decimal | None) -> str | None:
|
def format_amount(value: Decimal | None) -> str | None:
|
||||||
@ -47,7 +48,7 @@ def format_date(value: dt.date) -> str:
|
|||||||
:param value: The date.
|
:param value: The date.
|
||||||
:return: The human-friendly date text.
|
:return: The human-friendly date text.
|
||||||
"""
|
"""
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
if value == today:
|
if value == today:
|
||||||
return gettext("Today")
|
return gettext("Today")
|
||||||
if value == today - dt.timedelta(days=1):
|
if value == today - dt.timedelta(days=1):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
The Mia! Accounting Project
|
The Mia! Accounting Project
|
||||||
base.html: The application-wide base template.
|
base.html: The application-wide base template.
|
||||||
|
|
||||||
Copyright (c) 2023 imacat.
|
Copyright (c) 2023-2024 imacat.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -27,5 +27,6 @@ First written: 2023/1/27
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ url_for("accounting.babel_catalog") }}"></script>
|
<script src="{{ url_for("accounting.babel_catalog") }}"></script>
|
||||||
|
<script src="{{ url_for("accounting.static", filename="js/timezone.js") }}"></script>
|
||||||
{% block accounting_scripts %}{% endblock %}
|
{% block accounting_scripts %}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -23,7 +23,7 @@ First written: 2023/2/26
|
|||||||
|
|
||||||
{% block as_trasfer %}
|
{% block as_trasfer %}
|
||||||
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}">
|
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}">
|
||||||
<i class="fa-solid fa-bars-staggered"></i>
|
<i class="fa-solid fa-table-columns"></i>
|
||||||
<span class="d-none d-md-inline">{{ A_("As Transfer") }}</span>
|
<span class="d-none d-md-inline">{{ A_("As Transfer") }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/1/25
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -19,7 +19,6 @@
|
|||||||
This module should not import any other module from the application.
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import TypeVar, Generic
|
|
||||||
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \
|
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \
|
||||||
ParseResult
|
ParseResult
|
||||||
|
|
||||||
@ -62,11 +61,8 @@ class Redirection(RequestRedirect):
|
|||||||
DEFAULT_PAGE_SIZE: int = 10
|
DEFAULT_PAGE_SIZE: int = 10
|
||||||
"""The default page size."""
|
"""The default page size."""
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
"""The pagination item type."""
|
|
||||||
|
|
||||||
|
class Pagination[T]:
|
||||||
class Pagination(Generic[T]):
|
|
||||||
"""The pagination utility."""
|
"""The pagination utility."""
|
||||||
|
|
||||||
def __init__(self, items: list[T], is_reversed: bool = False):
|
def __init__(self, items: list[T], is_reversed: bool = False):
|
||||||
@ -92,7 +88,7 @@ class Pagination(Generic[T]):
|
|||||||
"""The options to the number of items in a page."""
|
"""The options to the number of items in a page."""
|
||||||
|
|
||||||
|
|
||||||
class AbstractPagination(Generic[T]):
|
class AbstractPagination[T]:
|
||||||
"""An abstract pagination."""
|
"""An abstract pagination."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -109,12 +105,12 @@ class AbstractPagination(Generic[T]):
|
|||||||
"""The options to the number of items in a page."""
|
"""The options to the number of items in a page."""
|
||||||
|
|
||||||
|
|
||||||
class EmptyPagination(AbstractPagination[T]):
|
class EmptyPagination[T](AbstractPagination[T]):
|
||||||
"""The pagination from empty data."""
|
"""The pagination from empty data."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NonEmptyPagination(AbstractPagination[T]):
|
class NonEmptyPagination[T](AbstractPagination[T]):
|
||||||
"""The pagination with real data."""
|
"""The pagination with real data."""
|
||||||
PAGE_SIZE_OPTION_VALUES: list[int] = [10, 100, 200]
|
PAGE_SIZE_OPTION_VALUES: list[int] = [10, 100, 200]
|
||||||
"""The page size options."""
|
"""The page size options."""
|
||||||
|
37
src/accounting/utils/timezone.py
Normal file
37
src/accounting/utils/timezone.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# The Mia! Accounting Project.
|
||||||
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2024/6/4
|
||||||
|
|
||||||
|
# Copyright (c) 2024 imacat.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""The timezone utility.
|
||||||
|
|
||||||
|
This module should not import any other module from the application.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
|
||||||
|
def get_tz_today() -> dt.date:
|
||||||
|
"""Returns today in the client timezone.
|
||||||
|
|
||||||
|
:return: today in the client timezone.
|
||||||
|
"""
|
||||||
|
tz_name: str | None = request.cookies.get("accounting-tz")
|
||||||
|
if tz_name is None:
|
||||||
|
return dt.date.today()
|
||||||
|
return dt.datetime.now(tz=pytz.timezone(tz_name)).date()
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Project.
|
# The Mia! Accounting Project.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -20,17 +20,14 @@ This module should not import any other module from the application.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import TypeVar, Generic, Type
|
from typing import Type
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask import g, Response
|
from flask import g, Response
|
||||||
from flask_sqlalchemy.model import Model
|
from flask_sqlalchemy.model import Model
|
||||||
|
|
||||||
T = TypeVar("T", bound=Model)
|
|
||||||
"""The user data model data type."""
|
|
||||||
|
|
||||||
|
class UserUtilityInterface[T: Model](ABC):
|
||||||
class UserUtilityInterface(Generic[T], ABC):
|
|
||||||
"""The interface for the user utilities."""
|
"""The interface for the user utilities."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -113,7 +110,7 @@ class UserUtilityInterface(Generic[T], ABC):
|
|||||||
|
|
||||||
__user_utils: UserUtilityInterface
|
__user_utils: UserUtilityInterface
|
||||||
"""The user utilities."""
|
"""The user utilities."""
|
||||||
user_cls: Type[Model] = Model
|
type user_cls = Model
|
||||||
"""The user class."""
|
"""The user class."""
|
||||||
user_pk_column: sa.Column = sa.Column(sa.Integer)
|
user_pk_column: sa.Column = sa.Column(sa.Integer)
|
||||||
"""The primary key column of the user class."""
|
"""The primary key column of the user class."""
|
||||||
|
@ -49,7 +49,7 @@ def create_app(is_testing: bool = False) -> Flask:
|
|||||||
import accounting
|
import accounting
|
||||||
|
|
||||||
app: Flask = Flask(__name__)
|
app: Flask = Flask(__name__)
|
||||||
db_uri: str = "sqlite:///" if is_testing else "sqlite:///local.sqlite"
|
db_uri: str = "sqlite://" if is_testing else "sqlite:///local.sqlite"
|
||||||
app.config.from_mapping({
|
app.config.from_mapping({
|
||||||
"SECRET_KEY": os.environ.get("SECRET_KEY", token_urlsafe(32)),
|
"SECRET_KEY": os.environ.get("SECRET_KEY", token_urlsafe(32)),
|
||||||
"SESSION_COOKIE_SAMESITE": "Lax",
|
"SESSION_COOKIE_SAMESITE": "Lax",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Demonstration Website.
|
# The Mia! Accounting Demonstration Website.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/13
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/13
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -28,6 +28,7 @@ from typing import Any
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
from accounting.utils.timezone import get_tz_today
|
||||||
from . import db
|
from . import db
|
||||||
from .auth import User
|
from .auth import User
|
||||||
|
|
||||||
@ -44,6 +45,17 @@ class Accounts:
|
|||||||
MEAL: str = "6272-001"
|
MEAL: str = "6272-001"
|
||||||
|
|
||||||
|
|
||||||
|
def get_today() -> dt.date:
|
||||||
|
"""Returns today, based on the context.
|
||||||
|
|
||||||
|
:return: Today.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return get_tz_today()
|
||||||
|
except RuntimeError:
|
||||||
|
return dt.date.today()
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryLineItemData:
|
class JournalEntryLineItemData:
|
||||||
"""The journal entry line item data."""
|
"""The journal entry line item data."""
|
||||||
|
|
||||||
@ -183,7 +195,7 @@ class JournalEntryData:
|
|||||||
:param is_update: True for an update operation, or False otherwise
|
:param is_update: True for an update operation, or False otherwise
|
||||||
:return: The journal entry as a form.
|
:return: The journal entry as a form.
|
||||||
"""
|
"""
|
||||||
date: dt.date = dt.date.today() - dt.timedelta(days=self.days)
|
date: dt.date = get_today() - dt.timedelta(days=self.days)
|
||||||
form: dict[str, str] = {"csrf_token": csrf_token,
|
form: dict[str, str] = {"csrf_token": csrf_token,
|
||||||
"next": encoded_next_uri,
|
"next": encoded_next_uri,
|
||||||
"date": date.isoformat()}
|
"date": date.isoformat()}
|
||||||
@ -260,8 +272,7 @@ class BaseTestData(ABC):
|
|||||||
existing_j_id: set[int] = {x["id"] for x in self.__journal_entries}
|
existing_j_id: set[int] = {x["id"] for x in self.__journal_entries}
|
||||||
existing_l_id: set[int] = {x["id"] for x in self.__line_items}
|
existing_l_id: set[int] = {x["id"] for x in self.__line_items}
|
||||||
journal_entry_data.id = self.__new_id(existing_j_id)
|
journal_entry_data.id = self.__new_id(existing_j_id)
|
||||||
date: dt.date \
|
date: dt.date = get_today() - dt.timedelta(days=journal_entry_data.days)
|
||||||
= dt.date.today() - dt.timedelta(days=journal_entry_data.days)
|
|
||||||
self.__journal_entries.append(
|
self.__journal_entries.append(
|
||||||
{"id": journal_entry_data.id,
|
{"id": journal_entry_data.id,
|
||||||
"date": date,
|
"date": date,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# The Mia! Accounting Demonstration Website.
|
# The Mia! Accounting Demonstration Website.
|
||||||
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/12
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/12
|
||||||
|
|
||||||
# Copyright (c) 2023 imacat.
|
# Copyright (c) 2023-2024 imacat.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -23,6 +23,7 @@ from flask import Flask, Blueprint, url_for, flash, redirect, session, \
|
|||||||
render_template, current_app
|
render_template, current_app
|
||||||
from flask_babel import lazy_gettext
|
from flask_babel import lazy_gettext
|
||||||
|
|
||||||
|
from accounting.utils.timezone import get_tz_today
|
||||||
from . import db
|
from . import db
|
||||||
from .auth import admin_required
|
from .auth import admin_required
|
||||||
from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \
|
from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \
|
||||||
@ -117,7 +118,7 @@ class SampleData(BaseTestData):
|
|||||||
|
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
days: int
|
days: int
|
||||||
year: int
|
year: int
|
||||||
month: int
|
month: int
|
||||||
@ -160,7 +161,7 @@ class SampleData(BaseTestData):
|
|||||||
|
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
today: dt.date = dt.date.today()
|
today: dt.date = get_tz_today()
|
||||||
|
|
||||||
year: int = today.year - 5
|
year: int = today.year - 5
|
||||||
month: int = today.month
|
month: int = today.month
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
The Mia! Accounting Demonstration Website
|
The Mia! Accounting Demonstration Website
|
||||||
base.html: The side-wide layout template
|
base.html: The side-wide layout template
|
||||||
|
|
||||||
Copyright (c) 2023 imacat.
|
Copyright (c) 2023-2024 imacat.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -25,21 +25,21 @@ First written: 2023/1/27
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="author" content="{{ "imacat" }}" />
|
<meta name="author" content="{{ "imacat" }}" />
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" integrity="sha384-iw3OoTErCYJJB9mCa8LNS2hbsQ7M3C0EpIsO/H5+EGAkPGc6rk+V8i04oW/K5xq0" crossorigin="anonymous">
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.7/dist/css/tempus-dominus.min.css" integrity="sha384-l66rSL7gUubrdJxFRbXUo/tO7eNPAcCiZXFs/Xl147146xNqQ1qt4oPW6jlVezsS" crossorigin="anonymous">
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.9.6/dist/css/tempus-dominus.min.css" integrity="sha384-NzVf7b26bC2au5J9EqNceWlrs7iIkBa0bA46tRpK5C3J08J7MRTPmSdpRKhWNgDL" crossorigin="anonymous">
|
||||||
{% block styles %}{% endblock %}
|
{% block styles %}{% endblock %}
|
||||||
<script src="{{ url_for("babel_catalog") }}"></script>
|
<script src="{{ url_for("babel_catalog") }}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/decimal.js-light@2.5.1/decimal.min.js" integrity="sha384-QdsxGEq4Y0erX8WUIsZJDtfoSSyBF6dmNCnzRNYCa2AOM/xzNsyhHu0RbdFBAm+l" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/decimal.js-light@2.5.1/decimal.min.js" integrity="sha384-QdsxGEq4Y0erX8WUIsZJDtfoSSyBF6dmNCnzRNYCa2AOM/xzNsyhHu0RbdFBAm+l" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.7.7/dist/js/tempus-dominus.min.js" integrity="sha384-MxHp+/TqTjbku1jSTIe1e/4l6CZTLhACLDbWyxYaFRgD3AM4oh99AY8bxsGhIoRc" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@eonasdan/tempus-dominus@6.9.6/dist/js/tempus-dominus.min.js" integrity="sha384-GRg4jmBEA/AnwmpV7MhpXUTim20ncyZTm9/1fbna86CRqMcdrou46etX8scQ9dPe" crossorigin="anonymous"></script>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
<link rel="shortcut icon" href="{{ url_for("static", filename="favicon.svg") }}">
|
<link rel="shortcut icon" href="{{ url_for("static", filename="favicon.svg") }}">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark navbar-dark">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary bg-dark" data-bs-theme="dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{{ url_for("home.home") }}">
|
<a class="navbar-brand" href="{{ url_for("home.home") }}">
|
||||||
<i class="fa-solid fa-house"></i>
|
<i class="fa-solid fa-house"></i>
|
||||||
|
@ -67,8 +67,9 @@ class NextUriTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
self.__app.add_url_rule("/test-next", view_func=test_next_uri_view,
|
self.__app.add_url_rule("/test-next", view_func=test_next_uri_view,
|
||||||
methods=["GET", "POST"])
|
methods=["GET", "POST"])
|
||||||
client: httpx.Client = httpx.Client(app=self.__app,
|
client: httpx.Client = httpx.Client(
|
||||||
base_url=TEST_SERVER)
|
transport=httpx.WSGITransport(app=self.__app),
|
||||||
|
base_url=TEST_SERVER)
|
||||||
client.headers["Referer"] = TEST_SERVER
|
client.headers["Referer"] = TEST_SERVER
|
||||||
csrf_token: str = get_csrf_token(client)
|
csrf_token: str = get_csrf_token(client)
|
||||||
response: httpx.Response
|
response: httpx.Response
|
||||||
@ -96,8 +97,9 @@ class NextUriTestCase(unittest.TestCase):
|
|||||||
self.__app.add_url_rule("/test-no-next",
|
self.__app.add_url_rule("/test-no-next",
|
||||||
view_func=test_no_next_uri_view,
|
view_func=test_no_next_uri_view,
|
||||||
methods=["GET", "POST"])
|
methods=["GET", "POST"])
|
||||||
client: httpx.Client = httpx.Client(app=self.__app,
|
client: httpx.Client = httpx.Client(
|
||||||
base_url=TEST_SERVER)
|
transport=httpx.WSGITransport(app=self.__app),
|
||||||
|
base_url=TEST_SERVER)
|
||||||
client.headers["Referer"] = TEST_SERVER
|
client.headers["Referer"] = TEST_SERVER
|
||||||
csrf_token: str = get_csrf_token(client)
|
csrf_token: str = get_csrf_token(client)
|
||||||
response: httpx.Response
|
response: httpx.Response
|
||||||
@ -122,8 +124,9 @@ class NextUriTestCase(unittest.TestCase):
|
|||||||
self.__app.add_url_rule("/test-invalid-next",
|
self.__app.add_url_rule("/test-invalid-next",
|
||||||
view_func=test_invalid_next_uri_view,
|
view_func=test_invalid_next_uri_view,
|
||||||
methods=["GET", "POST"])
|
methods=["GET", "POST"])
|
||||||
client: httpx.Client = httpx.Client(app=self.__app,
|
client: httpx.Client = httpx.Client(
|
||||||
base_url=TEST_SERVER)
|
transport=httpx.WSGITransport(app=self.__app),
|
||||||
|
base_url=TEST_SERVER)
|
||||||
client.headers["Referer"] = TEST_SERVER
|
client.headers["Referer"] = TEST_SERVER
|
||||||
csrf_token: str = get_csrf_token(client)
|
csrf_token: str = get_csrf_token(client)
|
||||||
next_uri: str
|
next_uri: str
|
||||||
@ -227,8 +230,9 @@ class PaginationTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(pagination.list, self.__params.result)
|
self.assertEqual(pagination.list, self.__params.result)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
self.__client: httpx.Client = httpx.Client(app=self.__app,
|
self.__client: httpx.Client = httpx.Client(
|
||||||
base_url=TEST_SERVER)
|
transport=httpx.WSGITransport(app=self.__app),
|
||||||
|
base_url=TEST_SERVER)
|
||||||
"""The user client."""
|
"""The user client."""
|
||||||
self.__client.headers["Referer"] = TEST_SERVER
|
self.__client.headers["Referer"] = TEST_SERVER
|
||||||
|
|
||||||
|
@ -96,7 +96,9 @@ def get_client(app: Flask, username: str) -> httpx.Client:
|
|||||||
:param username: The username.
|
:param username: The username.
|
||||||
:return: The user client.
|
:return: The user client.
|
||||||
"""
|
"""
|
||||||
client: httpx.Client = httpx.Client(app=app, base_url=TEST_SERVER)
|
client: httpx.Client = httpx.Client(
|
||||||
|
transport=httpx.WSGITransport(app=app),
|
||||||
|
base_url=TEST_SERVER)
|
||||||
client.headers["Referer"] = TEST_SERVER
|
client.headers["Referer"] = TEST_SERVER
|
||||||
csrf_token: str = get_csrf_token(client)
|
csrf_token: str = get_csrf_token(client)
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
Reference in New Issue
Block a user