monopyly 1.5.0__tar.gz → 1.5.1__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 (237) hide show
  1. {monopyly-1.5.0 → monopyly-1.5.1}/PKG-INFO +8 -8
  2. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/CHANGELOG.md +11 -0
  3. monopyly-1.5.1/monopyly/_version.py +16 -0
  4. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/auth/actions.py +12 -0
  5. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/auth/routes.py +30 -21
  6. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/auth/tools.py +1 -2
  7. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/forms/utils.py +1 -2
  8. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/routes.py +0 -6
  9. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/forms.py +1 -1
  10. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/routes.py +19 -1
  11. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/transactions/activity/parser.py +14 -6
  12. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/css/style.css +191 -11
  13. monopyly-1.5.1/monopyly/templates/auth/change_password.html +21 -0
  14. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/auth/login.html +3 -1
  15. monopyly-1.5.1/monopyly/templates/auth/register.html +25 -0
  16. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/profile.html +2 -2
  17. monopyly-1.5.1/monopyly/templates/credit/transaction_submission_page.html +89 -0
  18. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/layout.html +2 -2
  19. {monopyly-1.5.0 → monopyly-1.5.1}/pyproject.toml +6 -6
  20. monopyly-1.5.0/monopyly/_version.py +0 -4
  21. monopyly-1.5.0/monopyly/templates/auth/register.html +0 -15
  22. monopyly-1.5.0/monopyly/templates/credit/transaction_submission_page.html +0 -96
  23. {monopyly-1.5.0 → monopyly-1.5.1}/.gitignore +0 -0
  24. {monopyly-1.5.0 → monopyly-1.5.1}/COPYING +0 -0
  25. {monopyly-1.5.0 → monopyly-1.5.1}/LICENSE +0 -0
  26. {monopyly-1.5.0 → monopyly-1.5.1}/README.md +0 -0
  27. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/README.md +0 -0
  28. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/__init__.py +0 -0
  29. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/auth/blueprint.py +0 -0
  30. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/accounts.py +0 -0
  31. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/actions.py +0 -0
  32. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/banks.py +0 -0
  33. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/blueprint.py +0 -0
  34. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/filters.py +0 -0
  35. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/forms.py +0 -0
  36. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/routes.py +0 -0
  37. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/banking/transactions.py +0 -0
  38. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/cli/apps.py +0 -0
  39. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/cli/launch.py +0 -0
  40. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/forms/__init__.py +0 -0
  41. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/forms/_forms.py +0 -0
  42. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/forms/fields.py +0 -0
  43. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/forms/validators.py +0 -0
  44. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/transactions.py +0 -0
  45. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/common/utils.py +0 -0
  46. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/config/__init__.py +0 -0
  47. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/config/default_settings.py +0 -0
  48. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/config/settings.py +0 -0
  49. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/actions.py +0 -0
  50. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/blueprint.py +0 -0
  51. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/context_processors.py +0 -0
  52. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/errors.py +0 -0
  53. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/filters.py +0 -0
  54. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/core/internal_transactions.py +0 -0
  55. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/accounts.py +0 -0
  56. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/actions.py +0 -0
  57. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/blueprint.py +0 -0
  58. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/cards.py +0 -0
  59. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/statements.py +0 -0
  60. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/transactions/__init__.py +0 -0
  61. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/transactions/_transactions.py +0 -0
  62. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/transactions/activity/__init__.py +0 -0
  63. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/transactions/activity/data.py +0 -0
  64. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/credit/transactions/activity/reconciliation.py +0 -0
  65. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/database/__init__.py +0 -0
  66. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/database/models.py +0 -0
  67. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/database/preloads.sql +0 -0
  68. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/database/schema.sql +0 -0
  69. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/database/views.sql +0 -0
  70. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/browserconfig.xml +0 -0
  71. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-114.png +0 -0
  72. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-120.png +0 -0
  73. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-144.png +0 -0
  74. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-150.png +0 -0
  75. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-152.png +0 -0
  76. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-16.png +0 -0
  77. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-160.png +0 -0
  78. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-180.png +0 -0
  79. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-192.png +0 -0
  80. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-310.png +0 -0
  81. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-32.png +0 -0
  82. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-57.png +0 -0
  83. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-60.png +0 -0
  84. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-64.png +0 -0
  85. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-70.png +0 -0
  86. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-72.png +0 -0
  87. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-76.png +0 -0
  88. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon-96.png +0 -0
  89. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon.ico +0 -0
  90. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/favicon/favicon.png +0 -0
  91. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/bank-account-details.png +0 -0
  92. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/bank-account-summaries.png +0 -0
  93. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/bank-accounts.png +0 -0
  94. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/credit-account-details.png +0 -0
  95. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/credit-transactions.png +0 -0
  96. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/homepage-user.png +0 -0
  97. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/homepage.png +0 -0
  98. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/about/statement-details.png +0 -0
  99. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/cards/chase-card.png +0 -0
  100. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/cards/discover-card.png +0 -0
  101. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/cards/new-card.png +0 -0
  102. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/cards/template-card.png +0 -0
  103. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/arrow-down.png +0 -0
  104. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/arrow-left.png +0 -0
  105. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/arrow-up.png +0 -0
  106. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/checkmark.png +0 -0
  107. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/delete-orange-thick.png +0 -0
  108. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/delete-orange.png +0 -0
  109. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/delete-thick.png +0 -0
  110. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/delete.png +0 -0
  111. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/edit.png +0 -0
  112. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/link.png +0 -0
  113. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/minus-thick.png +0 -0
  114. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/minus.png +0 -0
  115. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/plus-thick.png +0 -0
  116. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/plus.png +0 -0
  117. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/refresh.png +0 -0
  118. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/save.png +0 -0
  119. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/sort-asc.png +0 -0
  120. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/sort-desc.png +0 -0
  121. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/statement-pair.png +0 -0
  122. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/statement-thick.png +0 -0
  123. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/statement.png +0 -0
  124. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/icons/x-thick.png +0 -0
  125. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/img/statement.png +0 -0
  126. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/jquery-3.7.0.min.js +0 -0
  127. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/add-subtransaction.js +0 -0
  128. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/add-transfer.js +0 -0
  129. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/autocomplete-transaction.js +0 -0
  130. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/bind-tag-actions.js +0 -0
  131. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/create-balance-chart.js +0 -0
  132. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/create-category-chart.js +0 -0
  133. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/define-filter.js +0 -0
  134. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/display-new-account-type-inputs.js +0 -0
  135. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/display-new-bank-inputs.js +0 -0
  136. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/display-new-credit-account-inputs.js +0 -0
  137. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/expand-bank-account.js +0 -0
  138. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/expand-bank.js +0 -0
  139. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/expand-transaction.js +0 -0
  140. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/flip-card.js +0 -0
  141. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/hide-homepage-block.js +0 -0
  142. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/highlight-discrepant-transactions.js +0 -0
  143. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/infer-card.js +0 -0
  144. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/infer-statement.js +0 -0
  145. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/make-payment.js +0 -0
  146. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/ajax.js +0 -0
  147. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/autocomplete-input.js +0 -0
  148. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/expand-box-row.js +0 -0
  149. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/expand-transaction.js +0 -0
  150. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/form-suggestions.js +0 -0
  151. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/manage-acquisition-form.js +0 -0
  152. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/manage-overlays.js +0 -0
  153. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/manage-subforms.js +0 -0
  154. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/update-database-widget.js +0 -0
  155. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/modules/update-display-ajax.js +0 -0
  156. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/show-credit-activity-loader.js +0 -0
  157. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/show-linked-transaction.js +0 -0
  158. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/toggle-navigation.js +0 -0
  159. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/update-account-statement-parameters.js +0 -0
  160. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/update-bank-name.js +0 -0
  161. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/update-card-status.js +0 -0
  162. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/update-statement-parameters.js +0 -0
  163. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/update-statements-display.js +0 -0
  164. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/update-transactions-display.js +0 -0
  165. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/use-suggested-amount.js +0 -0
  166. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/static/js/use-suggested-merchant.js +0 -0
  167. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/account_form/account_form.html +0 -0
  168. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/account_form/account_form_page_new.html +0 -0
  169. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/account_page.html +0 -0
  170. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/account_summaries.html +0 -0
  171. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/account_summaries_page.html +0 -0
  172. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/account_summary.html +0 -0
  173. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/accounts_page.html +0 -0
  174. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transaction_form/bank_info_form.html +0 -0
  175. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transaction_form/transaction_form.html +0 -0
  176. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transaction_form/transaction_form_page.html +0 -0
  177. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transaction_form/transaction_form_page_new.html +0 -0
  178. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transaction_form/transaction_form_page_update.html +0 -0
  179. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transaction_form/transfer_form.html +0 -0
  180. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transactions_table/condensed_row_content.html +0 -0
  181. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transactions_table/expanded_row_content.html +0 -0
  182. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transactions_table/transaction_field_titles.html +0 -0
  183. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/banking/transactions_table/transactions.html +0 -0
  184. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/form_page.html +0 -0
  185. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transaction_form/subform.html +0 -0
  186. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transaction_form/subtransaction_subform.html +0 -0
  187. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transaction_form/transaction_form_page.html +0 -0
  188. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/linked_bank_transaction.html +0 -0
  189. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/linked_credit_transaction.html +0 -0
  190. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/linked_transaction_overlay.html +0 -0
  191. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/subtransactions.html +0 -0
  192. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/transaction_condensed.html +0 -0
  193. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/transaction_expanded.html +0 -0
  194. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/common/transactions_table/transactions.html +0 -0
  195. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/credits.html +0 -0
  196. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/400.html +0 -0
  197. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/401.html +0 -0
  198. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/403.html +0 -0
  199. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/404.html +0 -0
  200. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/405.html +0 -0
  201. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/408.html +0 -0
  202. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/418.html +0 -0
  203. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/425.html +0 -0
  204. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/500.html +0 -0
  205. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/errors/error.html +0 -0
  206. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/index.html +0 -0
  207. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/core/story.html +0 -0
  208. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/account_page.html +0 -0
  209. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/card_form/card_form.html +0 -0
  210. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/card_form/card_form_page_new.html +0 -0
  211. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/card_form/transfer_statement_inquiry.html +0 -0
  212. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/card_graphic/card_back.html +0 -0
  213. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/card_graphic/card_front.html +0 -0
  214. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/card_submission_page.html +0 -0
  215. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/cards.html +0 -0
  216. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/cards_page.html +0 -0
  217. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_page.html +0 -0
  218. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_reconciliation/discrepant_records.html +0 -0
  219. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_inquiry.html +0 -0
  220. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_reconciliation/statement_reconciliation_page.html +0 -0
  221. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_reconciliation/summary.html +0 -0
  222. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_reconciliation/unrecorded_activities.html +0 -0
  223. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statement_summary.html +0 -0
  224. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statements.html +0 -0
  225. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/statements_page.html +0 -0
  226. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/tag_tree/subtag_tree.html +0 -0
  227. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/tag_tree/tag_tree.html +0 -0
  228. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/tags_page.html +0 -0
  229. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transaction_form/transaction_form.html +0 -0
  230. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transaction_form/transaction_form_page.html +0 -0
  231. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transaction_form/transaction_form_page_new.html +0 -0
  232. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transaction_form/transaction_form_page_update.html +0 -0
  233. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transactions_page.html +0 -0
  234. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transactions_table/condensed_row_content.html +0 -0
  235. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transactions_table/expanded_row_content.html +0 -0
  236. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transactions_table/transaction_field_titles.html +0 -0
  237. {monopyly-1.5.0 → monopyly-1.5.1}/monopyly/templates/credit/transactions_table/transactions.html +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: monopyly
3
- Version: 1.5.0
3
+ Version: 1.5.1
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,16 @@ 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: authanor==1.1.0
26
+ Requires-Dist: authanor==1.1.1
27
27
  Requires-Dist: flask-wtf==1.2.1
28
28
  Requires-Dist: flask==3.0.3
29
29
  Requires-Dist: fuisce==1.0.2
30
- Requires-Dist: gunicorn==22.0.0
31
- Requires-Dist: markdown==3.6
32
- Requires-Dist: nltk==3.8.1
30
+ Requires-Dist: gunicorn==23.0.0
31
+ Requires-Dist: markdown==3.7
32
+ Requires-Dist: nltk==3.9.1
33
33
  Requires-Dist: python-dateutil==2.9.0
34
- Requires-Dist: rich==13.7.1
35
- Requires-Dist: sqlalchemy==2.0.29
34
+ Requires-Dist: rich==13.8.1
35
+ Requires-Dist: sqlalchemy==2.0.35
36
36
  Description-Content-Type: text/markdown
37
37
 
38
38
  <div id="header">
@@ -199,4 +199,15 @@
199
199
  - Create a tool for reconciling credit card transactions with information collected from external resources (e.g., CSVs downloaded from a user's online credit card account)
200
200
 
201
201
 
202
+ ### 1.5.1
203
+
204
+ - Bump dependencies (including patching security vulnerability in NLTK)
205
+ - Style credit transaction submissions as receipts
206
+ - Style flash messages according to content
207
+ - Return to statement details page after deleting a transaction (rather than returning to the general transactions page)
208
+ - Allow users to change their password
209
+ - Warn users before form submission if the configuration currently disallows registration
210
+ - Increase the flexibility of the credit activity parser
211
+
212
+
202
213
  <a name="bottom" id="bottom"></a>
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '1.5.1'
16
+ __version_tuple__ = version_tuple = (1, 5, 1)
@@ -1,5 +1,10 @@
1
1
  """Module describing logical authorization actions (to be used in routes)."""
2
2
 
3
+ from flask import current_app
4
+ from sqlalchemy import select
5
+
6
+ from ..database.models import User
7
+
3
8
 
4
9
  def get_username_and_password(form):
5
10
  """
@@ -11,3 +16,10 @@ def get_username_and_password(form):
11
16
  username = form["username"].lower()
12
17
  password = form["password"]
13
18
  return username, password
19
+
20
+
21
+ def identify_user(username):
22
+ """Identify the user in the database based on the username."""
23
+ user_query = select(User).where(User.username == username)
24
+ user = current_app.db.session.scalar(user_query)
25
+ return user
@@ -5,6 +5,7 @@ Routes for site authentication.
5
5
  from flask import (
6
6
  current_app,
7
7
  flash,
8
+ g,
8
9
  redirect,
9
10
  render_template,
10
11
  request,
@@ -16,7 +17,7 @@ from sqlalchemy import select
16
17
  from werkzeug.security import check_password_hash, generate_password_hash
17
18
 
18
19
  from ..database.models import User
19
- from .actions import get_username_and_password
20
+ from .actions import get_username_and_password, identify_user
20
21
  from .blueprint import bp
21
22
 
22
23
 
@@ -33,24 +34,17 @@ def register():
33
34
  error = "Username is required."
34
35
  elif not password:
35
36
  error = "Password is required."
37
+ elif user := identify_user(username):
38
+ error = f"User {username} is already registered."
36
39
  else:
37
- # Get user information from the database
38
- user_query = select(User).where(User.username == username)
39
- user = current_app.db.session.scalar(user_query)
40
- if user:
41
- error = f"User {username} is already registered."
42
- else:
43
- error = None
44
- # Add the username and hashed password to the database
45
- if not error:
40
+ # Create a new user
46
41
  new_user = User(
47
42
  username=username,
48
43
  password=generate_password_hash(password),
49
44
  )
50
45
  current_app.db.session.add(new_user)
51
46
  return redirect(url_for("auth.login"))
52
- else:
53
- flash(error)
47
+ flash(error)
54
48
  # Display the registration page
55
49
  return render_template("auth/register.html")
56
50
 
@@ -60,23 +54,17 @@ def login():
60
54
  if request.method == "POST":
61
55
  # Get username and passwords from the form
62
56
  username, password = get_username_and_password(request.form)
63
- # Get user information from the database
64
- user_query = select(User).where(User.username == username)
65
- user = current_app.db.session.scalar(user_query)
66
57
  # Check for errors in the accessed information
67
- if user is None:
58
+ if (user := identify_user(username)) is None:
68
59
  error = "That user is not yet registered."
69
60
  elif not check_password_hash(user.password, password):
70
61
  error = "Incorrect username and password combination."
71
62
  else:
72
- error = None
73
- # Set the user ID securely for a new session
74
- if not error:
63
+ # Set the user ID securely for a new session
75
64
  session.clear()
76
65
  session["user_id"] = user.id
77
66
  return redirect(url_for("core.index"))
78
- else:
79
- flash(error)
67
+ flash(error)
80
68
  # Display the login page
81
69
  return render_template("auth/login.html")
82
70
 
@@ -86,3 +74,24 @@ def logout():
86
74
  # End the session and clear the user ID
87
75
  session.clear()
88
76
  return redirect(url_for("core.index"))
77
+
78
+
79
+ @bp.route("/change_password", methods=("GET", "POST"))
80
+ @db_transaction
81
+ def change_password():
82
+ if request.method == "POST":
83
+ current_password = request.form["current-password"]
84
+ new_password = request.form["new-password"]
85
+ if check_password_hash(g.user.password, current_password):
86
+ # Merge the user item (dissociated from the current session) for updating
87
+ g.user = current_app.db.session.merge(g.user)
88
+ g.user.password = generate_password_hash(new_password)
89
+ flash("Password updated successfully.", category="success")
90
+ return redirect(url_for("core.load_profile"))
91
+ else:
92
+ flash(
93
+ "The provided value for the current password does not match the "
94
+ "value of the current password set on this account.",
95
+ category="error",
96
+ )
97
+ return render_template("auth/change_password.html")
@@ -14,8 +14,7 @@ from .blueprint import bp
14
14
  @bp.before_app_request
15
15
  def load_logged_in_user():
16
16
  # Match the user's information with the session
17
- user_id = session.get("user_id")
18
- if user_id is None:
17
+ if (user_id := session.get("user_id")) is None:
19
18
  g.user = None
20
19
  else:
21
20
  query = select(User).where(User.id == user_id)
@@ -145,7 +145,6 @@ def execute_on_form_validation(func):
145
145
  else:
146
146
  # Show an error to the user and print the errors for the admin
147
147
  flash(form_err_msg)
148
- print(form.errors)
149
- raise ValidationError("The form did not validate properly.")
148
+ raise ValidationError(f"The form did not validate properly: {form.errors}")
150
149
 
151
150
  return wrapper
@@ -5,7 +5,6 @@ Routes for core functionality.
5
5
  from pathlib import Path
6
6
 
7
7
  from flask import g, render_template, render_template_string, session
8
- from werkzeug.exceptions import abort
9
8
 
10
9
  from ..auth.tools import login_required
11
10
  from ..banking.accounts import BankAccountHandler
@@ -85,8 +84,3 @@ def load_profile():
85
84
  banks = BankHandler.get_banks()
86
85
  # Return banks as a list to allow multiple reuse
87
86
  return render_template("core/profile.html", banks=list(banks))
88
-
89
-
90
- @bp.route("/change_password")
91
- def change_password():
92
- abort(418)
@@ -301,7 +301,7 @@ class CreditTransactionForm(TransactionForm):
301
301
 
302
302
  def _extract_merchant_suggestion(self, data):
303
303
  # Use the merchant transaction data as a suggestion source
304
- if merchant := self._extract_suggestion(data, "merchant"):
304
+ if merchant := data.get("merchant"):
305
305
  merchant_tokens = ActivityMatchmaker.tokenize(merchant)
306
306
  # Suggest a known merchant with the closest distance to the activity merchant
307
307
  score_records = []
@@ -201,6 +201,8 @@ def load_statement_details(statement_id):
201
201
  categories = categorize(transactions)
202
202
  # Get bank accounts for potential payments
203
203
  bank_accounts = BankAccountHandler.get_accounts()
204
+ # Save a pointer to this statement to allow easy returns
205
+ session["statement_focus"] = statement_id
204
206
  return render_template(
205
207
  "credit/statement_page.html",
206
208
  statement=statement,
@@ -210,6 +212,18 @@ def load_statement_details(statement_id):
210
212
  )
211
213
 
212
214
 
215
+ @bp.before_app_request
216
+ def clear_statement_focus():
217
+ exempt_endpoints = (
218
+ "credit.expand_transaction",
219
+ "credit.delete_transaction",
220
+ "static",
221
+ None,
222
+ )
223
+ if request.endpoint not in exempt_endpoints:
224
+ session.pop("statement_focus", None)
225
+
226
+
213
227
  @bp.route("/_update_statement_due_date/<int:statement_id>", methods=("POST",))
214
228
  @login_required
215
229
  @db_transaction
@@ -307,7 +321,7 @@ def clear_reconciliation_info():
307
321
  "credit.update_transaction",
308
322
  "credit.infer_statement",
309
323
  "credit.suggest_transaction_autocomplete",
310
- "credit.delete_transacton",
324
+ "credit.delete_transaction",
311
325
  "static",
312
326
  None,
313
327
  )
@@ -483,6 +497,10 @@ def add_subtransaction_fields():
483
497
  @db_transaction
484
498
  def delete_transaction(transaction_id):
485
499
  CreditTransactionHandler.delete_entry(transaction_id)
500
+ if statement_id := session.pop("statement_focus", None):
501
+ return redirect(
502
+ url_for("credit.load_statement_details", statement_id=statement_id)
503
+ )
486
504
  return redirect(url_for("credit.load_transactions"))
487
505
 
488
506
 
@@ -4,12 +4,14 @@ import csv
4
4
  from abc import ABC, abstractmethod
5
5
  from pathlib import Path
6
6
 
7
- from flask import current_app
7
+ from flask import abort, current_app
8
8
  from werkzeug.utils import secure_filename
9
9
 
10
10
  from ....common.utils import parse_date
11
11
  from .data import ActivityLoadingError, TransactionActivities, TransactionActivityLoader
12
12
 
13
+ SUPPORTED_BANKS = ("Bank of America", "Chase", "Discover")
14
+
13
15
 
14
16
  def parse_transaction_activity_file(transaction_file):
15
17
  """
@@ -91,7 +93,7 @@ class _TransactionDateColumnIdentifier(_ColumnIdentifier):
91
93
  elif "date" in standardized_title.split():
92
94
  if "trans." in standardized_title:
93
95
  return True
94
- elif standardized_title == "date":
96
+ elif standardized_title == "date" or standardized_title == "posted date":
95
97
  return None
96
98
  return False
97
99
 
@@ -104,8 +106,8 @@ class _TransactionTotalColumnIdentifier(_ColumnIdentifier):
104
106
 
105
107
  class _TransactionDescriptionColumnIdentifier(_ColumnIdentifier):
106
108
  def check(self, standardized_title):
107
- """Check if the title indicates this column contains a description."""
108
- return standardized_title in ("description", "desc.")
109
+ """Check if the title indicates this column contains a description (payee)."""
110
+ return standardized_title in ("description", "desc.", "payee")
109
111
 
110
112
 
111
113
  class _TransactionCategoryColumnIdentifier(_ColumnIdentifier):
@@ -184,9 +186,15 @@ class _TransactionActivityParser:
184
186
  }
185
187
  for column_type in self.column_types:
186
188
  if raw_column_indices[column_type] is None:
187
- raise RuntimeError(
188
- f"The '{column_type}' column could not be identified in the data."
189
+ current_app.logger.debug(
190
+ f"The '{column_type}' column could not be identified in the data. "
191
+ )
192
+ msg = (
193
+ "The data was unable to be parsed, most likely because it did not "
194
+ "match a recognized format. Supported data formats include those "
195
+ f"from the following banks: {', '.join(SUPPORTED_BANKS)}."
189
196
  )
197
+ abort(400, msg)
190
198
  return raw_column_indices
191
199
 
192
200
  def _determine_expenditure_sign(self, raw_data):
@@ -11,6 +11,10 @@
11
11
  --masthead-height: 50px;
12
12
  --masthead-color: #2b2b2b;
13
13
  --border-gray: #dddddd;
14
+ --button-background-base: #fbfbfb;
15
+ --button-background: linear-gradient(#fbfbfb, #efefef);
16
+ --button-block-background-base: #eeeeee;
17
+ --button-block-background: linear-gradient(#eeeeee, #e6e6e6);
14
18
  }
15
19
 
16
20
  html {
@@ -386,6 +390,35 @@ aside.sidebar {
386
390
  }
387
391
 
388
392
 
393
+ /*
394
+ * Provide styles for flashed messages
395
+ */
396
+ .flash.success,
397
+ .flash.warning,
398
+ .flash.error {
399
+ margin: 20px 0;
400
+ padding: 10px 20px;
401
+ border-radius: 10px;
402
+ box-shadow: 1px 1px 4px #ddd;
403
+ font-size: 0.9em;
404
+ }
405
+
406
+ .flash.success {
407
+ border: 1px solid #6fda6f;
408
+ background: linear-gradient(45deg, #e6efe1, #f5fef1);
409
+ }
410
+
411
+ .flash.warning {
412
+ border: 1px solid #dac96f;
413
+ background: linear-gradient(45deg, #efede1, #fefbf1);
414
+ }
415
+
416
+ .flash.error {
417
+ border: 1px solid #da6f6f;
418
+ background: linear-gradient(45deg, #f3e8e8, #fef1f1);
419
+ }
420
+
421
+
389
422
  /*
390
423
  * Provide styles for error pages
391
424
  */
@@ -459,8 +492,8 @@ aside.sidebar {
459
492
  border: 1px solid var(--border-gray);
460
493
  border-radius: 10px;
461
494
  box-shadow: 1px 1px 3px #bbbbbb;
462
- background-color: #eeeeee;
463
- background-image: linear-gradient(#eeeeee, #e6e6e6);
495
+ background-color: var(--button-block-background-base);
496
+ background-image: var(--button-block-background);
464
497
  color: inherit;
465
498
  }
466
499
 
@@ -546,15 +579,16 @@ aside.sidebar {
546
579
  left: 0;
547
580
  height: 100%;
548
581
  width: 100%;
582
+ display: flex;
583
+ flex-direction: column;
584
+ justify-content: center;
585
+ align-items: center;
549
586
  background-color: rgba(0, 0, 0, 0.75);
550
587
  z-index: 50; /* in front of content, but behind the header */
551
588
  }
552
589
 
553
590
  .modal {
554
- position: relative;
555
- top: 0%;
556
- left: 50%;
557
- transform: translate(-50%, 15%);
591
+ min-width: 200px;
558
592
  }
559
593
 
560
594
  .modal-box {
@@ -638,8 +672,8 @@ form input.button {
638
672
  align-items: center;
639
673
  margin-top: 20px;
640
674
  padding: 10px;
641
- background-color: #fbfbfb;
642
- background-image: linear-gradient(#fbfbfb, #efefef);
675
+ background-color: var(--button-background-base);
676
+ background-image: var(--button-background);
643
677
  color: #333333;
644
678
  font-weight:bold;
645
679
  text-align: center;
@@ -2564,6 +2598,21 @@ form .autocomplete-box .item.active {
2564
2598
  }
2565
2599
 
2566
2600
 
2601
+ /*
2602
+ * Customization for the 'Registration' page
2603
+ */
2604
+ .no-registration-notice {
2605
+ width: 60%;
2606
+ margin: 25px auto;
2607
+ padding: 10px;
2608
+ border: 1px solid var(--moneytree-leaves);
2609
+ border-radius: 10px;
2610
+ box-shadow: 0 0 10px var(--moneytree-leaves);
2611
+ background-color: #f5f5f5;
2612
+ text-align: center;
2613
+ }
2614
+
2615
+
2567
2616
  /*
2568
2617
  * Customization for the 'Profile' page
2569
2618
  */
@@ -3532,7 +3581,6 @@ form#pay #make-payment[type="submit"] #prompt {
3532
3581
  #statement-reconciliation.modal {
3533
3582
  width: 50%;
3534
3583
  min-width: 300px;
3535
- transform: translate(-50%, 100%);
3536
3584
  }
3537
3585
 
3538
3586
  #statement-reconciliation.modal .modal-box {
@@ -3604,13 +3652,145 @@ form#credit-transaction .merchant-field {
3604
3652
  /*
3605
3653
  * Customization for the transaction submission page
3606
3654
  */
3607
- #receipt {
3608
- width: 31%;
3655
+ #submission {
3656
+ display: flex;
3657
+ flex-direction: column;
3658
+ align-items: center;
3659
+ width: 50%;
3609
3660
  margin: auto;
3610
3661
  }
3662
+ @media screen and (max-width: 600px) {
3663
+ /* Mobile layout */
3664
+ #submission {
3665
+ width: 90%;
3666
+ }
3667
+ }
3668
+
3669
+ #submission-title {
3670
+ text-align: center;
3671
+ }
3672
+
3673
+ #receipt {
3674
+ width: 60%;
3675
+ min-width: 250px;
3676
+ max-width: 300px;
3677
+ margin: 40px auto;
3678
+ padding: 10px 20px 30px;
3679
+ box-shadow: 2px 2px 6px #bbbbbb;
3680
+ background-color: #fafafa;
3681
+ font-family: monospace;
3682
+ }
3683
+ @media screen and (max-width: 600px) {
3684
+ /* Mobile layout */
3685
+ #receipt {
3686
+ margin: 10px auto 30px;
3687
+ }
3688
+ }
3611
3689
 
3612
3690
  #receipt-title {
3691
+ margin-bottom: 20px;
3613
3692
  text-align: center;
3693
+ color: var(--silver-dollar);
3694
+ font-size: 10pt;
3695
+ font-weight: 400;
3696
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
3697
+ letter-spacing: 2px;
3698
+ text-transform: uppercase;
3699
+ }
3700
+
3701
+ #receipt .receipt-item {
3702
+ display: flex;
3703
+ margin: 15px 0;
3704
+ }
3705
+
3706
+ #receipt .receipt-item .receipt-key {
3707
+ font-weight: bold;
3708
+ }
3709
+
3710
+ #receipt #receipt-header {
3711
+ margin-bottom: 40px;
3712
+ }
3713
+
3714
+ #receipt #receipt-header #receipt-merchant {
3715
+ font-size: 14pt;
3716
+ justify-content: center;
3717
+ }
3718
+
3719
+ #receipt #receipt-header #receipt-date {
3720
+ font-size: 10pt;
3721
+ font-weight: normal;
3722
+ justify-content: center;
3723
+ }
3724
+
3725
+ #receipt .receipt-subtransaction {
3726
+ display: flex;
3727
+ gap: 10px 20px;
3728
+ margin: 10px 0;
3729
+ }
3730
+
3731
+ #receipt .receipt-subtransaction .note,
3732
+ #receipt #receipt-total .total {
3733
+ flex: 4;
3734
+ }
3735
+
3736
+ #receipt .receipt-subtransaction .amount,
3737
+ #receipt #receipt-total .amount {
3738
+ flex: 1;
3739
+ text-align: right;
3740
+ }
3741
+
3742
+ #receipt #receipt-card {
3743
+ margin-top: 40px;
3744
+ }
3745
+
3746
+ #receipt #receipt-card .receipt-item {
3747
+ flex-direction: column;
3748
+ }
3749
+
3750
+ #receipt #receipt-card .receipt-item .receipt-key {
3751
+ margin-bottom: 5px;
3752
+ }
3753
+
3754
+ #receipt #receipt-card .receipt-item .receipt-value {
3755
+ margin-left: 0;
3756
+ }
3757
+
3758
+ #submission-actions {
3759
+ display: flex;
3760
+ flex-direction: column;
3761
+ gap: 3px;
3762
+ align-items: center;
3763
+ width: 100%;
3764
+ max-width: 400px;
3765
+ }
3766
+ @media screen and (max-width: 600px) {
3767
+ /* Mobile layout */
3768
+ #submission-actions {
3769
+ gap: 5px;
3770
+ }
3771
+ }
3772
+
3773
+ #submission-actions .submission.button {
3774
+ width: 100%;
3775
+ padding: 3px 10px;
3776
+ border: 1px solid var(--border-gray);
3777
+ border-radius: 5px;
3778
+ background-color: var(--button-background-base);
3779
+ background-image: var(--button-background);;
3780
+ font-size: 12pt;
3781
+ text-decoration: none;
3782
+ box-sizing: border-box;
3783
+ }
3784
+ @media screen and (max-width: 600px) {
3785
+ /* Mobile layout */
3786
+ #submission-actions .submission.button {
3787
+ padding: 10px;
3788
+ font-size: 10pt;
3789
+ }
3790
+ }
3791
+
3792
+ #submission-actions .submission.button:hover {
3793
+ filter: brightness(0.98);
3614
3794
  }
3615
3795
 
3616
3796
 
@@ -0,0 +1,21 @@
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block header %}
4
+ <h1>{% block title %}Change Password{% endblock %}</h1>
5
+ {% endblock %}
6
+
7
+ {% block content %}
8
+
9
+ <form id="change-password" method="post">
10
+ <p class="instructions">
11
+ Use this form to change the password for user: <b>{{ g.user.username }}</b>
12
+ </p>
13
+
14
+ <label for="password">Current Password</label>
15
+ <input type="password" name="current-password" id="current-password" required>
16
+ <label for="new-password">New Password</label>
17
+ <input type="password" name="new-password" id="new-password" required>
18
+ <input class="button" type="submit" value="Update" />
19
+ </form>
20
+
21
+ {% endblock %}
@@ -5,11 +5,13 @@
5
5
  {% endblock %}
6
6
 
7
7
  {% block content %}
8
- <form method="post">
8
+
9
+ <form id="log-in" method="post">
9
10
  <label for="username">Username</label>
10
11
  <input name="username" id="username" required>
11
12
  <label for="password">Password</label>
12
13
  <input type="password" name="password" id="password" required>
13
14
  <input class="button" type="submit" value="Log In" />
14
15
  </form>
16
+
15
17
  {% endblock %}
@@ -0,0 +1,25 @@
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block header %}
4
+ <h1>{% block title %}Register{% endblock %}</h1>
5
+ {% endblock %}
6
+
7
+ {% block content %}
8
+
9
+ {% if not config['REGISTRATION'] %}
10
+ <div class="no-registration-notice">
11
+ <b>Notice:</b> The app is not currently accepting new registrations.
12
+ </div>
13
+ {% endif %}
14
+
15
+ {% with input_status = 'required' if config['REGISTRATION'] else 'disabled' %}
16
+ <form id="register" method="post">
17
+ <label for="username">Username</label>
18
+ <input name="username" id="username" {{ input_status }}>
19
+ <label for="password">Password</label>
20
+ <input type="password" name="password" id="password" {{ input_status }}>
21
+ <input class="button" type="submit" value="Register" />
22
+ </form>
23
+ {% endwith %}
24
+
25
+ {% endblock %}
@@ -25,8 +25,8 @@
25
25
  <div class=user-settings">
26
26
 
27
27
  <div class="password">
28
- <a style="color: gray;" href="{{ url_for('core.change_password') }}">
29
- Change password (coming soon)
28
+ <a style="color: gray;" href="{{ url_for('auth.change_password') }}">
29
+ Change password
30
30
  </a>
31
31
  </div>
32
32