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.
Files changed (51) hide show
  1. monopyly/CHANGELOG.md +195 -0
  2. monopyly/README.md +14 -3
  3. monopyly/__init__.py +20 -1
  4. monopyly/_version.py +2 -2
  5. monopyly/auth/actions.py +7 -2
  6. monopyly/banking/accounts.py +1 -1
  7. monopyly/banking/actions.py +51 -10
  8. monopyly/banking/routes.py +2 -1
  9. monopyly/cli/apps.py +51 -28
  10. monopyly/cli/{run.py → launch.py} +18 -10
  11. monopyly/common/forms/_forms.py +4 -1
  12. monopyly/core/actions.py +108 -21
  13. monopyly/core/blueprint.py +1 -1
  14. monopyly/core/context_processors.py +10 -0
  15. monopyly/core/errors.py +9 -0
  16. monopyly/core/filters.py +4 -2
  17. monopyly/core/routes.py +22 -9
  18. monopyly/credit/routes.py +9 -1
  19. monopyly/credit/transactions/_transactions.py +1 -1
  20. monopyly/static/css/style.css +166 -73
  21. monopyly/static/js/make-payment.js +4 -2
  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/core/credits.html +32 -0
  27. monopyly/templates/core/errors/400.html +8 -0
  28. monopyly/templates/core/errors/401.html +8 -0
  29. monopyly/templates/core/errors/403.html +8 -0
  30. monopyly/templates/core/errors/404.html +8 -0
  31. monopyly/templates/core/errors/405.html +8 -0
  32. monopyly/templates/core/errors/408.html +8 -0
  33. monopyly/templates/core/errors/418.html +8 -0
  34. monopyly/templates/core/errors/425.html +8 -0
  35. monopyly/templates/core/errors/500.html +8 -0
  36. monopyly/templates/core/errors/error.html +31 -0
  37. monopyly/templates/{profile.html → core/profile.html} +3 -1
  38. monopyly/templates/core/story.html +62 -0
  39. monopyly/templates/credit/statement_summary.html +7 -2
  40. monopyly/templates/credit/statements.html +7 -6
  41. monopyly/templates/layout.html +11 -2
  42. {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/METADATA +15 -4
  43. {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/RECORD +48 -36
  44. monopyly-1.4.8.dist-info/entry_points.txt +2 -0
  45. monopyly/templates/credits.html +0 -20
  46. monopyly/templates/story.html +0 -47
  47. monopyly-1.4.6.dist-info/entry_points.txt +0 -2
  48. /monopyly/templates/{index.html → core/index.html} +0 -0
  49. {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/WHEEL +0 -0
  50. {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/licenses/COPYING +0 -0
  51. {monopyly-1.4.6.dist-info → monopyly-1.4.8.dist-info}/licenses/LICENSE +0 -0
monopyly/CHANGELOG.md ADDED
@@ -0,0 +1,195 @@
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
+
188
+ ### 1.4.8
189
+
190
+ - Set username collection to be case insensitive
191
+ - Use Flask/Gunicorn APIs (rather than subprocess CLI calls) to launch the app
192
+ - Fix bug in the ordering of balances in the bank account balance charts for transactions on duplicate dates
193
+
194
+
195
+ <a name="bottom" id="bottom"></a>
monopyly/README.md CHANGED
@@ -22,17 +22,28 @@ To install the app, simply run
22
22
  $ pip install monopyly
23
23
  ```
24
24
 
25
- The package requires a recent version of Python (3.9+).
25
+ The package requires a recent version of Python (3.10+).
26
26
 
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
@@ -1,10 +1,11 @@
1
1
  """
2
- Run a development server for the Monopyly app.
2
+ Run the Monopyly app.
3
3
  """
4
4
 
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.6'
4
- __version_tuple__ = version_tuple = (1, 4, 6)
3
+ __version__ = version = '1.4.8'
4
+ __version_tuple__ = version_tuple = (1, 4, 8)
monopyly/auth/actions.py CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
 
4
4
  def get_username_and_password(form):
5
- """Get username and password from a form."""
6
- username = form["username"]
5
+ """
6
+ Get username and password from a form.
7
+
8
+ Get the username and password from the given form. Username should
9
+ be case insensitive.
10
+ """
11
+ username = form["username"].lower()
7
12
  password = form["password"]
8
13
  return username, password
@@ -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(
@@ -1,5 +1,7 @@
1
1
  """Module describing logical banking actions (to be used in routes)."""
2
2
 
3
+ from collections import UserList, namedtuple
4
+
3
5
  from ..common.utils import convert_date_to_midnight_timestamp
4
6
  from .accounts import BankAccountHandler, BankAccountTypeHandler
5
7
 
@@ -29,16 +31,55 @@ def get_balance_chart_data(transactions):
29
31
  Returns
30
32
  -------
31
33
  chart_data : list
32
- A list of sorted (x, y) pairs consisting of the Unix timestamp
33
- (in milliseconds) and the bank account balance.
34
+ A list containing (x, y) pairs, each consisting of the Unix
35
+ timestamp (in milliseconds) and the bank account balance.
36
+ """
37
+ return list(_BalanceChartData(transactions))
38
+
39
+
40
+ class _BalanceChartData(UserList):
34
41
  """
35
- chart_data = sorted(map(_make_transaction_balance_ordered_pair, transactions))
36
- return chart_data
42
+ A list of balances to be passed to a `chartist.js` chart constructor.
43
+
44
+ A special list-like object containing transaction data formatted for
45
+ use in a balance chart created by the `chartist.js` library. This
46
+ converts each transaction into an (x, y) pair consisting of a Unix
47
+ timestamp (in milleseconds) and a corresponding bank account
48
+ balance. For transactions occurring on the same day (the finest
49
+ granularity recorded by the Monopyly app), a slight offset is
50
+ added to each timestamp to guarantee a smooth representation in the
51
+ rendered chart.
52
+
53
+ Parameters
54
+ ----------
55
+ transactions : list
56
+ A list of transactions to be used for generating the chart data.
57
+ """
58
+
59
+ _DAILY_MILLISECONDS = 86_400_000
60
+ offset = 1
61
+ point = namedtuple("DataPoint", ["timestamp", "balance"])
62
+
63
+ def __init__(self, transactions):
64
+ super().__init__()
65
+ transaction_groups = self._group_transactions_by_date(transactions)
66
+ self._prepare_chart_data(transaction_groups)
37
67
 
68
+ @staticmethod
69
+ def _group_transactions_by_date(transactions):
70
+ date_groups = {}
71
+ for transaction in transactions:
72
+ group = date_groups.setdefault(transaction.transaction_date, [])
73
+ group.append(transaction)
74
+ return date_groups
38
75
 
39
- def _make_transaction_balance_ordered_pair(transaction):
40
- # Create an ordered pair of date (timestamp) and account balance
41
- timestamp = convert_date_to_midnight_timestamp(
42
- transaction.transaction_date, milliseconds=True
43
- )
44
- return timestamp, transaction.balance
76
+ def _prepare_chart_data(self, transaction_groups):
77
+ # Assign chart data to the list as tuples, adding offsets for duplicated dates
78
+ for transaction_date, transaction_group in transaction_groups.items():
79
+ base_timestamp = convert_date_to_midnight_timestamp(
80
+ transaction_date, milliseconds=True
81
+ )
82
+ offset = self._DAILY_MILLISECONDS / len(transaction_group)
83
+ for i, transaction in enumerate(transaction_group):
84
+ adjusted_timestamp = base_timestamp + (i * offset)
85
+ self.data.append((adjusted_timestamp, transaction.balance))
@@ -79,7 +79,8 @@ def load_account_details(account_id):
79
79
  "banking/account_page.html",
80
80
  account=account,
81
81
  account_transactions=transactions[:100],
82
- chart_data=get_balance_chart_data(transactions),
82
+ # Reverse the chart transactions to be chronologically ascending
83
+ chart_data=get_balance_chart_data(reversed(transactions)),
83
84
  )
84
85
 
85
86
 
monopyly/cli/apps.py CHANGED
@@ -9,65 +9,88 @@ 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
- command = ["flask"]
24
+ _debug = None
16
25
 
17
26
  def __init__(self, host=None, port=None, **options):
18
27
  """Initialize the application in development mode."""
19
28
  self._host = host
20
- self._port = port
29
+ self._port = port or self.default_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
  )
34
+ self.application = create_app()
25
35
 
26
36
  def run(self):
27
37
  """Run the Monopyly application in development mode."""
28
- instruction = self.command + ["run"]
29
- if self._host:
30
- instruction += ["--host", self._host]
31
- if self._port:
32
- instruction += ["--port", self._port]
33
- server = subprocess.Popen(instruction)
38
+ self.application.run(
39
+ host=self._host,
40
+ port=self._port,
41
+ debug=self._debug,
42
+ )
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
 
39
- default_port = "5000"
40
- command = LocalApplication.command + ["--debug"]
54
+ mode_name = "development"
55
+ default_port = None # traditionally 5000
56
+ _debug = True
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).
45
62
 
46
- default_port = "8000"
63
+ This application object will run the Flask application using a
64
+ Gunicorn server instead of the built-in Python server.
65
+ """
66
+
67
+ default_port = None # traditionally 8000
47
68
  _default_worker_count = (multiprocessing.cpu_count() * 2) + 1
48
69
 
49
70
  def __init__(self, host=None, port=None, **options):
50
71
  """Initialize the application in production mode."""
51
- options["bind"] = self._parse_binding(host, port, options.get("bind"))
52
- options.setdefault("workers", self._default_worker_count)
72
+ if port and not host:
73
+ raise ValueError("A host must be specified when the port is given.")
74
+ self._host = host
75
+ self._port = port or self.default_port
53
76
  self.options = options
77
+ self.options["bind"] = self._determine_binding(options.get("bind"))
78
+ self.options.setdefault("workers", self._default_worker_count)
54
79
  self.application = create_app()
55
80
  super().__init__()
56
81
 
57
- @staticmethod
58
- def _parse_binding(host, port, bind_option):
82
+ def _determine_binding(self, bind_option):
59
83
  # Parse any socket binding options
60
- if (host or port) and bind_option:
84
+ if self._host and bind_option:
61
85
  raise ValueError(
62
- "Neither `host` nor `port` parameters can be specified if the "
63
- "`bind` option is given."
86
+ "The `host` may not be specified directly if the `bind` option is used."
64
87
  )
65
- bind_values = []
66
- if host:
67
- bind_values.append(host)
68
- if port:
69
- bind_values.append(port)
70
- return bind if (bind := ":".join(bind_values)) else bind_option
88
+ if self._host:
89
+ bind_values = [self._host]
90
+ if self._port:
91
+ bind_values.append(self._port)
92
+ bind_option = ":".join(bind_values)
93
+ return bind_option
71
94
 
72
95
  def load_config(self):
73
96
  config = {
@@ -16,26 +16,34 @@ from rich.console import Console
16
16
 
17
17
  from .apps import DevelopmentApplication, LocalApplication, ProductionApplication
18
18
 
19
- # Set the Flask environment variable
20
- os.environ["FLASK_APP"] = "monopyly"
21
19
 
22
-
23
- def main():
24
- args = parse_arguments()
25
- app_launcher = Launcher(args.mode, host=args.host, port=args.port)
20
+ def main(mode, host=None, port=None, backup=False, browser=False):
21
+ app_launcher = Launcher(mode, host=host, port=port)
26
22
  # Initialize the database and run the app
27
23
  app_launcher.initialize_database()
28
- if args.backup:
24
+ if backup:
29
25
  app_launcher.backup_database()
30
26
  app_launcher.launch()
31
- if args.mode in ("development", "local"):
27
+ if mode in ("development", "local"):
32
28
  # Enable browser viewing in development mode
33
- if args.browser:
29
+ if browser:
34
30
  app_launcher.open_browser(delay=1)
35
31
  # Wait for the exit command to stop
36
32
  app_launcher.wait_for_exit()
37
33
 
38
34
 
35
+ def main_cli():
36
+ """Run the app as a command line program."""
37
+ args = parse_arguments()
38
+ main(
39
+ args.mode,
40
+ host=args.host,
41
+ port=args.port,
42
+ backup=args.backup,
43
+ browser=args.browser,
44
+ )
45
+
46
+
39
47
  def parse_arguments():
40
48
  parser = argparse.ArgumentParser(description=__doc__)
41
49
  parser.add_argument("--host", help="the host address where the app will be run")
@@ -121,4 +129,4 @@ class Launcher:
121
129
 
122
130
 
123
131
  if __name__ == "__main__":
124
- main()
132
+ main_cli()
@@ -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
@@ -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")