Compare commits
	
		
			17 Commits
		
	
	
		
			v1.5.9
			...
			aea9dcae79
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| aea9dcae79 | |||
| 40278eaf06 | |||
| e00c14f277 | |||
| f20c462685 | |||
| 80ae4bd91c | |||
| 6ee3ee76ea | |||
| 2bfcc8b889 | |||
| 99564c02d0 | |||
| 25d9904180 | |||
| 1cf83adf87 | |||
| 8e3d1f11b5 | |||
| 0ab14aa34d | |||
| e0ed81ad1f | |||
| ece7481e9e | |||
| 50d4526e0b | |||
| 3f0a0b4227 | |||
| dcc9626b23 | 
| @@ -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. | ||||||
|   | |||||||
| @@ -2,10 +2,52 @@ 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 | Version 1.5.9 | ||||||
| ------------- | ------------- | ||||||
|  |  | ||||||
| Released 2023/10/24 | Released 2023/11/28 | ||||||
|  |  | ||||||
| Bug fix. | Bug fix. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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.9" | 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,8 +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")), | ||||||
|                                 sa.not_(sa.and_(cls.base_code == "3351", |  | ||||||
|                                                 cls.no == 1)), |  | ||||||
|                                 cls.base_code != "3353")\ |                                 cls.base_code != "3353")\ | ||||||
|             .order_by(cls.base_code, cls.no).all() |             .order_by(cls.base_code, cls.no).all() | ||||||
|  |  | ||||||
| @@ -327,8 +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")), | ||||||
|                                 sa.not_(sa.and_(cls.base_code == "3351", |  | ||||||
|                                                 cls.no == 1)), |  | ||||||
|                                 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 %} | ||||||
|   | |||||||
| @@ -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,7 +67,8 @@ 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( | ||||||
|  |             transport=httpx.WSGITransport(app=self.__app), | ||||||
|             base_url=TEST_SERVER) |             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) | ||||||
| @@ -96,7 +97,8 @@ 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( | ||||||
|  |             transport=httpx.WSGITransport(app=self.__app), | ||||||
|             base_url=TEST_SERVER) |             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) | ||||||
| @@ -122,7 +124,8 @@ 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( | ||||||
|  |             transport=httpx.WSGITransport(app=self.__app), | ||||||
|             base_url=TEST_SERVER) |             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) | ||||||
| @@ -227,7 +230,8 @@ 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( | ||||||
|  |             transport=httpx.WSGITransport(app=self.__app), | ||||||
|             base_url=TEST_SERVER) |             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