monopyly 1.5.1__tar.gz → 1.5.2__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.5.1 → monopyly-1.5.2}/.gitignore +4 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/PKG-INFO +9 -10
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/CHANGELOG.md +12 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/README.md +2 -2
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/__init__.py +22 -27
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/_version.py +2 -2
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/auth/routes.py +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/accounts.py +3 -3
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/banks.py +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/routes.py +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/transactions.py +14 -5
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/transactions.py +17 -7
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/actions.py +0 -7
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/accounts.py +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/cards.py +7 -3
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/routes.py +31 -28
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/statements.py +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/transactions/_transactions.py +18 -5
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/transactions/activity/reconciliation.py +20 -1
- monopyly-1.5.2/monopyly/database/__init__.py +46 -0
- monopyly-1.5.2/monopyly/database/models.py +391 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/create-balance-chart.js +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +1 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transactions_table/condensed_row_content.html +0 -1
- {monopyly-1.5.1 → monopyly-1.5.2}/pyproject.toml +8 -8
- monopyly-1.5.1/monopyly/cli/apps.py +0 -108
- monopyly-1.5.1/monopyly/cli/launch.py +0 -135
- monopyly-1.5.1/monopyly/config/__init__.py +0 -1
- monopyly-1.5.1/monopyly/config/default_settings.py +0 -56
- monopyly-1.5.1/monopyly/config/settings.py +0 -59
- monopyly-1.5.1/monopyly/database/__init__.py +0 -101
- monopyly-1.5.1/monopyly/database/models.py +0 -483
- {monopyly-1.5.1 → monopyly-1.5.2}/COPYING +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/LICENSE +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/README.md +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/auth/actions.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/auth/blueprint.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/auth/tools.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/actions.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/blueprint.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/filters.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/banking/forms.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/forms/__init__.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/forms/_forms.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/forms/fields.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/forms/utils.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/forms/validators.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/common/utils.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/blueprint.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/context_processors.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/errors.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/filters.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/internal_transactions.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/core/routes.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/actions.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/blueprint.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/forms.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/transactions/__init__.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/transactions/activity/__init__.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/transactions/activity/data.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/credit/transactions/activity/parser.py +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/database/preloads.sql +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/database/schema.sql +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/database/views.sql +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/css/style.css +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/browserconfig.xml +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-114.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-120.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-144.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-150.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-152.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-16.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-160.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-180.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-192.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-310.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-32.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-57.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-60.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-64.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-70.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-72.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-76.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon-96.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon.ico +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/favicon/favicon.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/bank-account-details.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/bank-account-summaries.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/bank-accounts.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/credit-account-details.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/credit-transactions.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/homepage-user.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/homepage.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/about/statement-details.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/cards/chase-card.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/cards/discover-card.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/cards/new-card.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/cards/template-card.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/arrow-down.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/arrow-left.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/arrow-up.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/checkmark.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/delete-orange-thick.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/delete-orange.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/delete-thick.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/delete.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/edit.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/link.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/minus-thick.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/minus.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/plus-thick.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/plus.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/refresh.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/save.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/sort-asc.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/sort-desc.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/statement-pair.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/statement-thick.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/statement.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/icons/x-thick.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/img/statement.png +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/jquery-3.7.0.min.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/add-subtransaction.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/add-transfer.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/autocomplete-transaction.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/bind-tag-actions.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/create-category-chart.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/define-filter.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/display-new-account-type-inputs.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/display-new-bank-inputs.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/display-new-credit-account-inputs.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/expand-bank-account.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/expand-bank.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/expand-transaction.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/flip-card.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/hide-homepage-block.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/highlight-discrepant-transactions.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/infer-card.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/infer-statement.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/make-payment.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/ajax.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/autocomplete-input.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/expand-box-row.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/expand-transaction.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/form-suggestions.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/manage-acquisition-form.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/manage-overlays.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/manage-subforms.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/update-database-widget.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/modules/update-display-ajax.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/show-credit-activity-loader.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/show-linked-transaction.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/toggle-navigation.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/update-account-statement-parameters.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/update-bank-name.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/update-card-status.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/update-statement-parameters.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/update-statements-display.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/update-transactions-display.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/use-suggested-amount.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/static/js/use-suggested-merchant.js +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/auth/change_password.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/auth/login.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/auth/register.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/account_form/account_form.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/account_form/account_form_page_new.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/account_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/account_summaries.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/account_summaries_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/account_summary.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/accounts_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transaction_form/bank_info_form.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transaction_form/transaction_form.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transaction_form/transaction_form_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transaction_form/transaction_form_page_new.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transaction_form/transaction_form_page_update.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transaction_form/transfer_form.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transactions_table/condensed_row_content.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transactions_table/expanded_row_content.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transactions_table/transaction_field_titles.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/banking/transactions_table/transactions.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/form_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transaction_form/subform.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transaction_form/subtransaction_subform.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transaction_form/transaction_form_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/linked_bank_transaction.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/linked_credit_transaction.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/linked_transaction_overlay.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/subtransactions.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/transaction_condensed.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/transaction_expanded.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/common/transactions_table/transactions.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/credits.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/400.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/401.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/403.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/404.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/405.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/408.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/418.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/425.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/500.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/errors/error.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/index.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/profile.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/core/story.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/account_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/card_form/card_form.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/card_form/card_form_page_new.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/card_form/transfer_statement_inquiry.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/card_graphic/card_back.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/card_graphic/card_front.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/card_submission_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/cards.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/cards_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_reconciliation/discrepant_records.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_reconciliation/summary.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_reconciliation/unrecorded_activities.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statement_summary.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statements.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/statements_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/tag_tree/subtag_tree.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/tag_tree/tag_tree.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/tags_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transaction_form/transaction_form.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transaction_form/transaction_form_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transaction_form/transaction_form_page_new.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transaction_form/transaction_form_page_update.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transaction_submission_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transactions_page.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transactions_table/expanded_row_content.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transactions_table/transaction_field_titles.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/credit/transactions_table/transactions.html +0 -0
- {monopyly-1.5.1 → monopyly-1.5.2}/monopyly/templates/layout.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: monopyly
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
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
|
|
@@ -23,16 +23,15 @@ Classifier: Topic :: Office/Business :: Financial
|
|
|
23
23
|
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
24
24
|
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
25
25
|
Requires-Python: <3.11,>=3.10
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist: flask-wtf==1.2.
|
|
28
|
-
Requires-Dist: flask==3.
|
|
29
|
-
Requires-Dist: fuisce==1.0.2
|
|
26
|
+
Requires-Dist: dry-foundation==1.3.0
|
|
27
|
+
Requires-Dist: flask-wtf==1.2.2
|
|
28
|
+
Requires-Dist: flask==3.1.2
|
|
30
29
|
Requires-Dist: gunicorn==23.0.0
|
|
31
|
-
Requires-Dist: markdown==3.
|
|
30
|
+
Requires-Dist: markdown==3.9
|
|
32
31
|
Requires-Dist: nltk==3.9.1
|
|
33
32
|
Requires-Dist: python-dateutil==2.9.0
|
|
34
|
-
Requires-Dist: rich==
|
|
35
|
-
Requires-Dist: sqlalchemy==2.0.
|
|
33
|
+
Requires-Dist: rich==14.1.0
|
|
34
|
+
Requires-Dist: sqlalchemy==2.0.43
|
|
36
35
|
Description-Content-Type: text/markdown
|
|
37
36
|
|
|
38
37
|
<div id="header">
|
|
@@ -64,10 +63,10 @@ The package requires a recent version of Python (3.10+).
|
|
|
64
63
|
|
|
65
64
|
## Getting started
|
|
66
65
|
|
|
67
|
-
Once the package is properly installed,
|
|
66
|
+
Once the package is properly installed, launch 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):
|
|
68
67
|
|
|
69
68
|
```
|
|
70
|
-
$ monopyly local --browser [--host HOST] [--port PORT]
|
|
69
|
+
$ monopyly launch local --browser [--host HOST] [--port PORT]
|
|
71
70
|
```
|
|
72
71
|
|
|
73
72
|
Local mode indicates that the app is just going to be run using a locally hosted server, accessible to just your machine.
|
|
@@ -207,7 +207,19 @@
|
|
|
207
207
|
- Return to statement details page after deleting a transaction (rather than returning to the general transactions page)
|
|
208
208
|
- Allow users to change their password
|
|
209
209
|
- Warn users before form submission if the configuration currently disallows registration
|
|
210
|
+
- Fix issues with the application launcher not launching the browser; couple application launch process more tightly with click
|
|
211
|
+
- Use type annotations for SQLAlchemy ORM declarative mappings
|
|
210
212
|
- Increase the flexibility of the credit activity parser
|
|
211
213
|
|
|
212
214
|
|
|
215
|
+
### 1.5.2
|
|
216
|
+
|
|
217
|
+
- Use smoothing on charts for up to 100 transactions
|
|
218
|
+
- Improve tokenization normalization for credit reconciliation
|
|
219
|
+
- Remove statement requiring activity files be located in the `Downloads` directory
|
|
220
|
+
- Do not clear the reconciliation info when adding subtransaction fields via POST request
|
|
221
|
+
- Incorporate support for enhanced database handler selection subsets
|
|
222
|
+
- Bump dependencies (including support for recent SQLAlchemy versions)
|
|
223
|
+
|
|
224
|
+
|
|
213
225
|
<a name="bottom" id="bottom"></a>
|
|
@@ -27,10 +27,10 @@ The package requires a recent version of Python (3.10+).
|
|
|
27
27
|
|
|
28
28
|
## Getting started
|
|
29
29
|
|
|
30
|
-
Once the package is properly installed,
|
|
30
|
+
Once the package is properly installed, launch 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 local --browser [--host HOST] [--port PORT]
|
|
33
|
+
$ monopyly launch local --browser [--host HOST] [--port PORT]
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Local mode indicates that the app is just going to be run using a locally hosted server, accessible to just your machine.
|
|
@@ -2,39 +2,29 @@
|
|
|
2
2
|
Run the Monopyly app.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation import DryFlask, Factory, interact
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from monopyly.database import SQLAlchemy, register_db_cli_commands
|
|
7
|
+
from .core.errors import render_error_template
|
|
8
|
+
from .database import SQLAlchemy
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Prepare the app configuration
|
|
17
|
-
if test_config:
|
|
18
|
-
config = test_config
|
|
19
|
-
else:
|
|
20
|
-
# Load the development/production config when not testing
|
|
21
|
-
if app.debug or debug:
|
|
22
|
-
config = DevelopmentConfig.configure_for_instance(app.instance_path)
|
|
23
|
-
else:
|
|
24
|
-
config = ProductionConfig.configure_for_instance(app.instance_path)
|
|
25
|
-
app.config.from_object(config)
|
|
26
|
-
|
|
27
|
-
# Initialize the app, including CLI commands and blueprints
|
|
28
|
-
init_app(app)
|
|
29
|
-
return app
|
|
30
|
-
|
|
11
|
+
@Factory(db_interface=SQLAlchemy)
|
|
12
|
+
def create_app(config=None):
|
|
13
|
+
"""
|
|
14
|
+
Create the Flask application.
|
|
31
15
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
Create the Flask app, including configurations as specified. This
|
|
17
|
+
will configure the app using the configuration objects made
|
|
18
|
+
available by the Monopyly application and initialize the app
|
|
19
|
+
by registering app blueprints, routes, and commands.
|
|
20
|
+
"""
|
|
21
|
+
# Create and configure the app
|
|
22
|
+
app = DryFlask(__name__, app_name="Monopyly")
|
|
23
|
+
app.configure(config)
|
|
24
|
+
# Register blueprints and error handlers specific to this app
|
|
35
25
|
register_blueprints(app)
|
|
36
26
|
register_errorhandlers(app)
|
|
37
|
-
|
|
27
|
+
return app
|
|
38
28
|
|
|
39
29
|
|
|
40
30
|
def register_blueprints(app):
|
|
@@ -76,3 +66,8 @@ def register_errorhandlers(app):
|
|
|
76
66
|
]
|
|
77
67
|
for code in handled_error_codes:
|
|
78
68
|
app.register_error_handler(code, render_error_template)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main():
|
|
72
|
+
"""The entry point to the Monopyly application."""
|
|
73
|
+
interact(__name__)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Routes for site authentication.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from dry_foundation.database import db_transaction
|
|
5
6
|
from flask import (
|
|
6
7
|
current_app,
|
|
7
8
|
flash,
|
|
@@ -12,7 +13,6 @@ from flask import (
|
|
|
12
13
|
session,
|
|
13
14
|
url_for,
|
|
14
15
|
)
|
|
15
|
-
from fuisce.database import db_transaction
|
|
16
16
|
from sqlalchemy import select
|
|
17
17
|
from werkzeug.security import check_password_hash, generate_password_hash
|
|
18
18
|
|
|
@@ -3,7 +3,7 @@ Tools for interacting with bank accounts in the database.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import sqlalchemy.sql.functions as sql_func
|
|
6
|
-
from
|
|
6
|
+
from dry_foundation.database.handler import DatabaseViewHandler
|
|
7
7
|
from werkzeug.exceptions import abort
|
|
8
8
|
|
|
9
9
|
from ..common.forms.utils import execute_on_form_validation
|
|
@@ -229,10 +229,10 @@ class BankAccountHandler(
|
|
|
229
229
|
return account
|
|
230
230
|
|
|
231
231
|
@classmethod
|
|
232
|
-
def _filter_entries(cls, query, criteria):
|
|
232
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
233
233
|
# Add a join to enable filtering by bank account type
|
|
234
234
|
query = query.join(BankAccountTypeView)
|
|
235
|
-
return super()._filter_entries(query, criteria)
|
|
235
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
236
236
|
|
|
237
237
|
@classmethod
|
|
238
238
|
def delete_entry(cls, entry_id):
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Routes for banking financials.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from dry_foundation.database import db_transaction
|
|
5
6
|
from flask import jsonify, redirect, render_template, request, url_for
|
|
6
|
-
from fuisce.database import db_transaction
|
|
7
7
|
|
|
8
8
|
from ..auth.tools import login_required
|
|
9
9
|
from ..common.forms.utils import extend_field_list_for_ajax
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Tools for interacting with the bank transactions in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation.database.handler import DatabaseViewHandler
|
|
6
6
|
|
|
7
7
|
from ..common.forms.utils import execute_on_form_validation
|
|
8
8
|
from ..common.transactions import TransactionHandler, TransactionTagHandler
|
|
@@ -36,7 +36,9 @@ class BankTransactionHandler(
|
|
|
36
36
|
|
|
37
37
|
@classmethod
|
|
38
38
|
@DatabaseViewHandler.view_query
|
|
39
|
-
def get_transactions(
|
|
39
|
+
def get_transactions(
|
|
40
|
+
cls, account_ids=None, active=None, sort_order="DESC", offset=None, limit=None
|
|
41
|
+
):
|
|
40
42
|
"""
|
|
41
43
|
Get bank transactions from the database.
|
|
42
44
|
|
|
@@ -61,6 +63,13 @@ class BankTransactionHandler(
|
|
|
61
63
|
An indicator of whether the transactions should be ordered
|
|
62
64
|
in ascending (oldest at top) or descending (newest at top)
|
|
63
65
|
order. The default is descending order.
|
|
66
|
+
offset : int, optional
|
|
67
|
+
The number of transactions by which to offset the results
|
|
68
|
+
returned by this query. The default is `None`, in which case
|
|
69
|
+
no offset will be added.
|
|
70
|
+
limit : int, optional
|
|
71
|
+
A limit on the number of transactions retrieved from the
|
|
72
|
+
database.
|
|
64
73
|
|
|
65
74
|
Returns
|
|
66
75
|
-------
|
|
@@ -71,7 +80,7 @@ class BankTransactionHandler(
|
|
|
71
80
|
criteria.add_match_filter(cls.model, "account_id", account_ids)
|
|
72
81
|
criteria.add_match_filter(BankAccountView, "active", active)
|
|
73
82
|
transactions = super()._get_transactions(
|
|
74
|
-
criteria=criteria, sort_order=sort_order
|
|
83
|
+
criteria=criteria, sort_order=sort_order, offset=None, limit=None
|
|
75
84
|
)
|
|
76
85
|
return transactions
|
|
77
86
|
|
|
@@ -150,7 +159,7 @@ class BankTagHandler(TransactionTagHandler, model=TransactionTagHandler.model):
|
|
|
150
159
|
return tags
|
|
151
160
|
|
|
152
161
|
@classmethod
|
|
153
|
-
def _filter_entries(cls, query, criteria):
|
|
162
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
154
163
|
# Add a join to enable filtering by transaction ID or subtransaction ID
|
|
155
164
|
join_transaction = BankTransactionView in criteria.discriminators
|
|
156
165
|
join_subtransaction = (
|
|
@@ -160,7 +169,7 @@ class BankTagHandler(TransactionTagHandler, model=TransactionTagHandler.model):
|
|
|
160
169
|
query = query.join(bank_tag_link_table).join(BankSubtransaction)
|
|
161
170
|
if join_transaction:
|
|
162
171
|
query = query.join(BankTransactionView)
|
|
163
|
-
return super()._filter_entries(query, criteria)
|
|
172
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
164
173
|
|
|
165
174
|
|
|
166
175
|
@execute_on_form_validation
|
|
@@ -4,7 +4,7 @@ Tools for building a common transaction interface.
|
|
|
4
4
|
|
|
5
5
|
from abc import abstractmethod
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from dry_foundation.database.handler import DatabaseHandler, DatabaseViewHandler
|
|
8
8
|
from flask import current_app
|
|
9
9
|
|
|
10
10
|
from ..database.models import (
|
|
@@ -31,17 +31,27 @@ class TransactionHandler(DatabaseViewHandler):
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
|
-
def _customize_entries_query(
|
|
34
|
+
def _customize_entries_query(
|
|
35
|
+
cls, query, criteria, column_orders, offset=None, limit=None
|
|
36
|
+
):
|
|
35
37
|
# Group transactions and order by transaction date
|
|
36
38
|
query = query.group_by(cls.model.id)
|
|
37
|
-
return super()._customize_entries_query(
|
|
39
|
+
return super()._customize_entries_query(
|
|
40
|
+
query, criteria, column_orders, offset=offset, limit=limit
|
|
41
|
+
)
|
|
38
42
|
|
|
39
43
|
@classmethod
|
|
40
|
-
def _get_transactions(
|
|
44
|
+
def _get_transactions(
|
|
45
|
+
cls, criteria=None, sort_order="DESC", offset=None, limit=None
|
|
46
|
+
):
|
|
41
47
|
# Specify transaction order
|
|
42
48
|
column_orders = {cls.model.transaction_date: sort_order}
|
|
43
49
|
entries = cls.get_entries(
|
|
44
|
-
entry_ids=None,
|
|
50
|
+
entry_ids=None,
|
|
51
|
+
criteria=criteria,
|
|
52
|
+
column_orders=column_orders,
|
|
53
|
+
offset=offset,
|
|
54
|
+
limit=limit,
|
|
45
55
|
)
|
|
46
56
|
return entries
|
|
47
57
|
|
|
@@ -232,10 +242,10 @@ class TransactionTagHandler(DatabaseHandler, model=TransactionTag):
|
|
|
232
242
|
return tags
|
|
233
243
|
|
|
234
244
|
@classmethod
|
|
235
|
-
def _filter_entries(cls, query, criteria):
|
|
245
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
236
246
|
# Only get distinct tag entries
|
|
237
247
|
query = query.distinct()
|
|
238
|
-
return super()._filter_entries(query, criteria)
|
|
248
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
239
249
|
|
|
240
250
|
@classmethod
|
|
241
251
|
def get_subtags(cls, tag):
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
"""Module describing logical core actions (to be used in routes)."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
|
|
5
3
|
import markdown
|
|
6
4
|
|
|
7
5
|
|
|
8
|
-
def get_timestamp():
|
|
9
|
-
"""Get a timestamp for backup filenames."""
|
|
10
|
-
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
11
|
-
|
|
12
|
-
|
|
13
6
|
class MarkdownConverter:
|
|
14
7
|
|
|
15
8
|
replacements = {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Tools for interacting with credit cards in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation.database.handler import DatabaseHandler
|
|
6
6
|
|
|
7
7
|
from ..common.forms.utils import execute_on_form_validation
|
|
8
8
|
from ..database.models import Bank, CreditAccount, CreditCard
|
|
@@ -99,8 +99,12 @@ class CreditCardHandler(DatabaseHandler, model=CreditCard):
|
|
|
99
99
|
return card
|
|
100
100
|
|
|
101
101
|
@classmethod
|
|
102
|
-
def _customize_entries_query(
|
|
103
|
-
|
|
102
|
+
def _customize_entries_query(
|
|
103
|
+
cls, query, filters, sort_order, offset=None, limit=None
|
|
104
|
+
):
|
|
105
|
+
query = super()._customize_entries_query(
|
|
106
|
+
query, filters, sort_order, offset=offset, limit=limit
|
|
107
|
+
)
|
|
104
108
|
# Order cards by active status (active cards first)
|
|
105
109
|
query = query.order_by(cls.model.active.desc())
|
|
106
110
|
return query
|
|
@@ -4,6 +4,7 @@ Routes for credit card financials.
|
|
|
4
4
|
|
|
5
5
|
from itertools import islice
|
|
6
6
|
|
|
7
|
+
from dry_foundation.database import db_transaction
|
|
7
8
|
from flask import (
|
|
8
9
|
flash,
|
|
9
10
|
g,
|
|
@@ -14,7 +15,6 @@ from flask import (
|
|
|
14
15
|
session,
|
|
15
16
|
url_for,
|
|
16
17
|
)
|
|
17
|
-
from fuisce.database import db_transaction
|
|
18
18
|
from sqlalchemy.exc import MultipleResultsFound
|
|
19
19
|
from werkzeug.exceptions import abort
|
|
20
20
|
from wtforms.validators import ValidationError
|
|
@@ -321,6 +321,7 @@ def clear_reconciliation_info():
|
|
|
321
321
|
"credit.update_transaction",
|
|
322
322
|
"credit.infer_statement",
|
|
323
323
|
"credit.suggest_transaction_autocomplete",
|
|
324
|
+
"credit.add_subtransaction_fields",
|
|
324
325
|
"credit.delete_transaction",
|
|
325
326
|
"static",
|
|
326
327
|
None,
|
|
@@ -344,15 +345,33 @@ def load_transactions(card_id):
|
|
|
344
345
|
# Get all of the user's transactions for the selected cards
|
|
345
346
|
sort_order = "DESC"
|
|
346
347
|
transactions = CreditTransactionHandler.get_transactions(
|
|
347
|
-
card_ids=selected_card_ids,
|
|
348
|
-
sort_order=sort_order,
|
|
348
|
+
card_ids=selected_card_ids, sort_order=sort_order, limit=100
|
|
349
349
|
)
|
|
350
350
|
return render_template(
|
|
351
351
|
"credit/transactions_page.html",
|
|
352
352
|
filter_cards=cards,
|
|
353
353
|
selected_card_ids=selected_card_ids,
|
|
354
354
|
sort_order=sort_order,
|
|
355
|
-
transactions=
|
|
355
|
+
transactions=transactions,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@bp.route("/_update_transactions_display", methods=("POST",))
|
|
360
|
+
@login_required
|
|
361
|
+
def update_transactions_display():
|
|
362
|
+
# Separate the arguments of the POST method
|
|
363
|
+
post_args = request.get_json()
|
|
364
|
+
card_ids = map(int, post_args["card_ids"])
|
|
365
|
+
sort_order = "ASC" if post_args["sort_order"] == "asc" else "DESC"
|
|
366
|
+
# Filter selected transactions from the database
|
|
367
|
+
transactions = CreditTransactionHandler.get_transactions(
|
|
368
|
+
card_ids=card_ids, sort_order=sort_order, limit=100
|
|
369
|
+
)
|
|
370
|
+
return render_template(
|
|
371
|
+
"credit/transactions_table/transactions.html",
|
|
372
|
+
sort_order=sort_order,
|
|
373
|
+
transactions=transactions,
|
|
374
|
+
full_view=True,
|
|
356
375
|
)
|
|
357
376
|
|
|
358
377
|
|
|
@@ -385,26 +404,6 @@ def show_linked_transaction():
|
|
|
385
404
|
)
|
|
386
405
|
|
|
387
406
|
|
|
388
|
-
@bp.route("/_update_transactions_display", methods=("POST",))
|
|
389
|
-
@login_required
|
|
390
|
-
def update_transactions_display():
|
|
391
|
-
# Separate the arguments of the POST method
|
|
392
|
-
post_args = request.get_json()
|
|
393
|
-
card_ids = map(int, post_args["card_ids"])
|
|
394
|
-
sort_order = "ASC" if post_args["sort_order"] == "asc" else "DESC"
|
|
395
|
-
# Filter selected transactions from the database
|
|
396
|
-
transactions = CreditTransactionHandler.get_transactions(
|
|
397
|
-
card_ids=card_ids,
|
|
398
|
-
sort_order=sort_order,
|
|
399
|
-
)
|
|
400
|
-
return render_template(
|
|
401
|
-
"credit/transactions_table/transactions.html",
|
|
402
|
-
sort_order=sort_order,
|
|
403
|
-
transactions=islice(transactions, 100),
|
|
404
|
-
full_view=True,
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
408
407
|
@bp.route(
|
|
409
408
|
"/add_transaction",
|
|
410
409
|
defaults={"card_id": None, "statement_id": None},
|
|
@@ -497,10 +496,14 @@ def add_subtransaction_fields():
|
|
|
497
496
|
@db_transaction
|
|
498
497
|
def delete_transaction(transaction_id):
|
|
499
498
|
CreditTransactionHandler.delete_entry(transaction_id)
|
|
500
|
-
if statement_id := session.pop("statement_focus", None):
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
499
|
+
if (statement_id := session.pop("statement_focus", None)) is not None:
|
|
500
|
+
statement = CreditStatementHandler.get_entry(statement_id)
|
|
501
|
+
# Delete the statement if it has no more transactions
|
|
502
|
+
if statement.balance is not None:
|
|
503
|
+
return redirect(
|
|
504
|
+
url_for("credit.load_statement_details", statement_id=statement.id)
|
|
505
|
+
)
|
|
506
|
+
CreditStatementHandler.delete_entry(statement.id)
|
|
504
507
|
return redirect(url_for("credit.load_transactions"))
|
|
505
508
|
|
|
506
509
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Tools for interacting with the credit statements in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from authanor.database.handler import DatabaseViewHandler
|
|
6
5
|
from dateutil.relativedelta import relativedelta
|
|
6
|
+
from dry_foundation.database.handler import DatabaseViewHandler
|
|
7
7
|
|
|
8
8
|
from ..common.utils import get_next_occurrence_of_day
|
|
9
9
|
from ..database.models import (
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Tools for interacting with the credit transactions in the database.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from dry_foundation.database.handler import DatabaseHandler, DatabaseViewHandler
|
|
6
6
|
|
|
7
7
|
from ...common.forms.utils import execute_on_form_validation
|
|
8
8
|
from ...common.transactions import TransactionHandler, TransactionTagHandler
|
|
@@ -38,7 +38,13 @@ class CreditTransactionHandler(
|
|
|
38
38
|
@classmethod
|
|
39
39
|
@DatabaseViewHandler.view_query
|
|
40
40
|
def get_transactions(
|
|
41
|
-
cls,
|
|
41
|
+
cls,
|
|
42
|
+
statement_ids=None,
|
|
43
|
+
card_ids=None,
|
|
44
|
+
active=None,
|
|
45
|
+
sort_order="DESC",
|
|
46
|
+
offset=None,
|
|
47
|
+
limit=None,
|
|
42
48
|
):
|
|
43
49
|
"""
|
|
44
50
|
Get credit card transactions from the database.
|
|
@@ -66,6 +72,13 @@ class CreditTransactionHandler(
|
|
|
66
72
|
An indicator of whether the transactions should be ordered
|
|
67
73
|
in ascending (oldest at top) or descending (newest at top)
|
|
68
74
|
order.
|
|
75
|
+
offset : int, optional
|
|
76
|
+
The number of transactions by which to offset the results
|
|
77
|
+
returned by this query. The default is `None`, in which case
|
|
78
|
+
no offset will be added.
|
|
79
|
+
limit : int, optional
|
|
80
|
+
A limit on the number of transactions retrieved from the
|
|
81
|
+
database.
|
|
69
82
|
|
|
70
83
|
Returns
|
|
71
84
|
-------
|
|
@@ -77,7 +90,7 @@ class CreditTransactionHandler(
|
|
|
77
90
|
criteria.add_match_filter(CreditCard, "id", card_ids)
|
|
78
91
|
criteria.add_match_filter(CreditCard, "active", active)
|
|
79
92
|
transactions = super()._get_transactions(
|
|
80
|
-
criteria=criteria, sort_order=sort_order
|
|
93
|
+
criteria=criteria, sort_order=sort_order, offset=offset, limit=limit
|
|
81
94
|
)
|
|
82
95
|
return transactions
|
|
83
96
|
|
|
@@ -196,7 +209,7 @@ class CreditTagHandler(TransactionTagHandler, model=TransactionTagHandler.model)
|
|
|
196
209
|
return tags
|
|
197
210
|
|
|
198
211
|
@classmethod
|
|
199
|
-
def _filter_entries(cls, query, criteria):
|
|
212
|
+
def _filter_entries(cls, query, criteria, offset, limit):
|
|
200
213
|
# Add a join to enable filtering by transaction ID or subtransaction ID
|
|
201
214
|
join_transaction = CreditTransactionView in criteria.discriminators
|
|
202
215
|
join_subtransaction = (
|
|
@@ -206,7 +219,7 @@ class CreditTagHandler(TransactionTagHandler, model=TransactionTagHandler.model)
|
|
|
206
219
|
query = query.join(credit_tag_link_table).join(CreditSubtransaction)
|
|
207
220
|
if join_transaction:
|
|
208
221
|
query = query.join(CreditTransactionView)
|
|
209
|
-
return super()._filter_entries(query, criteria)
|
|
222
|
+
return super()._filter_entries(query, criteria, offset, limit)
|
|
210
223
|
|
|
211
224
|
|
|
212
225
|
@execute_on_form_validation
|
|
@@ -222,7 +222,26 @@ class _Matchmaker(ABC):
|
|
|
222
222
|
field : str
|
|
223
223
|
The string of text to be tokenized.
|
|
224
224
|
"""
|
|
225
|
-
|
|
225
|
+
replacements = [
|
|
226
|
+
# Remove disruptive punctuation
|
|
227
|
+
("1-800", "1800"),
|
|
228
|
+
("-", " "),
|
|
229
|
+
(".", " "),
|
|
230
|
+
(",", " "),
|
|
231
|
+
("(", " "),
|
|
232
|
+
(")", " "),
|
|
233
|
+
("'", ""),
|
|
234
|
+
# Standardize characters
|
|
235
|
+
("&", "and"),
|
|
236
|
+
("é", "e"),
|
|
237
|
+
]
|
|
238
|
+
for original, replacement in replacements:
|
|
239
|
+
field = field.replace(original, replacement)
|
|
240
|
+
tokens = set(wordpunct_tokenize(field.replace("'", "").casefold()))
|
|
241
|
+
removals = ["and", "the", "of"]
|
|
242
|
+
for word in removals:
|
|
243
|
+
tokens.discard(word)
|
|
244
|
+
return tokens
|
|
226
245
|
|
|
227
246
|
def _compute_transaction_activity_similarity_score(
|
|
228
247
|
self, merchant_tokens, notes_tokens, activity_tokens
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Expose commonly used database functionality to the rest of the package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dry_foundation.database import SQLAlchemy as _SQLAlchemy
|
|
8
|
+
from flask import current_app
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SQLAlchemy(_SQLAlchemy):
|
|
12
|
+
"""Store an interface to SQLAlchemy database objects."""
|
|
13
|
+
|
|
14
|
+
def initialize(self, app):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the database.
|
|
17
|
+
|
|
18
|
+
Initialize the database by executing the SQL schema to clear
|
|
19
|
+
existing data and create new tables.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
app : flask.Flask
|
|
24
|
+
The app object, which may pass initialization parameters via
|
|
25
|
+
its configuration.
|
|
26
|
+
"""
|
|
27
|
+
with app.app_context():
|
|
28
|
+
# Establish a raw connection in order to execute the complete files
|
|
29
|
+
raw_conn = self.engine.raw_connection()
|
|
30
|
+
# Load the tables, table views, and preloaded data
|
|
31
|
+
sql_dir = Path(__file__).parent
|
|
32
|
+
sql_filepaths = [
|
|
33
|
+
sql_dir / path for path in ("schema.sql", "views.sql", "preloads.sql")
|
|
34
|
+
]
|
|
35
|
+
auxiliary_preload_path = app.config.get("PRELOAD_DATA_PATH")
|
|
36
|
+
if auxiliary_preload_path:
|
|
37
|
+
sql_filepaths.append(auxiliary_preload_path)
|
|
38
|
+
for sql_filepath in sql_filepaths:
|
|
39
|
+
with current_app.open_resource(sql_filepath) as sql_file:
|
|
40
|
+
raw_conn.executescript(sql_file.read().decode("utf8"))
|
|
41
|
+
raw_conn.close()
|
|
42
|
+
# Top level initialization does not overwrite tables, so it goes at the end
|
|
43
|
+
super().initialize(app)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
SQLAlchemy.create_default_interface(echo_engine=False)
|