monopyly 1.5.2__tar.gz → 1.6.0__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.
Files changed (248) hide show
  1. {monopyly-1.5.2 → monopyly-1.6.0}/PKG-INFO +5 -5
  2. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/CHANGELOG.md +15 -0
  3. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/README.md +1 -1
  4. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/__init__.py +1 -1
  5. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/_version.py +2 -2
  6. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/auth/blueprint.py +2 -0
  7. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/auth/routes.py +1 -2
  8. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/accounts.py +4 -4
  9. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/actions.py +20 -17
  10. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/blueprint.py +2 -0
  11. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/filters.py +6 -6
  12. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/forms.py +3 -5
  13. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/routes.py +71 -9
  14. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/transactions.py +2 -3
  15. monopyly-1.6.0/monopyly/common/forms/__init__.py +15 -0
  16. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/common/forms/_forms.py +1 -2
  17. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/common/forms/fields.py +0 -2
  18. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/common/forms/utils.py +1 -1
  19. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/common/transactions.py +72 -7
  20. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/actions.py +2 -1
  21. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/blueprint.py +2 -0
  22. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/filters.py +0 -2
  23. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/routes.py +1 -1
  24. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/actions.py +4 -5
  25. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/blueprint.py +2 -0
  26. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/forms.py +3 -5
  27. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/routes.py +37 -62
  28. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/transactions/__init__.py +2 -0
  29. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/transactions/_transactions.py +1 -4
  30. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/transactions/activity/__init__.py +6 -0
  31. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/transactions/activity/parser.py +0 -1
  32. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/transactions/activity/reconciliation.py +5 -3
  33. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/database/__init__.py +0 -3
  34. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/database/models.py +63 -49
  35. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/database/preloads.sql +6 -1
  36. monopyly-1.6.0/monopyly/scripts/screenshot_application.py +100 -0
  37. monopyly-1.6.0/monopyly/static/chartist-1.5.0.min.js +8 -0
  38. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/css/style.css +35 -14
  39. monopyly-1.6.0/monopyly/static/img/about/bank-account-details.png +0 -0
  40. monopyly-1.6.0/monopyly/static/img/about/bank-account-summaries.png +0 -0
  41. monopyly-1.6.0/monopyly/static/img/about/bank-accounts.png +0 -0
  42. monopyly-1.6.0/monopyly/static/img/about/credit-account-details.png +0 -0
  43. monopyly-1.6.0/monopyly/static/img/about/credit-statement-details.png +0 -0
  44. monopyly-1.6.0/monopyly/static/img/about/credit-transactions.png +0 -0
  45. monopyly-1.6.0/monopyly/static/img/about/homepage-user.png +0 -0
  46. monopyly-1.6.0/monopyly/static/img/about/homepage.png +0 -0
  47. monopyly-1.6.0/monopyly/static/jquery-3.7.1.min.js +2 -0
  48. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/add-transfer.js +8 -9
  49. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/bind-tag-actions.js +6 -0
  50. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/create-balance-chart.js +1 -1
  51. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/create-category-chart.js +1 -1
  52. monopyly-1.6.0/monopyly/static/js/load-more-transactions.js +27 -0
  53. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/expand-transaction.js +7 -6
  54. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/update-display-ajax.js +20 -1
  55. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/update-transactions-display.js +8 -2
  56. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/account_page.html +15 -16
  57. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/account_summaries.html +2 -2
  58. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/account_summary.html +1 -1
  59. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/accounts_page.html +2 -2
  60. monopyly-1.6.0/monopyly/templates/banking/transactions_table/table.html +3 -0
  61. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transactions_table/transactions.html +0 -1
  62. monopyly-1.6.0/monopyly/templates/common/tag_tree.html +25 -0
  63. {monopyly-1.5.2/monopyly/templates/credit → monopyly-1.6.0/monopyly/templates/common}/tags_page.html +7 -3
  64. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transactions_table/linked_bank_transaction.html +2 -2
  65. monopyly-1.6.0/monopyly/templates/common/transactions_table/table.html +6 -0
  66. monopyly-1.6.0/monopyly/templates/common/transactions_table/transactions.html +12 -0
  67. monopyly-1.6.0/monopyly/templates/core/index.html +180 -0
  68. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/profile.html +1 -1
  69. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_page.html +2 -2
  70. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +3 -3
  71. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_summary.html +2 -2
  72. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transaction_submission_page.html +3 -3
  73. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transactions_page.html +19 -3
  74. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transactions_table/condensed_row_content.html +2 -2
  75. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transactions_table/expanded_row_content.html +5 -5
  76. monopyly-1.6.0/monopyly/templates/credit/transactions_table/table.html +3 -0
  77. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transactions_table/transactions.html +0 -1
  78. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/layout.html +9 -4
  79. {monopyly-1.5.2 → monopyly-1.6.0}/pyproject.toml +26 -13
  80. monopyly-1.5.2/monopyly/common/forms/__init__.py +0 -7
  81. monopyly-1.5.2/monopyly/static/img/about/bank-account-details.png +0 -0
  82. monopyly-1.5.2/monopyly/static/img/about/bank-account-summaries.png +0 -0
  83. monopyly-1.5.2/monopyly/static/img/about/bank-accounts.png +0 -0
  84. monopyly-1.5.2/monopyly/static/img/about/credit-account-details.png +0 -0
  85. monopyly-1.5.2/monopyly/static/img/about/credit-transactions.png +0 -0
  86. monopyly-1.5.2/monopyly/static/img/about/homepage-user.png +0 -0
  87. monopyly-1.5.2/monopyly/static/img/about/homepage.png +0 -0
  88. monopyly-1.5.2/monopyly/static/jquery-3.7.0.min.js +0 -2
  89. monopyly-1.5.2/monopyly/templates/common/transactions_table/transactions.html +0 -18
  90. monopyly-1.5.2/monopyly/templates/core/index.html +0 -169
  91. monopyly-1.5.2/monopyly/templates/credit/tag_tree/subtag_tree.html +0 -22
  92. monopyly-1.5.2/monopyly/templates/credit/tag_tree/tag_tree.html +0 -13
  93. {monopyly-1.5.2 → monopyly-1.6.0}/.gitignore +0 -0
  94. {monopyly-1.5.2 → monopyly-1.6.0}/COPYING +0 -0
  95. {monopyly-1.5.2 → monopyly-1.6.0}/LICENSE +0 -0
  96. {monopyly-1.5.2 → monopyly-1.6.0}/README.md +0 -0
  97. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/auth/actions.py +0 -0
  98. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/auth/tools.py +0 -0
  99. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/banking/banks.py +0 -0
  100. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/common/forms/validators.py +0 -0
  101. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/common/utils.py +0 -0
  102. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/context_processors.py +0 -0
  103. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/errors.py +0 -0
  104. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/core/internal_transactions.py +0 -0
  105. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/accounts.py +0 -0
  106. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/cards.py +0 -0
  107. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/statements.py +0 -0
  108. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/credit/transactions/activity/data.py +0 -0
  109. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/database/schema.sql +0 -0
  110. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/database/views.sql +0 -0
  111. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/browserconfig.xml +0 -0
  112. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-114.png +0 -0
  113. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-120.png +0 -0
  114. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-144.png +0 -0
  115. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-150.png +0 -0
  116. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-152.png +0 -0
  117. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-16.png +0 -0
  118. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-160.png +0 -0
  119. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-180.png +0 -0
  120. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-192.png +0 -0
  121. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-310.png +0 -0
  122. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-32.png +0 -0
  123. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-57.png +0 -0
  124. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-60.png +0 -0
  125. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-64.png +0 -0
  126. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-70.png +0 -0
  127. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-72.png +0 -0
  128. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-76.png +0 -0
  129. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon-96.png +0 -0
  130. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon.ico +0 -0
  131. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/favicon/favicon.png +0 -0
  132. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/about/statement-details.png +0 -0
  133. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/cards/chase-card.png +0 -0
  134. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/cards/discover-card.png +0 -0
  135. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/cards/new-card.png +0 -0
  136. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/cards/template-card.png +0 -0
  137. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/arrow-down.png +0 -0
  138. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/arrow-left.png +0 -0
  139. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/arrow-up.png +0 -0
  140. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/checkmark.png +0 -0
  141. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/delete-orange-thick.png +0 -0
  142. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/delete-orange.png +0 -0
  143. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/delete-thick.png +0 -0
  144. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/delete.png +0 -0
  145. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/edit.png +0 -0
  146. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/link.png +0 -0
  147. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/minus-thick.png +0 -0
  148. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/minus.png +0 -0
  149. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/plus-thick.png +0 -0
  150. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/plus.png +0 -0
  151. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/refresh.png +0 -0
  152. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/save.png +0 -0
  153. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/sort-asc.png +0 -0
  154. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/sort-desc.png +0 -0
  155. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/statement-pair.png +0 -0
  156. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/statement-thick.png +0 -0
  157. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/statement.png +0 -0
  158. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/icons/x-thick.png +0 -0
  159. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/img/statement.png +0 -0
  160. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/add-subtransaction.js +0 -0
  161. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/autocomplete-transaction.js +0 -0
  162. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/define-filter.js +0 -0
  163. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/display-new-account-type-inputs.js +0 -0
  164. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/display-new-bank-inputs.js +0 -0
  165. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/display-new-credit-account-inputs.js +0 -0
  166. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/expand-bank-account.js +0 -0
  167. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/expand-bank.js +0 -0
  168. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/expand-transaction.js +0 -0
  169. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/flip-card.js +0 -0
  170. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/hide-homepage-block.js +0 -0
  171. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/highlight-discrepant-transactions.js +0 -0
  172. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/infer-card.js +0 -0
  173. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/infer-statement.js +0 -0
  174. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/make-payment.js +0 -0
  175. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/ajax.js +0 -0
  176. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/autocomplete-input.js +0 -0
  177. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/expand-box-row.js +0 -0
  178. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/form-suggestions.js +0 -0
  179. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/manage-acquisition-form.js +0 -0
  180. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/manage-overlays.js +0 -0
  181. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/manage-subforms.js +0 -0
  182. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/modules/update-database-widget.js +0 -0
  183. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/show-credit-activity-loader.js +0 -0
  184. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/show-linked-transaction.js +0 -0
  185. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/toggle-navigation.js +0 -0
  186. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/update-account-statement-parameters.js +0 -0
  187. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/update-bank-name.js +0 -0
  188. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/update-card-status.js +0 -0
  189. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/update-statement-parameters.js +0 -0
  190. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/update-statements-display.js +0 -0
  191. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/use-suggested-amount.js +0 -0
  192. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/static/js/use-suggested-merchant.js +0 -0
  193. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/auth/change_password.html +0 -0
  194. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/auth/login.html +0 -0
  195. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/auth/register.html +0 -0
  196. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/account_form/account_form.html +0 -0
  197. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/account_form/account_form_page_new.html +0 -0
  198. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/account_summaries_page.html +0 -0
  199. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transaction_form/bank_info_form.html +0 -0
  200. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transaction_form/transaction_form.html +0 -0
  201. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transaction_form/transaction_form_page.html +0 -0
  202. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transaction_form/transaction_form_page_new.html +0 -0
  203. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transaction_form/transaction_form_page_update.html +0 -0
  204. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transaction_form/transfer_form.html +0 -0
  205. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transactions_table/condensed_row_content.html +0 -0
  206. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transactions_table/expanded_row_content.html +0 -0
  207. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/banking/transactions_table/transaction_field_titles.html +0 -0
  208. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/form_page.html +0 -0
  209. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transaction_form/subform.html +0 -0
  210. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transaction_form/subtransaction_subform.html +0 -0
  211. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transaction_form/transaction_form_page.html +0 -0
  212. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transactions_table/linked_credit_transaction.html +0 -0
  213. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transactions_table/linked_transaction_overlay.html +0 -0
  214. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transactions_table/subtransactions.html +0 -0
  215. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transactions_table/transaction_condensed.html +0 -0
  216. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/common/transactions_table/transaction_expanded.html +0 -0
  217. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/credits.html +0 -0
  218. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/400.html +0 -0
  219. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/401.html +0 -0
  220. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/403.html +0 -0
  221. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/404.html +0 -0
  222. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/405.html +0 -0
  223. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/408.html +0 -0
  224. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/418.html +0 -0
  225. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/425.html +0 -0
  226. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/500.html +0 -0
  227. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/errors/error.html +0 -0
  228. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/core/story.html +0 -0
  229. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/account_page.html +0 -0
  230. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/card_form/card_form.html +0 -0
  231. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/card_form/card_form_page_new.html +0 -0
  232. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/card_form/transfer_statement_inquiry.html +0 -0
  233. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/card_graphic/card_back.html +0 -0
  234. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/card_graphic/card_front.html +0 -0
  235. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/card_submission_page.html +0 -0
  236. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/cards.html +0 -0
  237. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/cards_page.html +0 -0
  238. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_reconciliation/discrepant_records.html +0 -0
  239. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +0 -0
  240. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_reconciliation/summary.html +0 -0
  241. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statement_reconciliation/unrecorded_activities.html +0 -0
  242. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statements.html +0 -0
  243. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/statements_page.html +0 -0
  244. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transaction_form/transaction_form.html +0 -0
  245. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transaction_form/transaction_form_page.html +0 -0
  246. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transaction_form/transaction_form_page_new.html +0 -0
  247. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transaction_form/transaction_form_page_update.html +0 -0
  248. {monopyly-1.5.2 → monopyly-1.6.0}/monopyly/templates/credit/transactions_table/transaction_field_titles.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monopyly
3
- Version: 1.5.2
3
+ Version: 1.6.0
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,13 +22,13 @@ 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.11,>=3.10
26
- Requires-Dist: dry-foundation==1.3.0
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: dry-foundation==1.4.1
27
27
  Requires-Dist: flask-wtf==1.2.2
28
28
  Requires-Dist: flask==3.1.2
29
29
  Requires-Dist: gunicorn==23.0.0
30
30
  Requires-Dist: markdown==3.9
31
- Requires-Dist: nltk==3.9.1
31
+ Requires-Dist: nltk==3.9.2
32
32
  Requires-Dist: python-dateutil==2.9.0
33
33
  Requires-Dist: rich==14.1.0
34
34
  Requires-Dist: sqlalchemy==2.0.43
@@ -159,7 +159,7 @@ Card balances are also visible by visiting the pages for individual statements.
159
159
  A full history of statements for each card is available off the homepage.
160
160
  Each statement's page gives the statement's balance, transactions, and due date.
161
161
 
162
- <img class="screenshot" src="https://raw.githubusercontent.com/mitchnegus/monopyly/main/monopyly/static/img/about/statement-details.png" alt="statement details" width="800px" />
162
+ <img class="screenshot" src="https://raw.githubusercontent.com/mitchnegus/monopyly/main/monopyly/static/img/about/credit-statement-details.png" alt="statement details" width="800px" />
163
163
 
164
164
  Payments can be made directly from a statement's page and can be linked to a bank account in the _Monopyly_ system for simplified tracking.
165
165
  (Note that even linked transactions must be edited independently, as there are times when a user may wish to have separate values for linked transactions. For example, a credit card payment may be processed on a given date while it is only registered as a bank account transaction several days later.)
@@ -222,4 +222,19 @@
222
222
  - Bump dependencies (including support for recent SQLAlchemy versions)
223
223
 
224
224
 
225
+ ### 1.6.0
226
+
227
+ - Enable additional transactions to be loaded on bank/credit transactions tables
228
+ - Create a script to take application screenshots (e.g., for the 'About' page)
229
+ - Remove Python version requirement (allow Python versions after 3.10)
230
+ - Use Jinja recursive loops for transaction tag tree structures in templates
231
+ - Refactor to include ruff-based linting checks
232
+ - Set 'Credit payments' to be a default tag for all users
233
+ - Protect globally defined tags from user deletion
234
+ - Hide transaction tags when the Escape key is pressed
235
+ - Ensure that the 'Record Transfer' functionality only ever adds one input box
236
+ - Update dependencies (including using ruff in place of _Black_ and _isort_)
237
+ - Update JavaScript libraries (jQuery, Chartist)
238
+
239
+
225
240
  <a name="bottom" id="bottom"></a>
@@ -123,7 +123,7 @@ Card balances are also visible by visiting the pages for individual statements.
123
123
  A full history of statements for each card is available off the homepage.
124
124
  Each statement's page gives the statement's balance, transactions, and due date.
125
125
 
126
- <img class="screenshot" src="monopyly/static/img/about/statement-details.png" alt="statement details" width="800px" />
126
+ <img class="screenshot" src="monopyly/static/img/about/credit-statement-details.png" alt="statement details" width="800px" />
127
127
 
128
128
  Payments can be made directly from a statement's page and can be linked to a bank account in the _Monopyly_ system for simplified tracking.
129
129
  (Note that even linked transactions must be edited independently, as there are times when a user may wish to have separate values for linked transactions. For example, a credit card payment may be processed on a given date while it is only registered as a bank account transaction several days later.)
@@ -8,7 +8,7 @@ from .core.errors import render_error_template
8
8
  from .database import SQLAlchemy
9
9
 
10
10
 
11
- @Factory(db_interface=SQLAlchemy)
11
+ @Factory(db_interface=SQLAlchemy, echo_engine=False)
12
12
  def create_app(config=None):
13
13
  """
14
14
  Create the Flask application.
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.5.2'
16
- __version_tuple__ = version_tuple = (1, 5, 2)
15
+ __version__ = version = '1.6.0'
16
+ __version_tuple__ = version_tuple = (1, 6, 0)
@@ -9,3 +9,5 @@ bp = Blueprint("auth", __name__, url_prefix="/auth")
9
9
 
10
10
  # Import routes after defining blueprint to avoid circular imports
11
11
  from . import routes
12
+
13
+ __all__ = ["routes"]
@@ -13,7 +13,6 @@ from flask import (
13
13
  session,
14
14
  url_for,
15
15
  )
16
- from sqlalchemy import select
17
16
  from werkzeug.security import check_password_hash, generate_password_hash
18
17
 
19
18
  from ..database.models import User
@@ -34,7 +33,7 @@ def register():
34
33
  error = "Username is required."
35
34
  elif not password:
36
35
  error = "Password is required."
37
- elif user := identify_user(username):
36
+ elif identify_user(username):
38
37
  error = f"User {username} is already registered."
39
38
  else:
40
39
  # Create a new user
@@ -4,7 +4,7 @@ Tools for interacting with bank accounts in the database.
4
4
 
5
5
  import sqlalchemy.sql.functions as sql_func
6
6
  from dry_foundation.database.handler import DatabaseViewHandler
7
- from werkzeug.exceptions import abort
7
+ from flask import abort
8
8
 
9
9
  from ..common.forms.utils import execute_on_form_validation
10
10
  from ..database.models import (
@@ -114,15 +114,15 @@ class BankAccountTypeHandler(
114
114
  super().delete_entry(entry_id)
115
115
 
116
116
  @classmethod
117
- def _validate_authorization(cls, entry_id):
118
- account_type = super()._validate_authorization(entry_id)
119
- # Limit manipulation to only the user (excluding common entries)
117
+ def _retrieve_authorized_manipulable_entry(cls, entry_id):
118
+ account_type = super()._retrieve_authorized_manipulable_entry(entry_id)
120
119
  if account_type.user_id != cls.user_id:
121
120
  abort_msg = (
122
121
  "The current user is not authorized to manipulate "
123
122
  "this account type entry."
124
123
  )
125
124
  abort(403, abort_msg)
125
+ return account_type
126
126
 
127
127
 
128
128
  class BankAccountHandler(
@@ -1,6 +1,6 @@
1
1
  """Module describing logical banking actions (to be used in routes)."""
2
2
 
3
- from collections import UserList, namedtuple
3
+ from collections import UserDict, namedtuple
4
4
 
5
5
  from ..common.utils import convert_date_to_midnight_timestamp
6
6
  from .accounts import BankAccountHandler, BankAccountTypeHandler
@@ -30,25 +30,26 @@ def get_balance_chart_data(transactions):
30
30
 
31
31
  Returns
32
32
  -------
33
- chart_data : list
34
- A list containing (x, y) pairs, each consisting of the Unix
33
+ chart_data : dict
34
+ A dictionary containing a Chartist compatible data structure,
35
+ including (x, y) pairs that each represent the Unix
35
36
  timestamp (in milliseconds) and the bank account balance.
36
37
  """
37
- return list(_BalanceChartData(transactions))
38
+ return _BalanceChartData(transactions).data
38
39
 
39
40
 
40
- class _BalanceChartData(UserList):
41
+ class _BalanceChartData(UserDict):
41
42
  """
42
- A list of balances to be passed to a `chartist.js` chart constructor.
43
+ A mapping of balances to be passed to a `chartist.js` chart constructor.
43
44
 
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.
45
+ A special dictionary-like object containing transaction data
46
+ formatted for use in a balance chart created by the `chartist.js`
47
+ library. This converts each transaction into an (x, y) pair
48
+ consisting of a Unix timestamp (in milleseconds) and a corresponding
49
+ bank account balance. For transactions occurring on the same day
50
+ (the finest granularity recorded by the Monopyly app), a slight
51
+ offset is added to each timestamp to guarantee a smooth
52
+ representation in the rendered chart.
52
53
 
53
54
  Parameters
54
55
  ----------
@@ -61,9 +62,9 @@ class _BalanceChartData(UserList):
61
62
  point = namedtuple("DataPoint", ["timestamp", "balance"])
62
63
 
63
64
  def __init__(self, transactions):
64
- super().__init__()
65
65
  transaction_groups = self._group_transactions_by_date(transactions)
66
- self._prepare_chart_data(transaction_groups)
66
+ chart_data = self._prepare_chart_data(transaction_groups)
67
+ super().__init__({"series": [{"name": "balances", "data": chart_data}]})
67
68
 
68
69
  @staticmethod
69
70
  def _group_transactions_by_date(transactions):
@@ -75,6 +76,7 @@ class _BalanceChartData(UserList):
75
76
 
76
77
  def _prepare_chart_data(self, transaction_groups):
77
78
  # Assign chart data to the list as tuples, adding offsets for duplicated dates
79
+ chart_data = []
78
80
  for transaction_date, transaction_group in transaction_groups.items():
79
81
  base_timestamp = convert_date_to_midnight_timestamp(
80
82
  transaction_date, milliseconds=True
@@ -82,4 +84,5 @@ class _BalanceChartData(UserList):
82
84
  offset = self._DAILY_MILLISECONDS / len(transaction_group)
83
85
  for i, transaction in enumerate(transaction_group):
84
86
  adjusted_timestamp = base_timestamp + (i * offset)
85
- self.data.append((adjusted_timestamp, transaction.balance))
87
+ chart_data.append({"x": adjusted_timestamp, "y": transaction.balance})
88
+ return chart_data
@@ -9,3 +9,5 @@ bp = Blueprint("banking", __name__, url_prefix="/banking")
9
9
 
10
10
  # Import routes after defining blueprint to avoid circular imports
11
11
  from . import filters, routes
12
+
13
+ __all__ = ["filters", "routes"]
@@ -6,14 +6,14 @@ from .blueprint import bp
6
6
 
7
7
 
8
8
  @bp.app_template_filter("is_single_bank_transfer")
9
- def check_transfer_is_within_bank(transaction):
10
- """Check if the transfer is linked ot another transaction at the same bank."""
11
- if transaction.internal_transaction:
12
- linked_bank_transactions = transaction.internal_transaction.bank_transactions
9
+ def check_transfer_is_within_bank(transaction_view):
10
+ """Check if the transfer is linked to another transaction at the same bank."""
11
+ if internal_transaction := transaction_view.internal_transaction:
12
+ linked_bank_transactions = internal_transaction.bank_transaction_views
13
13
  if len(linked_bank_transactions) > 1:
14
- common_bank_id = linked_bank_transactions[0].account.bank_id
14
+ common_bank_id = linked_bank_transactions[0].account_view.bank_id
15
15
  return all(
16
- transaction.account.bank_id == common_bank_id
16
+ transaction.account_view.bank_id == common_bank_id
17
17
  for transaction in linked_bank_transactions
18
18
  )
19
19
  return False
@@ -2,18 +2,16 @@
2
2
  Generate banking forms for the user to complete.
3
3
  """
4
4
 
5
- from wtforms.fields import BooleanField, FieldList, FormField, StringField, SubmitField
5
+ from wtforms.fields import BooleanField, FieldList, FormField, SubmitField
6
6
  from wtforms.validators import DataRequired, Optional
7
7
 
8
8
  from ..common.forms import AcquisitionSubform, EntryForm, EntrySubform, TransactionForm
9
9
  from ..common.forms.fields import (
10
10
  CustomChoiceSelectField,
11
- DateField,
12
11
  LastFourDigitsField,
13
12
  StringField,
14
13
  )
15
14
  from ..common.forms.utils import Autocompleter
16
- from ..common.utils import parse_date
17
15
  from ..database.models import (
18
16
  Bank,
19
17
  BankAccountTypeView,
@@ -179,7 +177,7 @@ class BankTransactionForm(TransactionForm):
179
177
  data = {
180
178
  "bank_name": entry.bank.bank_name,
181
179
  "last_four_digits": entry.last_four_digits,
182
- "type_name": entry.account_type.type_name,
180
+ "type_name": entry.account_type_view.type_name,
183
181
  }
184
182
  elif isinstance(entry, Bank):
185
183
  data = {"bank_name": entry.bank_name}
@@ -278,7 +276,7 @@ class BankTransactionForm(TransactionForm):
278
276
  """Gather data for the form from the given database entry."""
279
277
  if isinstance(entry, BankTransactionView):
280
278
  data = self._gather_transaction_data(entry)
281
- account_info = entry.account
279
+ account_info = entry.account_view
282
280
  # Do not prepopulate any transfer information
283
281
  elif isinstance(entry, (BankAccountView, Bank)):
284
282
  data = {}
@@ -3,7 +3,7 @@ Routes for banking financials.
3
3
  """
4
4
 
5
5
  from dry_foundation.database import db_transaction
6
- from flask import jsonify, redirect, render_template, request, url_for
6
+ from flask import g, jsonify, redirect, render_template, request, url_for
7
7
 
8
8
  from ..auth.tools import login_required
9
9
  from ..common.forms.utils import extend_field_list_for_ajax
@@ -13,7 +13,10 @@ from .actions import get_balance_chart_data, get_bank_account_type_grouping
13
13
  from .banks import BankHandler
14
14
  from .blueprint import bp
15
15
  from .forms import BankAccountForm, BankTransactionForm
16
- from .transactions import BankTransactionHandler, save_transaction
16
+ from .transactions import BankTagHandler, BankTransactionHandler, save_transaction
17
+
18
+ # Set a limit on the number of transactions loaded at one time for certain routes
19
+ TRANSACTION_LIMIT = 100
17
20
 
18
21
 
19
22
  @bp.route("/accounts")
@@ -34,7 +37,7 @@ def add_account(bank_id):
34
37
  form = BankAccountForm()
35
38
  # Check if an account was submitted and add it to the database
36
39
  if request.method == "POST":
37
- account = save_account(form)
40
+ save_account(form)
38
41
  return redirect(url_for("banking.load_accounts"))
39
42
  else:
40
43
  if bank_id:
@@ -69,21 +72,39 @@ def load_account_summaries(bank_id):
69
72
  @login_required
70
73
  def load_account_details(account_id):
71
74
  account = BankAccountHandler.get_entry(account_id)
72
- transactions = list(
73
- BankTransactionHandler.get_transactions(
74
- account_ids=(account_id,), sort_order="DESC"
75
- )
76
- )
75
+ transactions = BankTransactionHandler.get_transactions(
76
+ account_ids=(account_id,), sort_order="DESC"
77
+ ).all()
77
78
  # Only display the first 100 transactions
78
79
  return render_template(
79
80
  "banking/account_page.html",
80
81
  account=account,
81
- account_transactions=transactions[:100],
82
+ transactions=transactions[:100],
83
+ total_transactions=len(transactions),
82
84
  # Reverse the chart transactions to be chronologically ascending
83
85
  chart_data=get_balance_chart_data(reversed(transactions)),
84
86
  )
85
87
 
86
88
 
89
+ @bp.route("/_extra_transactions", methods=("POST",))
90
+ @login_required
91
+ def load_more_transactions():
92
+ # Get info about the transactions being displayed from the AJAX request
93
+ post_args = request.get_json()
94
+ account_id = post_args["account_id"]
95
+ block_index = post_args["block_count"] - 1
96
+ # Get a subset of the remaining transactions to load
97
+ more_transactions = BankTransactionHandler.get_transactions(
98
+ account_ids=(account_id,),
99
+ offset=block_index * TRANSACTION_LIMIT,
100
+ limit=TRANSACTION_LIMIT,
101
+ )
102
+ return render_template(
103
+ "banking/transactions_table/transactions.html",
104
+ transactions=more_transactions,
105
+ )
106
+
107
+
87
108
  @bp.route("/_expand_transaction", methods=("POST",))
88
109
  @login_required
89
110
  def expand_transaction():
@@ -216,6 +237,47 @@ def delete_transaction(transaction_id):
216
237
  return redirect(url_for("banking.load_account_details", account_id=account_id))
217
238
 
218
239
 
240
+ @bp.route("/tags")
241
+ @login_required
242
+ def load_tags():
243
+ # Get the tag hierarchy from the database
244
+ hierarchy = BankTagHandler.get_hierarchy()
245
+ return render_template("common/tags_page.html", tags_hierarchy=hierarchy)
246
+
247
+
248
+ @bp.route("/_add_tag", methods=("POST",))
249
+ @login_required
250
+ @db_transaction
251
+ def add_tag():
252
+ # Get the new tag (and potentially parent category) from the AJAX request
253
+ post_args = request.get_json()
254
+ tag_name = post_args["tag_name"]
255
+ parent_name = post_args.get("parent")
256
+ # Check that the tag name does not already exist
257
+ if BankTagHandler.get_tags(tag_names=(tag_name,)):
258
+ raise ValueError("The given tag name already exists. Tag names must be unique.")
259
+ parent_id = BankTagHandler.find_tag(parent_name).id if parent_name else None
260
+ tag = BankTagHandler.add_entry(
261
+ parent_id=parent_id,
262
+ user_id=g.user.id,
263
+ tag_name=tag_name,
264
+ )
265
+ return render_template("common/tag_tree.html", tags_hierarchy={tag: []})
266
+
267
+
268
+ @bp.route("/_delete_tag", methods=("POST",))
269
+ @login_required
270
+ @db_transaction
271
+ def delete_tag():
272
+ # Get the tag to be deleted from the AJAX request
273
+ post_args = request.get_json()
274
+ tag_name = post_args["tag_name"]
275
+ tag = BankTagHandler.find_tag(tag_name)
276
+ # Remove the tag from the database
277
+ BankTagHandler.delete_entry(tag.id)
278
+ return ""
279
+
280
+
219
281
  @bp.route("/_suggest_transaction_autocomplete", methods=("POST",))
220
282
  @login_required
221
283
  def suggest_transaction_autocomplete():
@@ -8,7 +8,6 @@ from ..common.forms.utils import execute_on_form_validation
8
8
  from ..common.transactions import TransactionHandler, TransactionTagHandler
9
9
  from ..core.internal_transactions import add_internal_transaction
10
10
  from ..database.models import (
11
- Bank,
12
11
  BankAccountView,
13
12
  BankSubtransaction,
14
13
  BankTransaction,
@@ -80,7 +79,7 @@ class BankTransactionHandler(
80
79
  criteria.add_match_filter(cls.model, "account_id", account_ids)
81
80
  criteria.add_match_filter(BankAccountView, "active", active)
82
81
  transactions = super()._get_transactions(
83
- criteria=criteria, sort_order=sort_order, offset=None, limit=None
82
+ criteria=criteria, sort_order=sort_order, offset=offset, limit=limit
84
83
  )
85
84
  return transactions
86
85
 
@@ -214,7 +213,7 @@ def save_transaction(form, transaction_id=None):
214
213
  transfer = record_new_transfer(transfer_data)
215
214
  transaction_data.update(
216
215
  internal_transaction_id=transfer.internal_transaction_id,
217
- merchant=transfer.account.bank.bank_name,
216
+ merchant=transfer.account_view.bank.bank_name,
218
217
  )
219
218
  transaction = BankTransactionHandler.add_entry(**transaction_data)
220
219
  return transaction
@@ -0,0 +1,15 @@
1
+ from ._forms import (
2
+ AcquisitionSubform,
3
+ EntryForm,
4
+ EntrySubform,
5
+ TransactionForm,
6
+ form_err_msg,
7
+ )
8
+
9
+ __all__ = [
10
+ "AcquisitionSubform",
11
+ "EntryForm",
12
+ "EntrySubform",
13
+ "TransactionForm",
14
+ "form_err_msg",
15
+ ]
@@ -6,11 +6,10 @@ from abc import ABC, abstractmethod
6
6
  from datetime import date
7
7
 
8
8
  from flask_wtf import FlaskForm
9
- from wtforms.fields import FieldList, FormField, SelectField, StringField, SubmitField
9
+ from wtforms.fields import StringField, SubmitField
10
10
  from wtforms.validators import DataRequired
11
11
 
12
12
  from .fields import CurrencyField, DateField
13
- from .validators import SelectionNotBlank
14
13
 
15
14
  # Define a custom form error messaage
16
15
  form_err_msg = "There was an improper value in your form. Please try again."
@@ -4,12 +4,10 @@ General form constructions.
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
 
7
- from flask_wtf import FlaskForm
8
7
  from wtforms import fields as wtforms_fields
9
8
  from wtforms.validators import Length
10
9
  from wtforms.widgets import NumberInput
11
10
 
12
- from ...banking.banks import BankHandler
13
11
  from ..utils import parse_date
14
12
  from .validators import NumeralsOnly, SelectionNotBlank
15
13
 
@@ -75,7 +75,7 @@ class Autocompleter:
75
75
  # Get information from the database to use for autocompletion
76
76
  query = model.select_for_user(getattr(model, field))
77
77
  values = current_app.db.session.scalars(query)
78
- suggestions = sort_by_frequency([value for value in values])
78
+ suggestions = sort_by_frequency(list(values))
79
79
  return suggestions
80
80
 
81
81
  def _sort_suggestions_by_field(
@@ -2,10 +2,8 @@
2
2
  Tools for building a common transaction interface.
3
3
  """
4
4
 
5
- from abc import abstractmethod
6
-
7
5
  from dry_foundation.database.handler import DatabaseHandler, DatabaseViewHandler
8
- from flask import current_app
6
+ from flask import abort, current_app
9
7
 
10
8
  from ..database.models import (
11
9
  BankAccountTypeView,
@@ -86,7 +84,26 @@ class TransactionHandler(DatabaseViewHandler):
86
84
 
87
85
  @classmethod
88
86
  def update_entry(cls, entry_id, **field_values):
89
- """Update a transaction in the database."""
87
+ """
88
+ Update a transaction in the database.
89
+
90
+ Accept a mapping relating given inputs to database fields. This
91
+ mapping is used to update an existing transaction in the
92
+ database. All fields are sanitized prior to updating, and any
93
+ subtransactions are identified for individual processing.
94
+
95
+ Parameters
96
+ ----------
97
+ entry_id : int
98
+ The ID of the transaction to be updated.
99
+ **field_values :
100
+ Values for the fields to update in the transaction.
101
+
102
+ Returns
103
+ -------
104
+ transaction : database.models.BankTransaction
105
+ The saved transaction.
106
+ """
90
107
  # Extend the default method to account for subtransactions
91
108
  subtransactions_data = field_values.pop("subtransactions", None)
92
109
  transaction = super().update_entry(entry_id, **field_values)
@@ -110,6 +127,30 @@ class TransactionHandler(DatabaseViewHandler):
110
127
  # Flush to the database after all subtransactions have been added
111
128
  cls._db.session.flush()
112
129
 
130
+ @classmethod
131
+ def delete_entry(cls, entry_id):
132
+ """
133
+ Delete a transaction in the database given its ID.
134
+
135
+ Parameters
136
+ ----------
137
+ entry_id : int
138
+ The ID of the transaction to be deleted.
139
+
140
+ Notes
141
+ -----
142
+ This will also delete any internal transactions associated with
143
+ this transaction, since the internal transaction link no longer
144
+ exists.
145
+ """
146
+ internal_transaction = cls.get_entry(entry_id).internal_transaction
147
+ super().delete_entry(entry_id)
148
+ if internal_transaction:
149
+ cls._db.session.refresh(internal_transaction)
150
+ if len(internal_transaction.transaction_views) <= 1:
151
+ cls._db.session.delete(internal_transaction)
152
+ cls._db.session.flush()
153
+
113
154
 
114
155
  def get_linked_transaction(transaction):
115
156
  """
@@ -369,6 +410,25 @@ class TransactionTagHandler(DatabaseHandler, model=TransactionTag):
369
410
  tag = cls._db.session.execute(query).scalar_one_or_none()
370
411
  return tag
371
412
 
413
+ @classmethod
414
+ def delete_entry(cls, entry_id):
415
+ """
416
+ Delete the tag in the database given its ID.
417
+
418
+ Parameters
419
+ ----------
420
+ entry_id : int
421
+ The ID of the tag to be deleted.
422
+ """
423
+ super().delete_entry(entry_id)
424
+
425
+ @classmethod
426
+ def _retrieve_authorized_manipulable_entry(cls, entry_id):
427
+ tag = super()._retrieve_authorized_manipulable_entry(entry_id)
428
+ if tag.user_id != cls.user_id:
429
+ abort(403, "The current user is not authorized to manipulate this tag.")
430
+ return tag
431
+
372
432
 
373
433
  def categorize(transactions):
374
434
  """
@@ -412,6 +472,11 @@ class CategoryTree:
412
472
  """
413
473
  Store a tree of categories.
414
474
 
475
+ The category tree is a tree of categorized subtransactions. Each
476
+ leaf of the tree represents a transaction tag and the
477
+ subtransactions that have been categorized according to that tag
478
+ (the category).
479
+
415
480
  Parameters
416
481
  ----------
417
482
  category : database.models.TransactionTag, str
@@ -480,9 +545,9 @@ class RootCategoryTree(CategoryTree):
480
545
  Given a subtransaction, add that subtransaction to the category
481
546
  tree according to its tags. If multiple tags exist at the same
482
547
  level of the tree (i.e., a subtransaction with tags in diverging
483
- branches), the tag is determined to be "uncategorizable" and the
484
- tag is listed only as a member of the root tree and not as a
485
- member of any other subcategory tree.
548
+ branches), the subtransaction is determined to be "uncategorizable"
549
+ and the tag is listed only as a member of the root tree and not
550
+ as a member of any other subcategory tree.
486
551
 
487
552
  Parameters
488
553
  ----------
@@ -4,6 +4,7 @@ import markdown
4
4
 
5
5
 
6
6
  class MarkdownConverter:
7
+ """An object to convert Markdown to HTML."""
7
8
 
8
9
  replacements = {
9
10
  "src": [
@@ -71,7 +72,7 @@ def convert_readme_to_html_template(readme_path):
71
72
  '<div class="resource-links">'
72
73
  " <h2>Links</h2>"
73
74
  ' <p><a href="{{ url_for("core.story") }}">Story</a></p>'
74
- ' <p><a href="{{ url_for("core.credits") }}">Credits</a></p>'
75
+ ' <p><a href="{{ url_for("core.application_credits") }}">Credits</a></p>'
75
76
  "</div>"
76
77
  ),
77
78
  )
@@ -9,3 +9,5 @@ bp = Blueprint("core", __name__)
9
9
 
10
10
  # Import routes after defining blueprint to avoid circular imports
11
11
  from . import context_processors, errors, filters, routes
12
+
13
+ __all__ = ["context_processors", "errors", "filters", "routes"]
@@ -2,8 +2,6 @@
2
2
  Filters defined for the application.
3
3
  """
4
4
 
5
- from math import floor, log10
6
-
7
5
  from .blueprint import bp
8
6
 
9
7
 
@@ -74,7 +74,7 @@ def story():
74
74
 
75
75
 
76
76
  @bp.route("/credits")
77
- def credits():
77
+ def application_credits():
78
78
  return render_template("core/credits.html")
79
79
 
80
80