monopyly 1.7.2__tar.gz → 1.7.3__tar.gz
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-1.7.2 → monopyly-1.7.3}/PKG-INFO +7 -7
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/CHANGELOG.md +7 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/_version.py +2 -2
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/forms.py +13 -10
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/common/forms/_forms.py +3 -3
- monopyly-1.7.3/monopyly/common/forms/fields.py +47 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/common/utils.py +1 -2
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/forms.py +12 -12
- {monopyly-1.7.2 → monopyly-1.7.3}/pyproject.toml +6 -6
- monopyly-1.7.2/monopyly/common/forms/fields.py +0 -101
- monopyly-1.7.2/monopyly/common/forms/validators.py +0 -51
- {monopyly-1.7.2 → monopyly-1.7.3}/.gitignore +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/COPYING +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/LICENSE +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/README.md +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/README.md +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/__init__.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/analytics/actions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/analytics/blueprint.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/analytics/routes.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/analytics/tools.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/auth/actions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/auth/blueprint.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/auth/routes.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/auth/tools.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/accounts.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/actions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/banks.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/blueprint.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/filters.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/routes.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/banking/transactions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/common/forms/__init__.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/common/forms/utils.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/common/transactions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/actions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/blueprint.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/context_processors.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/errors.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/filters.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/internal_transactions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/core/routes.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/accounts.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/actions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/blueprint.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/cards.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/routes.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/statements.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/transactions/__init__.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/transactions/_transactions.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/transactions/activity/__init__.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/transactions/activity/data.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/transactions/activity/parser.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/credit/transactions/activity/reconciliation.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/database/__init__.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/database/models.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/database/preloads.sql +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/database/schema.sql +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/database/views.sql +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/scripts/screenshot_application.py +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/chartist-1.5.0.min.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/css/style.css +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/browserconfig.xml +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-114.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-120.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-144.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-150.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-152.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-16.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-160.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-180.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-192.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-310.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-32.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-57.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-60.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-64.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-70.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-72.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-76.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon-96.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon.ico +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/favicon/favicon.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/bank-account-details.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/bank-account-summaries.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/bank-accounts.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/credit-account-details.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/credit-statement-details.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/credit-transactions.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/homepage-user.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/homepage.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/about/statement-details.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/cards/chase-card.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/cards/discover-card.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/cards/new-card.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/cards/template-card.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/arrow-down.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/arrow-left.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/arrow-up.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/checkmark.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/delete-orange-thick.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/delete-orange.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/delete-thick.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/delete.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/edit.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/link.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/minus-thick.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/minus.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/plus-thick.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/plus.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/refresh.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/save.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/sort-asc.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/sort-desc.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/statement-pair.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/statement-thick.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/statement.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/icons/x-thick.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/img/statement.png +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/jquery-3.7.1.min.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/add-subtransaction.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/add-transfer.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/autocomplete-transaction.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/bind-tag-actions.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/create-balance-chart.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/create-category-chart.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/create-tag-stats-chart.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/define-filter.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/display-new-account-type-inputs.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/display-new-bank-inputs.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/display-new-credit-account-inputs.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/expand-bank-account.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/expand-bank.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/expand-transaction.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/flip-card.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/hide-homepage-block.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/highlight-discrepant-transactions.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/infer-card.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/infer-statement.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/load-more-transactions.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/make-payment.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/expand-box-row.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/expand-transaction.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/form-suggestions.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/manage-acquisition-form.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/manage-overlays.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/manage-subforms.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/update-database-widget.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/modules/update-display-request.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/show-credit-activity-loader.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/show-linked-transaction.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/toggle-navigation.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-account-statement-parameters.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-bank-name.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-card-status.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-statement-parameters.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-statements-display.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-tag-statistics.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/update-transactions-display.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/use-suggested-amount.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/static/js/use-suggested-merchant.js +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/analytics/net_worth.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/analytics/tag_statistics/tag_selection.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/analytics/tag_statistics/tag_statistics.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/analytics/tag_statistics/tag_statistics_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/auth/change_password.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/auth/login.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/auth/register.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/account_form/account_form.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/account_form/account_form_page_new.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/account_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/account_summaries.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/account_summaries_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/account_summary.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/accounts_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transaction_form/bank_info_form.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transaction_form/transaction_form.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transaction_form/transaction_form_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transaction_form/transaction_form_page_new.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transaction_form/transaction_form_page_update.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transaction_form/transfer_form.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transactions_table/condensed_row_content.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transactions_table/expanded_row_content.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transactions_table/table.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transactions_table/transaction_field_titles.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/banking/transactions_table/transactions.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/form_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/tag_tree.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/tags_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transaction_form/subform.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transaction_form/subtransaction_subform.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transaction_form/transaction_form_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/linked_bank_transaction.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/linked_credit_transaction.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/linked_transaction_overlay.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/subtransactions.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/table.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/transaction_condensed.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/transaction_expanded.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/common/transactions_table/transactions.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/credits.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/400.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/401.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/403.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/404.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/405.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/408.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/418.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/425.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/500.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/errors/error.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/index.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/profile.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/core/story.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/account_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/card_form/card_form.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/card_form/card_form_page_new.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/card_form/transfer_statement_inquiry.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/card_graphic/card_back.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/card_graphic/card_front.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/card_submission_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/cards.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/cards_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_reconciliation/discrepant_records.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_reconciliation/summary.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_reconciliation/unrecorded_activities.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statement_summary.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statements.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/statements_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transaction_form/transaction_form.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transaction_form/transaction_form_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transaction_form/transaction_form_page_new.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transaction_form/transaction_form_page_update.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transaction_submission_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transactions_page.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transactions_table/condensed_row_content.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transactions_table/expanded_row_content.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transactions_table/table.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transactions_table/transaction_field_titles.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/credit/transactions_table/transactions.html +0 -0
- {monopyly-1.7.2 → monopyly-1.7.3}/monopyly/templates/layout.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: monopyly
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
4
4
|
Summary: A homemade personal finance manager.
|
|
5
5
|
Project-URL: Download, https://pypi.org/project/monopyly
|
|
6
6
|
Project-URL: Homepage, http://monopyly.com
|
|
@@ -22,16 +22,16 @@ Classifier: Programming Language :: Python
|
|
|
22
22
|
Classifier: Topic :: Office/Business :: Financial
|
|
23
23
|
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
24
24
|
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
25
|
-
Requires-Python: >=3.
|
|
26
|
-
Requires-Dist: dry-foundation==2.
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: dry-foundation==2.2.0
|
|
27
27
|
Requires-Dist: flask-wtf==1.3.0
|
|
28
28
|
Requires-Dist: flask==3.1.3
|
|
29
29
|
Requires-Dist: gunicorn==26.2.0
|
|
30
|
-
Requires-Dist: markdown==3.
|
|
31
|
-
Requires-Dist: nltk==3.10.
|
|
32
|
-
Requires-Dist: python-dateutil==
|
|
30
|
+
Requires-Dist: markdown==3.11.2
|
|
31
|
+
Requires-Dist: nltk==3.10.4
|
|
32
|
+
Requires-Dist: python-dateutil==3.0.0
|
|
33
33
|
Requires-Dist: rich==15.0.0
|
|
34
|
-
Requires-Dist: sqlalchemy==2.
|
|
34
|
+
Requires-Dist: sqlalchemy==2.1.6
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
36
36
|
|
|
37
37
|
<div id="header">
|
|
@@ -280,4 +280,11 @@
|
|
|
280
280
|
- Prevent summation of statement balances returning `None` (continued)
|
|
281
281
|
|
|
282
282
|
|
|
283
|
+
### 1.7.3
|
|
284
|
+
|
|
285
|
+
- Update timezone handling via datetime `tzinfo`
|
|
286
|
+
- Rely on foundations package for (universal) form capabilities
|
|
287
|
+
- Bump dependencies and minimum Python version (3.11)
|
|
288
|
+
|
|
289
|
+
|
|
283
290
|
<a name="bottom" id="bottom"></a>
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.7.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 7,
|
|
31
|
+
__version__ = version = '1.7.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 7, 3)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
Generate banking forms for the user to complete.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from dry_foundation.forms.fields import CustomDatabaseSelectField
|
|
5
6
|
from wtforms.fields import BooleanField, FieldList, FormField, SubmitField
|
|
6
7
|
from wtforms.validators import DataRequired, Optional
|
|
7
8
|
|
|
8
9
|
from ..common.forms import AcquisitionSubform, EntryForm, EntrySubform, TransactionForm
|
|
9
|
-
from ..common.forms.fields import
|
|
10
|
-
CustomChoiceSelectField,
|
|
11
|
-
LastFourDigitsField,
|
|
12
|
-
StringField,
|
|
13
|
-
)
|
|
10
|
+
from ..common.forms.fields import LastFourDigitsField, StringField
|
|
14
11
|
from ..common.forms.utils import Autocompleter
|
|
15
12
|
from ..database.models import (
|
|
16
13
|
Bank,
|
|
@@ -25,7 +22,7 @@ from .accounts import BankAccountRepository, BankAccountTypeRepository
|
|
|
25
22
|
from .banks import BankRepository
|
|
26
23
|
|
|
27
24
|
|
|
28
|
-
class BankSelectField(
|
|
25
|
+
class BankSelectField(CustomDatabaseSelectField):
|
|
29
26
|
"""Bank field that uses the database to prepare field choices."""
|
|
30
27
|
|
|
31
28
|
_db_repo = BankRepository
|
|
@@ -38,8 +35,11 @@ class BankSelectField(CustomChoiceSelectField):
|
|
|
38
35
|
display_name = bank.bank_name
|
|
39
36
|
return display_name
|
|
40
37
|
|
|
38
|
+
def _format_new_choice(self):
|
|
39
|
+
return f"New {self.label.text.lower()}"
|
|
40
|
+
|
|
41
41
|
|
|
42
|
-
class BankAccountTypeSelectField(
|
|
42
|
+
class BankAccountTypeSelectField(CustomDatabaseSelectField):
|
|
43
43
|
"""Account type field that uses the database to prepare field choices."""
|
|
44
44
|
|
|
45
45
|
_db_repo = BankAccountTypeRepository
|
|
@@ -55,6 +55,9 @@ class BankAccountTypeSelectField(CustomChoiceSelectField):
|
|
|
55
55
|
display_name += f" ({account_type.type_common_name})"
|
|
56
56
|
return display_name
|
|
57
57
|
|
|
58
|
+
def _format_new_choice(self):
|
|
59
|
+
return f"New {self.label.text.lower()}"
|
|
60
|
+
|
|
58
61
|
|
|
59
62
|
class BankSubform(AcquisitionSubform):
|
|
60
63
|
"""Form to input/edit bank identification."""
|
|
@@ -83,7 +86,7 @@ class BankSubform(AcquisitionSubform):
|
|
|
83
86
|
"""Gather data for the form from the given database entry."""
|
|
84
87
|
if isinstance(entry, Bank):
|
|
85
88
|
data = {
|
|
86
|
-
"bank_id": entry.id,
|
|
89
|
+
"bank_id": str(entry.id),
|
|
87
90
|
"bank_name": entry.bank_name,
|
|
88
91
|
}
|
|
89
92
|
else:
|
|
@@ -135,8 +138,8 @@ class BankAccountForm(EntryForm):
|
|
|
135
138
|
bank = self.bank_info.get_bank()
|
|
136
139
|
account_type = self.account_type_info.get_account_type()
|
|
137
140
|
account_data = {
|
|
138
|
-
"bank_id": bank.id,
|
|
139
|
-
"account_type_id": account_type.id,
|
|
141
|
+
"bank_id": str(bank.id),
|
|
142
|
+
"account_type_id": str(account_type.id),
|
|
140
143
|
"last_four_digits": self["last_four_digits"].data,
|
|
141
144
|
"active": self["active"].data,
|
|
142
145
|
}
|
|
@@ -6,6 +6,7 @@ from abc import abstractmethod
|
|
|
6
6
|
from datetime import date
|
|
7
7
|
|
|
8
8
|
from dry_foundation.forms import AbstractFlaskForm, FlaskSubform
|
|
9
|
+
from dry_foundation.forms.fields import CustomDatabaseSelectField
|
|
9
10
|
from wtforms.fields import StringField, SubmitField
|
|
10
11
|
from wtforms.validators import DataRequired
|
|
11
12
|
|
|
@@ -102,15 +103,14 @@ class AcquisitionSubform(EntrySubform):
|
|
|
102
103
|
def _produce_entry_from_field(self, form_entry_id_field_name):
|
|
103
104
|
"""Produce an entry matching an ID field in the subform data."""
|
|
104
105
|
form_entry_id_field = getattr(self, form_entry_id_field_name)
|
|
105
|
-
form_entry_id = int(form_entry_id_field.data)
|
|
106
106
|
# If the entry is a new entry, create it from the form data
|
|
107
|
-
if
|
|
107
|
+
if form_entry_id_field.data == CustomDatabaseSelectField.NEW:
|
|
108
108
|
data = self._prepare_mapping()
|
|
109
109
|
entry = self._db_repo.add_entry(**data)
|
|
110
110
|
# Set the form field to match the newly added value
|
|
111
111
|
form_entry_id_field.data = entry.id
|
|
112
112
|
else:
|
|
113
|
-
entry = self._db_repo.get_entry(
|
|
113
|
+
entry = self._db_repo.get_entry(int(form_entry_id_field.data))
|
|
114
114
|
return entry
|
|
115
115
|
|
|
116
116
|
@abstractmethod
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General form constructions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dry_foundation.forms.fields import StrippedFieldMixin
|
|
6
|
+
from dry_foundation.forms.validators import NumeralsOnly
|
|
7
|
+
from wtforms import fields as wtforms_fields
|
|
8
|
+
from wtforms.validators import Length
|
|
9
|
+
from wtforms.widgets import NumberInput
|
|
10
|
+
|
|
11
|
+
from ..utils import parse_date
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DateField(wtforms_fields.DateField):
|
|
15
|
+
"""A date field with better parsing abilities than the WTForms default."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, filters=(), **kwargs):
|
|
18
|
+
# The date field should normally handle date conversion, but this
|
|
19
|
+
# filter remains as a backup for browsers not supporting locales
|
|
20
|
+
if parse_date not in filters:
|
|
21
|
+
filters = list(filters)
|
|
22
|
+
filters.append(parse_date)
|
|
23
|
+
super().__init__(*args, filters=filters, **kwargs)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CurrencyField(wtforms_fields.DecimalField):
|
|
27
|
+
"""A decimal field with currency-specific customizations."""
|
|
28
|
+
|
|
29
|
+
widget = NumberInput(step=0.01)
|
|
30
|
+
|
|
31
|
+
def __init__(self, *args, filters=(), **kwargs):
|
|
32
|
+
filters = list(filters)
|
|
33
|
+
filters.append(lambda x: float(round(x, 2)) if x else None)
|
|
34
|
+
super().__init__(*args, filters=filters, places=2, **kwargs)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class StringField(StrippedFieldMixin, wtforms_fields.StringField):
|
|
38
|
+
"""A custom string field."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class LastFourDigitsField(StringField):
|
|
42
|
+
"""A custom field for collecting the last four digits of cards/accounts."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, *args, validators=(), **kwargs):
|
|
45
|
+
validators = list(validators)
|
|
46
|
+
validators.extend([Length(4, 4), NumeralsOnly()])
|
|
47
|
+
super().__init__(*args, validators=validators, **kwargs)
|
|
@@ -4,7 +4,6 @@ General utility objects.
|
|
|
4
4
|
|
|
5
5
|
import datetime
|
|
6
6
|
from collections import Counter
|
|
7
|
-
from datetime import timezone
|
|
8
7
|
|
|
9
8
|
from dateutil.relativedelta import relativedelta
|
|
10
9
|
|
|
@@ -128,7 +127,7 @@ def convert_date_to_midnight_timestamp(date, milliseconds=False):
|
|
|
128
127
|
The timestamp corresponding to the given date (at midnight UTC).
|
|
129
128
|
"""
|
|
130
129
|
midnight = datetime.datetime.min.time()
|
|
131
|
-
utc_datetime = datetime.datetime.combine(date, midnight, tzinfo=
|
|
130
|
+
utc_datetime = datetime.datetime.combine(date, midnight, tzinfo=datetime.UTC)
|
|
132
131
|
timestamp = int(utc_datetime.timestamp())
|
|
133
132
|
if milliseconds:
|
|
134
133
|
timestamp *= 1000
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Generate credit card forms for the user to complete.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from dry_foundation.forms.fields import CustomDatabaseSelectField
|
|
5
6
|
from flask import abort
|
|
6
7
|
from wtforms.fields import (
|
|
7
8
|
BooleanField,
|
|
@@ -16,11 +17,7 @@ from wtforms.validators import DataRequired, Optional
|
|
|
16
17
|
|
|
17
18
|
from ..banking.forms import BankSubform
|
|
18
19
|
from ..common.forms import AcquisitionSubform, EntryForm, EntrySubform, TransactionForm
|
|
19
|
-
from ..common.forms.fields import
|
|
20
|
-
CustomChoiceSelectField,
|
|
21
|
-
LastFourDigitsField,
|
|
22
|
-
StringField,
|
|
23
|
-
)
|
|
20
|
+
from ..common.forms.fields import LastFourDigitsField, StringField
|
|
24
21
|
from ..common.forms.utils import Autocompleter
|
|
25
22
|
from ..database.models import (
|
|
26
23
|
Bank,
|
|
@@ -38,7 +35,7 @@ from .transactions import CreditTransactionRepository
|
|
|
38
35
|
from .transactions.activity.reconciliation import ActivityMatchmaker
|
|
39
36
|
|
|
40
37
|
|
|
41
|
-
class CreditAccountSelectField(
|
|
38
|
+
class CreditAccountSelectField(CustomDatabaseSelectField):
|
|
42
39
|
"""Account field that uses the database to prepare field choices."""
|
|
43
40
|
|
|
44
41
|
_db_repo = CreditAccountRepository
|
|
@@ -55,6 +52,9 @@ class CreditAccountSelectField(CustomChoiceSelectField):
|
|
|
55
52
|
display_name = f"{bank_name} (cards: {', '.join(digits)})"
|
|
56
53
|
return display_name
|
|
57
54
|
|
|
55
|
+
def _format_new_choice(self):
|
|
56
|
+
return f"New {self.label.text.lower()}"
|
|
57
|
+
|
|
58
58
|
|
|
59
59
|
class CreditCardForm(EntryForm):
|
|
60
60
|
"""Form to input/edit credit cards."""
|
|
@@ -76,13 +76,13 @@ class CreditCardForm(EntryForm):
|
|
|
76
76
|
|
|
77
77
|
Notes
|
|
78
78
|
-----
|
|
79
|
-
Unset values on the ``
|
|
79
|
+
Unset values on the ``CustomDatabaseSelectField`` will take a
|
|
80
80
|
default value that will be considered to be invalid. They
|
|
81
81
|
must be replaced (after processing) with the inferred value.
|
|
82
82
|
"""
|
|
83
|
-
if self.bank_info.bank_id.data ==
|
|
83
|
+
if self.bank_info.bank_id.data == self.bank_info.bank_id.UNSET:
|
|
84
84
|
account = self.get_account()
|
|
85
|
-
self.bank_info.bank_id.data = account.bank.id
|
|
85
|
+
self.bank_info.bank_id.data = str(account.bank.id)
|
|
86
86
|
return super().validate(extra_validators=extra_validators)
|
|
87
87
|
|
|
88
88
|
def get_account(self):
|
|
@@ -93,7 +93,7 @@ class CreditCardForm(EntryForm):
|
|
|
93
93
|
bank = self.bank_info.get_bank()
|
|
94
94
|
# Mapping must match format for `credit_accounts` database table
|
|
95
95
|
account_data = {
|
|
96
|
-
"bank_id": bank.id,
|
|
96
|
+
"bank_id": str(bank.id),
|
|
97
97
|
"statement_issue_day": self.statement_issue_day.data,
|
|
98
98
|
"statement_due_day": self.statement_due_day.data,
|
|
99
99
|
}
|
|
@@ -103,7 +103,7 @@ class CreditCardForm(EntryForm):
|
|
|
103
103
|
"""Gather data for the form from the given database entry."""
|
|
104
104
|
if isinstance(entry, CreditAccount):
|
|
105
105
|
data = {
|
|
106
|
-
"account_id": entry.id,
|
|
106
|
+
"account_id": str(entry.id),
|
|
107
107
|
"statement_issue_day": entry.statement_issue_day,
|
|
108
108
|
"statement_due_day": entry.statement_due_day,
|
|
109
109
|
}
|
|
@@ -126,7 +126,7 @@ class CreditCardForm(EntryForm):
|
|
|
126
126
|
"""Produce a dictionary corresponding to a database card."""
|
|
127
127
|
account = self.account_info.get_account()
|
|
128
128
|
card_data = {
|
|
129
|
-
"account_id": account.id,
|
|
129
|
+
"account_id": str(account.id),
|
|
130
130
|
"last_four_digits": self.last_four_digits.data,
|
|
131
131
|
"active": self.active.data,
|
|
132
132
|
}
|
|
@@ -9,17 +9,17 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
description = 'A homemade personal finance manager.'
|
|
11
11
|
license = { text = 'GNU GPLv3' }
|
|
12
|
-
requires-python = '>=3.
|
|
12
|
+
requires-python = '>=3.11'
|
|
13
13
|
dependencies = [
|
|
14
|
-
'dry-foundation==2.
|
|
14
|
+
'dry-foundation==2.2.0',
|
|
15
15
|
'Flask==3.1.3',
|
|
16
16
|
'Flask-WTF==1.3.0',
|
|
17
17
|
'gunicorn==26.2.0',
|
|
18
|
-
'Markdown==3.
|
|
19
|
-
'nltk==3.10.
|
|
20
|
-
'python-dateutil==
|
|
18
|
+
'Markdown==3.11.2',
|
|
19
|
+
'nltk==3.10.4',
|
|
20
|
+
'python-dateutil==3.0.0',
|
|
21
21
|
'rich==15.0.0',
|
|
22
|
-
'SQLAlchemy==2.
|
|
22
|
+
'SQLAlchemy==2.1.6',
|
|
23
23
|
]
|
|
24
24
|
keywords = [
|
|
25
25
|
'Finance',
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
General form constructions.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from abc import ABC, abstractmethod
|
|
6
|
-
|
|
7
|
-
from wtforms import fields as wtforms_fields
|
|
8
|
-
from wtforms.validators import Length
|
|
9
|
-
from wtforms.widgets import NumberInput
|
|
10
|
-
|
|
11
|
-
from ..utils import parse_date
|
|
12
|
-
from .validators import NumeralsOnly, SelectionNotBlank
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class DateField(wtforms_fields.DateField):
|
|
16
|
-
"""A date field with better parsing abilities than the WTForms default."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, *args, filters=(), **kwargs):
|
|
19
|
-
# The date field should normally handle date conversion, but this
|
|
20
|
-
# filter remains as a backup for browsers not supporting locales
|
|
21
|
-
if parse_date not in filters:
|
|
22
|
-
filters = list(filters)
|
|
23
|
-
filters.append(parse_date)
|
|
24
|
-
super().__init__(*args, filters=filters, **kwargs)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class CurrencyField(wtforms_fields.DecimalField):
|
|
28
|
-
"""A decimal field with currency-specific customizations."""
|
|
29
|
-
|
|
30
|
-
widget = NumberInput(step=0.01)
|
|
31
|
-
|
|
32
|
-
def __init__(self, *args, filters=(), **kwargs):
|
|
33
|
-
filters = list(filters)
|
|
34
|
-
filters.append(lambda x: float(round(x, 2)) if x else None)
|
|
35
|
-
super().__init__(*args, filters=filters, places=2, **kwargs)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class StringField(wtforms_fields.StringField):
|
|
39
|
-
"""A custom string field."""
|
|
40
|
-
|
|
41
|
-
def __init__(self, *args, filters=(), **kwargs):
|
|
42
|
-
filters = list(filters)
|
|
43
|
-
filters.append(lambda x: x.strip() if x else None)
|
|
44
|
-
super().__init__(*args, filters=filters, **kwargs)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class LastFourDigitsField(StringField):
|
|
48
|
-
"""A custom field for collecting the last four digits of cards/accounts."""
|
|
49
|
-
|
|
50
|
-
def __init__(self, *args, validators=(), **kwargs):
|
|
51
|
-
validators = list(validators)
|
|
52
|
-
validators.extend([Length(4, 4), NumeralsOnly()])
|
|
53
|
-
super().__init__(*args, validators=validators, **kwargs)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class CustomChoiceSelectField(wtforms_fields.SelectField, ABC):
|
|
57
|
-
"""A select field that can auto-prepare choices."""
|
|
58
|
-
|
|
59
|
-
def __init__(self, label=None, validators=None, coerce=int, **kwargs):
|
|
60
|
-
if not validators:
|
|
61
|
-
validators = [SelectionNotBlank()]
|
|
62
|
-
super().__init__(label=label, validators=validators, coerce=int, **kwargs)
|
|
63
|
-
self.prepare_field_choices()
|
|
64
|
-
|
|
65
|
-
def process_data(self, value):
|
|
66
|
-
"""Process incoming data, and set the field default value."""
|
|
67
|
-
value = -1 if value is None else value
|
|
68
|
-
super().process_data(value)
|
|
69
|
-
|
|
70
|
-
@property
|
|
71
|
-
@abstractmethod
|
|
72
|
-
def _db_repo(self):
|
|
73
|
-
raise NotImplementedError("Define the attribute in a subclass.")
|
|
74
|
-
|
|
75
|
-
def prepare_field_choices(self):
|
|
76
|
-
"""
|
|
77
|
-
Prepare choices for a field in the form using the database.
|
|
78
|
-
|
|
79
|
-
Using a reference to a database repository, this method queries the
|
|
80
|
-
database for entries belonging to the user, and then uses those
|
|
81
|
-
entries to populate the list of choices that may be selected
|
|
82
|
-
for the field value.
|
|
83
|
-
"""
|
|
84
|
-
# Collect all available user entries
|
|
85
|
-
entries = self._db_repo.get_entries()
|
|
86
|
-
# Set default choice values
|
|
87
|
-
self.choices = [(-1, "-"), (0, f"New {self.label.text.lower()}")]
|
|
88
|
-
# Set the user choices (for consistency, arbitrarily sort by entry ID)
|
|
89
|
-
for entry in sorted(entries, key=lambda entry: entry.id):
|
|
90
|
-
display_name = self._format_choice(entry)
|
|
91
|
-
self.choices.insert(-1, (entry.id, display_name))
|
|
92
|
-
|
|
93
|
-
@abstractmethod
|
|
94
|
-
def _format_choice(self, entry):
|
|
95
|
-
"""
|
|
96
|
-
Format a database entry field to display as a form field choice.
|
|
97
|
-
|
|
98
|
-
Accepts an entry returned by the database repository, and extract a
|
|
99
|
-
name to be displayed as a choice in the list of choices.
|
|
100
|
-
"""
|
|
101
|
-
raise NotImplementedError("Define the formatting for a choice in a subclass.")
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Commonly shared form validators.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from wtforms.validators import ValidationError
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class NumeralsOnly:
|
|
9
|
-
"""
|
|
10
|
-
Validates text contains only numerals.
|
|
11
|
-
|
|
12
|
-
Parameters
|
|
13
|
-
----------
|
|
14
|
-
message : str
|
|
15
|
-
Error message to raise in case of a validation error.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, message=None):
|
|
19
|
-
if not message:
|
|
20
|
-
message = "Field can only contain numerals."
|
|
21
|
-
self.message = message
|
|
22
|
-
|
|
23
|
-
def __call__(self, form, field):
|
|
24
|
-
try:
|
|
25
|
-
int(field.data)
|
|
26
|
-
except ValueError:
|
|
27
|
-
raise ValidationError(self.message)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class SelectionNotBlank:
|
|
31
|
-
"""
|
|
32
|
-
Validates that a selection is not a blank submission.
|
|
33
|
-
|
|
34
|
-
Parameters
|
|
35
|
-
----------
|
|
36
|
-
blank :
|
|
37
|
-
The value representing a blank selection. The default is the
|
|
38
|
-
integer `-1`.
|
|
39
|
-
message : str
|
|
40
|
-
Error message to raise in case of a validation error.
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
def __init__(self, blank=-1, message=None):
|
|
44
|
-
self.blank = blank
|
|
45
|
-
if not message:
|
|
46
|
-
message = "A selection must be made."
|
|
47
|
-
self.message = message
|
|
48
|
-
|
|
49
|
-
def __call__(self, form, field):
|
|
50
|
-
if field.data == self.blank:
|
|
51
|
-
raise ValidationError(self.message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|