monopyly 1.4.6__py3-none-any.whl → 1.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monopyly/CHANGELOG.md +195 -0
- monopyly/README.md +14 -3
- monopyly/__init__.py +20 -1
- monopyly/_version.py +2 -2
- monopyly/auth/actions.py +7 -2
- monopyly/banking/accounts.py +1 -1
- monopyly/banking/actions.py +51 -10
- monopyly/banking/routes.py +2 -1
- monopyly/cli/apps.py +51 -28
- monopyly/cli/{run.py → launch.py} +18 -10
- monopyly/common/forms/_forms.py +4 -1
- monopyly/core/actions.py +108 -21
- monopyly/core/blueprint.py +1 -1
- monopyly/core/context_processors.py +10 -0
- monopyly/core/errors.py +9 -0
- monopyly/core/filters.py +4 -2
- monopyly/core/routes.py +22 -9
- monopyly/credit/routes.py +9 -1
- monopyly/credit/transactions/_transactions.py +1 -1
- monopyly/static/css/style.css +166 -73
- monopyly/static/js/make-payment.js +4 -2
- monopyly/templates/banking/account_summaries.html +26 -12
- monopyly/templates/banking/account_summaries_page.html +2 -1
- monopyly/templates/banking/account_summary.html +7 -2
- monopyly/templates/banking/accounts_page.html +2 -2
- monopyly/templates/core/credits.html +32 -0
- monopyly/templates/core/errors/400.html +8 -0
- monopyly/templates/core/errors/401.html +8 -0
- monopyly/templates/core/errors/403.html +8 -0
- monopyly/templates/core/errors/404.html +8 -0
- monopyly/templates/core/errors/405.html +8 -0
- monopyly/templates/core/errors/408.html +8 -0
- monopyly/templates/core/errors/418.html +8 -0
- monopyly/templates/core/errors/425.html +8 -0
- monopyly/templates/core/errors/500.html +8 -0
- monopyly/templates/core/errors/error.html +31 -0
- monopyly/templates/{profile.html → core/profile.html} +3 -1
- monopyly/templates/core/story.html +62 -0
- monopyly/templates/credit/statement_summary.html +7 -2
- monopyly/templates/credit/statements.html +7 -6
- monopyly/templates/layout.html +11 -2
- {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/METADATA +15 -4
- {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/RECORD +48 -36
- monopyly-1.4.8.dist-info/entry_points.txt +2 -0
- monopyly/templates/credits.html +0 -20
- monopyly/templates/story.html +0 -47
- monopyly-1.4.6.dist-info/entry_points.txt +0 -2
- /monopyly/templates/{index.html → core/index.html} +0 -0
- {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/WHEEL +0 -0
- {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/licenses/COPYING +0 -0
- {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/licenses/LICENSE +0 -0
monopyly/core/actions.py
CHANGED
|
@@ -10,25 +10,112 @@ def get_timestamp():
|
|
|
10
10
|
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
13
|
+
class MarkdownConverter:
|
|
14
|
+
|
|
15
|
+
replacements = {
|
|
16
|
+
"src": [
|
|
17
|
+
["monopyly/static", "/static"],
|
|
18
|
+
],
|
|
19
|
+
"href": [
|
|
20
|
+
["README.md", '{{ url_for("core.about") }}'],
|
|
21
|
+
["CHANGELOG.md", '{{ url_for("core.changelog") }}'],
|
|
22
|
+
],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def convert(cls, markdown_path, title, id_="", class_="", extra_content=""):
|
|
27
|
+
"""Given a Markdown file, convert it to a renderable HTML template."""
|
|
28
|
+
raw_markdown = cls._read_markdown(markdown_path)
|
|
29
|
+
html_content = cls._convert_markdown_to_html(raw_markdown)
|
|
30
|
+
return cls._generate_html_template(
|
|
31
|
+
html_content, title, id_=id_, class_=class_, extra_content=extra_content
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _read_markdown(markdown_path):
|
|
36
|
+
with markdown_path.open(encoding="utf-8") as markdown_file:
|
|
37
|
+
raw_markdown = markdown_file.read()
|
|
38
|
+
return raw_markdown
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _convert_markdown_to_html(cls, raw_markdown):
|
|
42
|
+
raw_html = markdown.markdown(raw_markdown, extensions=["fenced_code"])
|
|
43
|
+
return cls._replace_links(raw_html)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _replace_links(cls, raw_html):
|
|
47
|
+
html = raw_html
|
|
48
|
+
for tag, pairs in cls.replacements.items():
|
|
49
|
+
for original, replacement in pairs:
|
|
50
|
+
html = html.replace(f'{tag}="{original}', f'{tag}="{replacement}')
|
|
51
|
+
return html
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _generate_html_template(content, title, id_="", class_="", extra_content=""):
|
|
55
|
+
# Format the HTML as a valid Jinja template
|
|
56
|
+
html_template = (
|
|
57
|
+
'{% extends "layout.html" %}'
|
|
58
|
+
"{% block title %}"
|
|
59
|
+
f" {title}"
|
|
60
|
+
"{% endblock %}"
|
|
61
|
+
"{% block content %}"
|
|
62
|
+
f' <div id="{id_}" class="{class_}">'
|
|
63
|
+
f" {content}"
|
|
64
|
+
f" {extra_content}"
|
|
65
|
+
"{% endblock %}"
|
|
66
|
+
)
|
|
67
|
+
return html_template
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def convert_readme_to_html_template(readme_path):
|
|
71
|
+
"""Given a README file in Markdown, convert it to a renderable HTML template."""
|
|
72
|
+
return MarkdownConverter.convert(
|
|
73
|
+
readme_path,
|
|
74
|
+
title="About",
|
|
75
|
+
id_="readme",
|
|
76
|
+
class_="about",
|
|
77
|
+
extra_content=(
|
|
78
|
+
'<div class="resource-links">'
|
|
79
|
+
" <h2>Links</h2>"
|
|
80
|
+
' <p><a href="{{ url_for("core.story") }}">Story</a></p>'
|
|
81
|
+
' <p><a href="{{ url_for("core.credits") }}">Credits</a></p>'
|
|
82
|
+
"</div>"
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def convert_changelog_to_html_template(changelog_path):
|
|
88
|
+
"""Given a CHANGELOG file in Markdown, convert it to a renderable HTML template."""
|
|
89
|
+
return MarkdownConverter.convert(
|
|
90
|
+
changelog_path,
|
|
91
|
+
title="Changes",
|
|
92
|
+
id_="changelog",
|
|
33
93
|
)
|
|
34
|
-
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def determine_summary_balance_svg_viewbox_width(currency_value):
|
|
97
|
+
"""
|
|
98
|
+
Determine the width of the SVG viewBox attribute displayed in summary boxes.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
currency_value : str
|
|
103
|
+
A currency value, displayed in the format output by the
|
|
104
|
+
`core.filters.make_currency` filter function.
|
|
105
|
+
"""
|
|
106
|
+
# Set the per-character width contributions
|
|
107
|
+
digit_width = 55
|
|
108
|
+
punctuation_width = 25
|
|
109
|
+
spacing_width = 25
|
|
110
|
+
# Count the number of commas and non-comma characters in the non-decimal portion
|
|
111
|
+
nondecimal_value = currency_value.rsplit(".", maxsplit=1)[0]
|
|
112
|
+
comma_count = nondecimal_value.count(",")
|
|
113
|
+
digit_count = len(nondecimal_value) - comma_count
|
|
114
|
+
# Width is the total of the following subcomponents
|
|
115
|
+
svg_currency_width_subcomponents = [
|
|
116
|
+
digit_width * digit_count, # ------------- total width of digits/sign
|
|
117
|
+
punctuation_width * comma_count, # ------- total width of commas
|
|
118
|
+
punctuation_width + (2 * digit_width), # - width of the decimal section
|
|
119
|
+
digit_width + spacing_width, # ----------- width of the dollar sign and spacing
|
|
120
|
+
]
|
|
121
|
+
return max(400, sum(svg_currency_width_subcomponents))
|
monopyly/core/blueprint.py
CHANGED
|
@@ -5,6 +5,7 @@ Filters defined for the application.
|
|
|
5
5
|
from datetime import date
|
|
6
6
|
from importlib import import_module
|
|
7
7
|
|
|
8
|
+
from .actions import determine_summary_balance_svg_viewbox_width
|
|
8
9
|
from .blueprint import bp
|
|
9
10
|
|
|
10
11
|
|
|
@@ -19,6 +20,15 @@ def inject_global_template_variables():
|
|
|
19
20
|
return template_globals
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
@bp.app_context_processor
|
|
24
|
+
def inject_utility_functions():
|
|
25
|
+
"""Inject utility functions globally into the template context."""
|
|
26
|
+
utility_functions = {
|
|
27
|
+
"calculate_summary_balance_width": determine_summary_balance_svg_viewbox_width,
|
|
28
|
+
}
|
|
29
|
+
return utility_functions
|
|
30
|
+
|
|
31
|
+
|
|
22
32
|
def _display_version():
|
|
23
33
|
"""Show the version (without commit information)."""
|
|
24
34
|
try:
|
monopyly/core/errors.py
ADDED
monopyly/core/filters.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Filters defined for the application.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from math import floor, log10
|
|
6
|
+
|
|
5
7
|
from .blueprint import bp
|
|
6
8
|
|
|
7
9
|
|
|
@@ -32,8 +34,8 @@ def make_ordinal(integer):
|
|
|
32
34
|
|
|
33
35
|
Notes
|
|
34
36
|
-----
|
|
35
|
-
This function is an adaptation of the one proposed by Stack Overflow
|
|
36
|
-
Florian Brucker (https://stackoverflow.com/a/50992575/8754471).
|
|
37
|
+
This function is an adaptation of the one proposed by Stack Overflow
|
|
38
|
+
user Florian Brucker (https://stackoverflow.com/a/50992575/8754471).
|
|
37
39
|
|
|
38
40
|
Parameters
|
|
39
41
|
----------
|
monopyly/core/routes.py
CHANGED
|
@@ -5,15 +5,18 @@ Routes for core functionality.
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from flask import g, render_template, render_template_string, session
|
|
8
|
+
from werkzeug.exceptions import abort
|
|
8
9
|
|
|
9
10
|
from ..auth.tools import login_required
|
|
10
11
|
from ..banking.accounts import BankAccountHandler
|
|
11
12
|
from ..banking.banks import BankHandler
|
|
12
13
|
from ..credit.cards import CreditCardHandler
|
|
13
14
|
from ..credit.statements import CreditStatementHandler
|
|
14
|
-
from .actions import
|
|
15
|
+
from .actions import convert_changelog_to_html_template, convert_readme_to_html_template
|
|
15
16
|
from .blueprint import bp
|
|
16
17
|
|
|
18
|
+
APP_ROOT_DIR = Path(__file__).parents[1]
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
@bp.route("/")
|
|
19
22
|
def index():
|
|
@@ -40,7 +43,7 @@ def index():
|
|
|
40
43
|
session["show_homepage_block"] = True
|
|
41
44
|
bank_accounts, active_cards = None, None
|
|
42
45
|
return render_template(
|
|
43
|
-
"index.html", bank_accounts=bank_accounts, cards=active_cards
|
|
46
|
+
"core/index.html", bank_accounts=bank_accounts, cards=active_cards
|
|
44
47
|
)
|
|
45
48
|
|
|
46
49
|
|
|
@@ -53,22 +56,27 @@ def hide_homepage_block():
|
|
|
53
56
|
|
|
54
57
|
@bp.route("/about")
|
|
55
58
|
def about():
|
|
56
|
-
readme_path =
|
|
57
|
-
|
|
58
|
-
raw_readme_text = readme_file.read()
|
|
59
|
-
about_page_template = format_readme_as_html_template(raw_readme_text)
|
|
59
|
+
readme_path = APP_ROOT_DIR / "README.md"
|
|
60
|
+
about_page_template = convert_readme_to_html_template(readme_path)
|
|
60
61
|
return render_template_string(about_page_template)
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
@bp.route("/changelog")
|
|
65
|
+
def changelog():
|
|
66
|
+
changelog_path = APP_ROOT_DIR / "CHANGELOG.md"
|
|
67
|
+
changelog_page_template = convert_changelog_to_html_template(changelog_path)
|
|
68
|
+
return render_template_string(changelog_page_template)
|
|
69
|
+
|
|
70
|
+
|
|
63
71
|
@bp.route("/story")
|
|
64
72
|
@login_required
|
|
65
73
|
def story():
|
|
66
|
-
return render_template("story.html")
|
|
74
|
+
return render_template("core/story.html")
|
|
67
75
|
|
|
68
76
|
|
|
69
77
|
@bp.route("/credits")
|
|
70
78
|
def credits():
|
|
71
|
-
return render_template("credits.html")
|
|
79
|
+
return render_template("core/credits.html")
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
@bp.route("/profile")
|
|
@@ -76,4 +84,9 @@ def credits():
|
|
|
76
84
|
def load_profile():
|
|
77
85
|
banks = BankHandler.get_banks()
|
|
78
86
|
# Return banks as a list to allow multiple reuse
|
|
79
|
-
return render_template("profile.html", banks=list(banks))
|
|
87
|
+
return render_template("core/profile.html", banks=list(banks))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@bp.route("/change_password")
|
|
91
|
+
def change_password():
|
|
92
|
+
abort(418)
|
monopyly/credit/routes.py
CHANGED
|
@@ -216,12 +216,20 @@ def pay_credit_card(card_id, statement_id):
|
|
|
216
216
|
make_payment(card_id, payment_account_id, payment_date, payment_amount)
|
|
217
217
|
# Get the current statement information from the database
|
|
218
218
|
statement = CreditStatementHandler.get_entry(statement_id)
|
|
219
|
+
transactions = CreditTransactionHandler.get_transactions(
|
|
220
|
+
statement_ids=(statement_id,)
|
|
221
|
+
)
|
|
219
222
|
bank_accounts = BankAccountHandler.get_accounts()
|
|
220
|
-
|
|
223
|
+
summary_template = render_template(
|
|
221
224
|
"credit/statement_summary.html",
|
|
222
225
|
statement=statement,
|
|
223
226
|
bank_accounts=bank_accounts,
|
|
224
227
|
)
|
|
228
|
+
transactions_table_template = render_template(
|
|
229
|
+
"credit/transactions_table/transactions.html",
|
|
230
|
+
transactions=transactions,
|
|
231
|
+
)
|
|
232
|
+
return jsonify((summary_template, transactions_table_template))
|
|
225
233
|
|
|
226
234
|
|
|
227
235
|
@bp.route("/transactions", defaults={"card_id": None})
|
|
@@ -52,7 +52,7 @@ class CreditTransactionHandler(
|
|
|
52
52
|
|
|
53
53
|
Parameters
|
|
54
54
|
----------
|
|
55
|
-
statement_ids : tuple of
|
|
55
|
+
statement_ids : tuple of int, optional
|
|
56
56
|
A sequence of statement IDs with which to filter
|
|
57
57
|
transactions (if `None`, all statement IDs will be shown).
|
|
58
58
|
card_ids : tuple of int, optional
|