monopyly 1.4.5__py3-none-any.whl → 1.4.7__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.
Files changed (50) hide show
  1. monopyly/CHANGELOG.md +187 -0
  2. monopyly/README.md +13 -2
  3. monopyly/__init__.py +19 -0
  4. monopyly/_version.py +2 -2
  5. monopyly/banking/accounts.py +1 -1
  6. monopyly/cli/apps.py +25 -4
  7. monopyly/common/forms/_forms.py +14 -11
  8. monopyly/core/actions.py +108 -21
  9. monopyly/core/blueprint.py +1 -1
  10. monopyly/core/context_processors.py +10 -0
  11. monopyly/core/errors.py +9 -0
  12. monopyly/core/filters.py +4 -2
  13. monopyly/core/routes.py +22 -9
  14. monopyly/credit/actions.py +29 -2
  15. monopyly/credit/routes.py +11 -6
  16. monopyly/credit/statements.py +21 -1
  17. monopyly/credit/transactions/_transactions.py +1 -1
  18. monopyly/static/css/style.css +161 -69
  19. monopyly/static/js/make-payment.js +4 -2
  20. monopyly/templates/auth/login.html +1 -1
  21. monopyly/templates/auth/register.html +1 -1
  22. monopyly/templates/banking/account_summaries.html +26 -12
  23. monopyly/templates/banking/account_summaries_page.html +2 -1
  24. monopyly/templates/banking/account_summary.html +7 -2
  25. monopyly/templates/banking/accounts_page.html +2 -2
  26. monopyly/templates/banking/transaction_form/transaction_form.html +1 -1
  27. monopyly/templates/core/credits.html +32 -0
  28. monopyly/templates/core/errors/400.html +8 -0
  29. monopyly/templates/core/errors/401.html +8 -0
  30. monopyly/templates/core/errors/403.html +8 -0
  31. monopyly/templates/core/errors/404.html +8 -0
  32. monopyly/templates/core/errors/405.html +8 -0
  33. monopyly/templates/core/errors/408.html +8 -0
  34. monopyly/templates/core/errors/418.html +8 -0
  35. monopyly/templates/core/errors/425.html +8 -0
  36. monopyly/templates/core/errors/500.html +8 -0
  37. monopyly/templates/core/errors/error.html +31 -0
  38. monopyly/templates/{profile.html → core/profile.html} +3 -1
  39. monopyly/templates/credit/card_graphic/card_back.html +1 -1
  40. monopyly/templates/credit/statement_summary.html +9 -4
  41. monopyly/templates/credit/statements.html +7 -6
  42. {monopyly-1.4.5.dist-info → monopyly-1.4.7.dist-info}/METADATA +16 -5
  43. {monopyly-1.4.5.dist-info → monopyly-1.4.7.dist-info}/RECORD +49 -37
  44. monopyly/templates/credits.html +0 -20
  45. /monopyly/templates/{index.html → core/index.html} +0 -0
  46. /monopyly/templates/{story.html → core/story.html} +0 -0
  47. {monopyly-1.4.5.dist-info → monopyly-1.4.7.dist-info}/WHEEL +0 -0
  48. {monopyly-1.4.5.dist-info → monopyly-1.4.7.dist-info}/entry_points.txt +0 -0
  49. {monopyly-1.4.5.dist-info → monopyly-1.4.7.dist-info}/licenses/COPYING +0 -0
  50. {monopyly-1.4.5.dist-info → monopyly-1.4.7.dist-info}/licenses/LICENSE +0 -0
monopyly/CHANGELOG.md ADDED
@@ -0,0 +1,187 @@
1
+ # Changelog
2
+
3
+ <a class="latest-release" href="#bottom">Latest</a>
4
+
5
+ ## 1.0.0
6
+
7
+ - Initial release
8
+
9
+
10
+ ### 1.0.1
11
+
12
+ - Added dependencies to `setup.py` for self-contained installation
13
+ - Added this changelog
14
+ - Added a generally comprehensive [README](README.md)
15
+ - Fixed a few minor bugs with the display interface
16
+
17
+
18
+ ### 1.0.2
19
+
20
+ - Added image support to PyPI description
21
+
22
+
23
+ ### 1.0.3
24
+
25
+ - Implemented (rudimentary) interface for adding and removing transaction tags
26
+ - Added button to add more statements to a transaction (from submission complete page)
27
+ - Fixed bug in updating a transaction's statement date to a new statement
28
+ - Fixed bug where tags where not saved on new transactions
29
+
30
+
31
+ ### 1.0.4
32
+
33
+ - Improved tagging interface with a 'Manage Tags' page
34
+ - Introduced statement level statistics
35
+ - Renamed database files to end with `.sqlite` extension
36
+ - Moved menus on 'Statement Details' and 'Account Details' pages into the sidebar to prevent overlap on collapsed screens
37
+ - Renamed `show_*` route functions to `load_*` to clarify that they are for loading the associated pages (as opposed to just displaying content)
38
+
39
+
40
+ ### 1.0.5
41
+
42
+ - Transactions may be split into an arbitrary number of subtransactions, each with a separate subtotal and note
43
+ - Backend properly handles duplicate/ancestor tags; all tags are saved in the database, but only the lowest-level child tag must be entered
44
+ - Removed statement level statistics display (not yet mature)
45
+
46
+
47
+ ## 1.1.0
48
+
49
+ - Added banking interface
50
+ - Added interface for displaying linked transactions (including between bank transactions and credit transactions)
51
+ - Grayed out pending transactions
52
+ - Fixed bug where transaction tags appeared in front of header bar
53
+ - Improved modularity of autocomplete JavaScript
54
+ - Improved modularity of transaction table templates (for banking and credit transactions)
55
+ - Minor cosmetic enhancements
56
+
57
+
58
+ ## 1.2.0
59
+
60
+ - Database backend converted to use SQLAlchemy (2.0)
61
+ - Complete test suite created
62
+ - Banking interface updated to use subtransactions
63
+ - Added the ability to transfer statements when a new credit card is added to an account
64
+ - Fixed error on handling subtransactions in credit forms
65
+ - Fixed error updating transaction display from widget bar
66
+
67
+
68
+ ### 1.2.1
69
+
70
+ - Improve development functionality; better/safer cleaning with a separate database and preloaded data
71
+ - Add custom form fields for consistent app-wide form implementations
72
+ - Overhaul documentation formatting with _isort_ and _Black_
73
+ - Fixed bug where multiple subtransactions failed to update properly
74
+ - Fixed error where validation was not raised for blank inputs to a `CustomChoiceSelectField`
75
+ - Fixed errors in field validation for nested subforms with unset values
76
+
77
+
78
+ ### 1.2.2
79
+
80
+ - Use `pyproject.toml` via Hatch
81
+ - Added version information to app footer
82
+ - Fixed bug where bank name failed to show on 'Credit Account Details' page
83
+ - Improved JavaScript management for acquisition forms
84
+
85
+
86
+ ### 1.2.3
87
+
88
+ - Add currency symbol to the amount field(s)
89
+ - Allow subform fields to be removed from forms
90
+ - Change 'Vendor' field name to 'Merchant' for better generalizability
91
+ - Remove wildcard imports from the package source code
92
+ - Update form styles to be more browser compatible
93
+ - Impose boolean constraint on boolean/integer database fields
94
+ - Use metaclasses for database handler to avoid stacking classmethod and property (deprecated in Python 3.11)
95
+
96
+
97
+ ## 1.3.0
98
+
99
+ - Add a merchant field and tags to bank transactions
100
+ - Add an enhanced autocompletion for assigning transaction tags
101
+ - Add a settings page for allowing bank names to be changed (and eventually allowing passwords to be updated, among other functionality)
102
+ - Leverage SQLAlchemy 2.0 `DeclarativeBase` class and `declared_attr` decorators
103
+ - Simplify `Model` base to avoid explicitly defining column attributes
104
+ - Update the database backup script to work as part of the package
105
+ - Swap the README instructions for the 'About' page (and move the story to a separate page)
106
+ - Improve utilization of `pyroject.toml`
107
+ - Factor out the database handlers (now found in the Authanor package dependency)
108
+
109
+
110
+ ### 1.3.1
111
+
112
+ - Upgrade jQuery version to 3.7.0
113
+ - Use recommended `importlib` method for pytest
114
+ - Automatically use the transferring bank as the merchant in bank transfers
115
+ - For transfers between accounts at the same bank, do not show 'Withdrawal' or 'Deposit' headers; just mark as 'Transfer'
116
+
117
+
118
+ ## 1.4.0
119
+
120
+ - Add charts showing historical balances to bank account details page
121
+ - Show projected balances for bank transactions occurring in the future
122
+ - Allow users to remove the welcome block from the homepage
123
+ - Make use of `data-*` HTML tag attributes rather than ID parsing
124
+ - Prevent JavaScript errors on statement pages when buttons are non-existent
125
+ - Clean up transaction table overflow for transaction notes
126
+ - Generalize transaction forms using Jinja `super` blocks
127
+ - Convert JavaScript files to be named using kebab case
128
+ - Use Fuisce in place of Authanor for db/testing interfaces
129
+
130
+
131
+ ### 1.4.1
132
+
133
+ - Use local time for determining projected balances
134
+ - Show future credit statement payments as "scheduled" (and improve logic for displaying paid notices)
135
+ - Fix incorrect return type in `CreditStatementHandler.infer_statement` method
136
+ - Distinguish between inline code and "fenced" code using a Markdown library extension
137
+ - Use more `data-*` attributes for processing transaction IDs
138
+ - Fix bug where linked transactions did not use jQuery reference
139
+
140
+
141
+ ### 1.4.2
142
+
143
+ - Configure production mode via Gunicorn
144
+ - Standardize CLI usage messages
145
+ - Make browser usage optional in CLI run script
146
+
147
+
148
+ ### 1.4.3
149
+
150
+ - Reconfigure makefile for smoother installation
151
+ - Fix bug where transaction toggling failed on filter updates
152
+
153
+
154
+ ### 1.4.4
155
+
156
+ - Update date-to-timestamp conversion to be timezone invariant
157
+ - Fix database initialization for development mode
158
+ - Fix bug where credit card number was not displayed in card summary
159
+ - Use Beautiful Soup to make tests more robust
160
+
161
+
162
+ ### 1.4.5
163
+
164
+ - Fix bug where a transaction for a new statement was marked as invalid on entry
165
+ - Sort bank account displays by account type and last four digits
166
+ - Use SQLAlchemy 'selectin' loading mode for commonly loaded relationships
167
+ - Give tags a property for identifying their depth in the user's "tag tree"
168
+
169
+
170
+ ### 1.4.6
171
+
172
+ - Bump dependencies (including patching security vulnerability in gunicorn)
173
+ - Add a function for acquiring a statement and all of its transactions
174
+ - Add a convenience method to the statement handler for getting the preceding statement
175
+
176
+
177
+ ### 1.4.7
178
+
179
+ - Create custom error pages for error responses (400, 404, 500, etc.)
180
+ - Render the changelog and show it via the app
181
+ - Display bank account type subtotals on the bank account summaries page
182
+ - Set the current date as the default for transaction form inputs
183
+ - Use the `strict` argument to the built-in `zip` function to strengthen tests
184
+ - Refresh the table of transactions after making a payment on a credit card statement
185
+ - Use SVG to handle long values in account/statement summary boxes; fixes bugs in page rendering (long value overflow) and hover actions not happening because of conflicting overlap with the sidebar
186
+
187
+ <a name="bottom" id="bottom"></a>
monopyly/README.md CHANGED
@@ -27,12 +27,23 @@ The package requires a recent version of Python (3.9+).
27
27
 
28
28
  ## Getting started
29
29
 
30
- Once the package is properly installed, run the app from the command line (the default options should be sensible, but you may customize the host and port if necessary):
30
+ Once the package is properly installed, run the app in local mode from the command line (the default options should be sensible, but you may customize the host and port if necessary):
31
31
 
32
32
  ```
33
- $ monopyly development --browser [--host HOST] [--port PORT]
33
+ $ monopyly local --browser [--host HOST] [--port PORT]
34
34
  ```
35
35
 
36
+ Local mode indicates that the app is just going to be run using a locally hosted server, accessible to just your machine.
37
+ Other available modes are `development` and `production`, for those looking to either develop the application or host the application on a server.
38
+
39
+ <div class="warning">
40
+ <h5>Use your brain when choosing a mode!</h5>
41
+ <small>
42
+ If you intend to be the only user of the app, served just from your PC, local mode is fine; you will be served well-enough by the built-in Python server, and you do not need to configure secret keys for the application.
43
+ If you plan to host the application, however, <b>DO NOT</b> use local mode (or development mode) and run the app in production mode instead.
44
+ </small>
45
+ </div>
46
+
36
47
  By using the `--browser` option in development mode, this will open to an empty homepage with a welcome message.
37
48
 
38
49
  <img class="screenshot" src="monopyly/static/img/about/homepage.png" alt="user homepage" width="800px">
monopyly/__init__.py CHANGED
@@ -5,6 +5,7 @@ Run a development server for the Monopyly app.
5
5
  from flask import Flask
6
6
 
7
7
  from monopyly.config import DevelopmentConfig, ProductionConfig
8
+ from monopyly.core.errors import render_error_template
8
9
  from monopyly.database import SQLAlchemy, register_db_cli_commands
9
10
 
10
11
 
@@ -32,6 +33,7 @@ def create_app(test_config=None):
32
33
  def init_app(app):
33
34
  """Initialize the app."""
34
35
  register_blueprints(app)
36
+ register_errorhandlers(app)
35
37
  register_db_cli_commands(app)
36
38
 
37
39
 
@@ -57,3 +59,20 @@ def register_blueprints(app):
57
59
  app.register_blueprint(auth_bp)
58
60
  app.register_blueprint(banking_bp)
59
61
  app.register_blueprint(credit_bp)
62
+
63
+
64
+ def register_errorhandlers(app):
65
+ """Register error handlers with the app."""
66
+ handled_error_codes = [
67
+ 400,
68
+ 401,
69
+ 403,
70
+ 404,
71
+ 405,
72
+ 408,
73
+ 418,
74
+ # 425 -- not yet supported
75
+ 500,
76
+ ]
77
+ for code in handled_error_codes:
78
+ app.register_error_handler(code, render_error_template)
monopyly/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '1.4.5'
4
- __version_tuple__ = version_tuple = (1, 4, 5)
3
+ __version__ = version = '1.4.7'
4
+ __version_tuple__ = version_tuple = (1, 4, 7)
@@ -122,7 +122,7 @@ class BankAccountTypeHandler(
122
122
  "The current user is not authorized to manipulate "
123
123
  "this account type entry."
124
124
  )
125
- abort(404, abort_msg)
125
+ abort(403, abort_msg)
126
126
 
127
127
 
128
128
  class BankAccountHandler(
monopyly/cli/apps.py CHANGED
@@ -9,8 +9,17 @@ from .. import create_app
9
9
 
10
10
 
11
11
  class LocalApplication:
12
- """An object for running the application locally."""
12
+ """
13
+ An object for running the application locally.
13
14
 
15
+ This application object will run the Flask application using the
16
+ built-in Python server on localhost, just like the Flask development
17
+ mode. However, it will launch from port 5001 to avoid conflicting
18
+ with other Python servers that may attempt to run on the default
19
+ port 5000.
20
+ """
21
+
22
+ mode_name = "local"
14
23
  default_port = "5001"
15
24
  command = ["flask"]
16
25
 
@@ -20,7 +29,7 @@ class LocalApplication:
20
29
  self._port = port
21
30
  if options:
22
31
  raise NotImplementedError(
23
- "Options besides `host` and `port` are not handled in development mode."
32
+ f"Options besides `host` and `port` are not handled in {self.mode_name} mode."
24
33
  )
25
34
 
26
35
  def run(self):
@@ -34,14 +43,26 @@ class LocalApplication:
34
43
 
35
44
 
36
45
  class DevelopmentApplication(LocalApplication):
37
- """An object for running the application in development mode."""
46
+ """
47
+ An object for running the application in development mode.
48
+
49
+ This application object will run the Flask application using the
50
+ built-in Python server on localhost, just like the Flask development
51
+ mode.
52
+ """
38
53
 
54
+ mode_name = "development"
39
55
  default_port = "5000"
40
56
  command = LocalApplication.command + ["--debug"]
41
57
 
42
58
 
43
59
  class ProductionApplication(BaseApplication):
44
- """An object for running the application in production mode (via Gunicorn)."""
60
+ """
61
+ An object for running the application in production mode (via Gunicorn).
62
+
63
+ This application object will run the Flask application using a
64
+ Gunicorn server instead of the built-in Python server.
65
+ """
45
66
 
46
67
  default_port = "8000"
47
68
  _default_worker_count = (multiprocessing.cpu_count() * 2) + 1
@@ -3,6 +3,7 @@ General form constructions.
3
3
  """
4
4
 
5
5
  from abc import ABC, abstractmethod
6
+ from datetime import date
6
7
 
7
8
  from flask_wtf import FlaskForm
8
9
  from wtforms.fields import FieldList, FormField, SelectField, StringField, SubmitField
@@ -34,16 +35,6 @@ class EntryForm(FlaskForm, metaclass=AbstractEntryFormMixinMeta):
34
35
  """
35
36
  Generate a duplicate prepopulated form.
36
37
 
37
- Notes
38
- -----
39
- WTForms requires that a form be instantiated in order to be
40
- able to properly introspect fields. Because of this, this method
41
- will only return a duplicate form matching the type of the
42
- form instance used to call it. Using the form's process method
43
- will not properly handle enumeration of field lists, so it
44
- can not be used as a replacement for populating an existing
45
- form.
46
-
47
38
  Parameters
48
39
  ----------
49
40
  entry : database.models.Model
@@ -55,6 +46,16 @@ class EntryForm(FlaskForm, metaclass=AbstractEntryFormMixinMeta):
55
46
  form : EntryForm
56
47
  A duplicate form, prepopulated with the collected database
57
48
  information.
49
+
50
+ Notes
51
+ -----
52
+ WTForms requires that a form be instantiated in order to be
53
+ able to properly introspect fields. Because of this, this method
54
+ will only return a duplicate form matching the type of the
55
+ form instance used to call it. Using the form's process method
56
+ will not properly handle enumeration of field lists, so it
57
+ can not be used as a replacement for populating an existing
58
+ form.
58
59
  """
59
60
  data = self.gather_entry_data(entry)
60
61
  return self.__class__(data=data)
@@ -165,7 +166,9 @@ class TransactionForm(EntryForm):
165
166
  return data
166
167
 
167
168
  # Fields pertaining to the transaction
168
- transaction_date = DateField("Transaction Date", [DataRequired()])
169
+ transaction_date = DateField(
170
+ "Transaction Date", validators=[DataRequired()], default=date.today
171
+ )
169
172
  # Subtransactions should be defined as a `FieldList` in a subclass
170
173
  subtransactions = None
171
174
  submit = SubmitField("Save Transaction")
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
- def format_readme_as_html_template(readme_text):
14
- """Given the README text in Markdown, convert it to a renderable HTML template."""
15
- # Convert Markdown to HTML
16
- raw_html_readme = markdown.markdown(readme_text, extensions=["fenced_code"])
17
- # Replace README relative links with app relevant links
18
- html_readme = raw_html_readme.replace('src="monopyly/static', 'src="/static')
19
- # Format the HTML as a valid Jinja template
20
- html_readme_template = (
21
- '{% extends "layout.html" %}'
22
- "{% block title %}About{% endblock %}"
23
- "{% block content %}"
24
- ' <div id="readme" class="about">'
25
- f" {html_readme}"
26
- ' <div class="resource-links">'
27
- " <h2>Links</h2>"
28
- ' <p><a href="{{ url_for("core.story") }}">Story</a></p>'
29
- ' <p><a href="{{ url_for("core.credits") }}">Credits</a></p>'
30
- " </div>"
31
- " </div>"
32
- "{% endblock %}"
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
- return html_readme_template
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))
@@ -8,4 +8,4 @@ from flask import Blueprint
8
8
  bp = Blueprint("core", __name__)
9
9
 
10
10
  # Import routes after defining blueprint to avoid circular imports
11
- from . import context_processors, filters, routes
11
+ from . import context_processors, errors, filters, routes
@@ -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:
@@ -0,0 +1,9 @@
1
+ """
2
+ Tools for handling app errors.
3
+ """
4
+
5
+ from flask import render_template
6
+
7
+
8
+ def render_error_template(exception):
9
+ return render_template(f"core/errors/{exception.code}.html", exception=exception)
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 user
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 format_readme_as_html_template
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 = Path(__file__).parents[1] / "README.md"
57
- with readme_path.open(encoding="utf-8") as readme_file:
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)