├── .env.example ├── .github ├── FUNDING.yml ├── img │ ├── all_transactions.png │ ├── calendar.png │ ├── logo.png │ ├── logo.svg │ ├── monthly_view.png │ ├── networth.png │ ├── readme_balance.png │ ├── readme_calculator.gif │ ├── readme_dca_1.png │ ├── readme_dca_2.png │ ├── readme_installment_plan.png │ ├── readme_recurring_transaction.png │ ├── readme_transaction.png │ ├── readme_transfer.png │ ├── readme_unit_price_calculator.png │ └── yearly.png └── workflows │ ├── release.yml │ └── translations.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── WYGIWYH │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── __init__.py ├── apps │ ├── __init__.py │ ├── accounts │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_account_exchange_currency.py │ │ │ ├── 0003_accountgroup_alter_account_name.py │ │ │ ├── 0004_account_group.py │ │ │ ├── 0005_account_archived.py │ │ │ ├── 0006_rename_archived_account_is_archived_and_more.py │ │ │ ├── 0007_make_account_names_unique.py │ │ │ ├── 0008_alter_account_name.py │ │ │ ├── 0009_account_owner_account_shared_with_accountgroup_owner.py │ │ │ ├── 0010_alter_account_managers_alter_accountgroup_managers_and_more.py │ │ │ ├── 0011_alter_account_owner_alter_accountgroup_owner.py │ │ │ ├── 0012_alter_account_managers_alter_accountgroup_managers_and_more.py │ │ │ ├── 0013_alter_account_visibility_and_more.py │ │ │ ├── 0014_alter_account_options_alter_accountgroup_options.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── account_groups.py │ │ │ ├── accounts.py │ │ │ └── balance.py │ ├── api │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── custom │ │ │ ├── __init__.py │ │ │ └── pagination.py │ │ ├── fields │ │ │ ├── __init__.py │ │ │ └── transactions.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── permissions.py │ │ ├── serializers │ │ │ ├── __init__.py │ │ │ ├── accounts.py │ │ │ ├── currencies.py │ │ │ ├── dca.py │ │ │ └── transactions.py │ │ ├── urls.py │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── accounts.py │ │ │ ├── currencies.py │ │ │ ├── dca.py │ │ │ └── transactions.py │ ├── calendar_view │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── calendar.py │ │ └── views.py │ ├── common │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── decorators │ │ │ ├── __init__.py │ │ │ ├── demo.py │ │ │ ├── htmx.py │ │ │ └── user.py │ │ ├── fields │ │ │ ├── __init__.py │ │ │ ├── forms │ │ │ │ ├── __init__.py │ │ │ │ ├── dynamic_select.py │ │ │ │ └── grouped_select.py │ │ │ └── month_year.py │ │ ├── forms.py │ │ ├── functions │ │ │ ├── __init__.py │ │ │ ├── dates.py │ │ │ ├── decimals.py │ │ │ └── format.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ └── setup_users.py │ │ ├── middleware │ │ │ ├── __init__.py │ │ │ ├── localization.py │ │ │ └── thread_local.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tasks.py │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ ├── active_link.py │ │ │ ├── decimal.py │ │ │ ├── formats.py │ │ │ ├── json.py │ │ │ ├── markdown.py │ │ │ ├── month_name.py │ │ │ ├── natural.py │ │ │ ├── settings.py │ │ │ ├── title.py │ │ │ ├── toast_bg.py │ │ │ └── tools.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── dicts.py │ │ │ └── django.py │ │ ├── views.py │ │ └── widgets │ │ │ ├── __init__.py │ │ │ ├── crispy │ │ │ ├── __init__.py │ │ │ └── submit.py │ │ │ ├── datepicker.py │ │ │ ├── decimal.py │ │ │ ├── month_year.py │ │ │ └── tom_select.py │ ├── currencies │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── exchange_rates │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── fetcher.py │ │ │ └── providers.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_currency_prefix_currency_suffix.py │ │ │ ├── 0003_alter_currency_decimal_places.py │ │ │ ├── 0004_exchangerate.py │ │ │ ├── 0005_alter_currency_name.py │ │ │ ├── 0006_currency_exchange_currency.py │ │ │ ├── 0007_exchangerateservice.py │ │ │ ├── 0008_exchangerateservice_target_accounts_and_more.py │ │ │ ├── 0009_alter_exchangerateservice_target_accounts_and_more.py │ │ │ ├── 0010_alter_currency_code.py │ │ │ ├── 0011_remove_exchangerateservice_fetch_interval_hours_and_more.py │ │ │ ├── 0012_alter_exchangerateservice_service_type.py │ │ │ ├── 0013_alter_exchangerateservice_service_type.py │ │ │ ├── 0014_alter_currency_options.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tasks.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── convert.py │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── currencies.py │ │ │ ├── exchange_rates.py │ │ │ └── exchange_rates_services.py │ ├── dca │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_alter_dcaentry_amount_paid_and_more.py │ │ │ ├── 0003_dcastrategy_owner_dcastrategy_shared_with_and_more.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── export_app │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── resources │ │ │ ├── __init__.py │ │ │ ├── accounts.py │ │ │ ├── currencies.py │ │ │ ├── dca.py │ │ │ ├── import_app.py │ │ │ ├── rules.py │ │ │ ├── transactions.py │ │ │ └── users.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── views.py │ │ └── widgets │ │ │ ├── __init__.py │ │ │ ├── foreign_key.py │ │ │ ├── many_to_many.py │ │ │ ├── numbers.py │ │ │ └── string.py │ ├── import_app │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_alter_importprofile_name_and_more.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── schemas │ │ │ ├── __init__.py │ │ │ └── v1.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ ├── presets.py │ │ │ └── v1.py │ │ ├── tasks.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── insights │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── category_explorer.py │ │ │ ├── category_overview.py │ │ │ ├── sankey.py │ │ │ └── transactions.py │ │ └── views.py │ ├── mini_tools │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── exchange_rate_map.py │ │ └── views.py │ ├── monthly_overview │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── daily_spending_allowance.py │ │ └── views.py │ ├── net_worth │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ └── calculate_net_worth.py │ │ └── views.py │ ├── rules │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_transactionrule_active_transactionrule_on_create_and_more.py │ │ │ ├── 0003_alter_transactionruleaction_unique_together.py │ │ │ ├── 0004_alter_transactionruleaction_field.py │ │ │ ├── 0005_alter_transactionruleaction_rule.py │ │ │ ├── 0006_updateorcreatetransactionruleaction.py │ │ │ ├── 0007_alter_updateorcreatetransactionruleaction_options_and_more.py │ │ │ ├── 0008_updateorcreatetransactionruleaction_search_entities_and_more.py │ │ │ ├── 0009_alter_transactionrule_options_and_more.py │ │ │ ├── 0010_alter_updateorcreatetransactionruleaction_search_account_and_more.py │ │ │ ├── 0011_alter_updateorcreatetransactionruleaction_set_is_paid.py │ │ │ ├── 0012_transactionrule_owner_transactionrule_shared_with_and_more.py │ │ │ ├── 0013_transactionrule_on_delete.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── signals.py │ │ ├── tasks.py │ │ ├── urls.py │ │ └── views.py │ ├── transactions │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── filters.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_transaction_account_transaction_amount_and_more.py │ │ │ ├── 0003_transaction_type_alter_transaction_account.py │ │ │ ├── 0004_alter_transaction_amount.py │ │ │ ├── 0005_transactioncategory_alter_transaction_options_and_more.py │ │ │ ├── 0006_transaction_category.py │ │ │ ├── 0007_transactiontags_alter_transactioncategory_table_and_more.py │ │ │ ├── 0008_rename_transactiontags_transactiontag.py │ │ │ ├── 0009_alter_transaction_tags.py │ │ │ ├── 0010_installmentplan_transaction_installment_plan.py │ │ │ ├── 0011_remove_installmentplan_start_date_and_more.py │ │ │ ├── 0012_alter_transaction_category.py │ │ │ ├── 0013_alter_transactioncategory_name_and_more.py │ │ │ ├── 0014_installmentplan_category_installmentplan_end_date_and_more.py │ │ │ ├── 0015_installmentplan_installment_total_number_and_more.py │ │ │ ├── 0016_recurringtransaction_and_more.py │ │ │ ├── 0017_recurringtransaction_reference_date_and_more.py │ │ │ ├── 0018_recurringtransaction_last_generated_reference_date.py │ │ │ ├── 0019_recurringtransaction_paused.py │ │ │ ├── 0020_installmentplan_notes_recurringtransaction_notes.py │ │ │ ├── 0021_alter_transaction_account.py │ │ │ ├── 0022_rename_paused_recurringtransaction_is_paused.py │ │ │ ├── 0023_transactionentity_transaction_entities.py │ │ │ ├── 0024_installmentplan_entities_and_more.py │ │ │ ├── 0025_transactioncategory_active_transactiontag_active.py │ │ │ ├── 0026_transactionentity_active.py │ │ │ ├── 0027_alter_transaction_description.py │ │ │ ├── 0028_transaction_internal_note.py │ │ │ ├── 0029_alter_transaction_options.py │ │ │ ├── 0030_transaction_deleted_transaction_deleted_at.py │ │ │ ├── 0031_alter_transaction_deleted.py │ │ │ ├── 0032_transaction_created_at_transaction_updated_at.py │ │ │ ├── 0033_transaction_internal_id.py │ │ │ ├── 0034_alter_installmentplan_managers_and_more.py │ │ │ ├── 0035_alter_transactioncategory_name_and_more.py │ │ │ ├── 0036_alter_transactioncategory_managers_and_more.py │ │ │ ├── 0037_alter_transactioncategory_visibility_and_more.py │ │ │ ├── 0038_transaction_owner.py │ │ │ ├── 0039_alter_transaction_internal_id_and_more.py │ │ │ ├── 0040_alter_transaction_unique_together_and_more.py │ │ │ ├── 0041_installmentplan_add_description_to_transaction_and_more.py │ │ │ ├── 0042_alter_transactioncategory_options_and_more.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tasks.py │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── currency_display.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── calculations.py │ │ │ ├── default_ordering.py │ │ │ └── monthly_summary.py │ │ ├── validators.py │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── actions.py │ │ │ ├── categories.py │ │ │ ├── entities.py │ │ │ ├── installment_plans.py │ │ │ ├── recurring_transactions.py │ │ │ ├── tags.py │ │ │ └── transactions.py │ ├── users │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── managers.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_usersettings.py │ │ │ ├── 0003_usersettings_language_usersettings_timezone.py │ │ │ ├── 0004_alter_usersettings_timezone.py │ │ │ ├── 0005_alter_usersettings_language.py │ │ │ ├── 0006_alter_usersettings_language.py │ │ │ ├── 0007_usersettings_mute_sounds.py │ │ │ ├── 0008_usersettings_start_page.py │ │ │ ├── 0009_alter_usersettings_start_page.py │ │ │ ├── 0010_alter_usersettings_start_page.py │ │ │ ├── 0011_alter_usersettings_start_page.py │ │ │ ├── 0012_alter_usersettings_start_page.py │ │ │ ├── 0013_usersettings_date_format_and_more.py │ │ │ ├── 0014_alter_usersettings_date_format_and_more.py │ │ │ ├── 0015_alter_usersettings_language.py │ │ │ ├── 0016_alter_usersettings_language.py │ │ │ ├── 0017_usersettings_number_format.py │ │ │ ├── 0018_alter_usersettings_start_page.py │ │ │ ├── 0019_alter_usersettings_language.py │ │ │ ├── 0020_alter_usersettings_language.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── signals.py │ │ ├── urls.py │ │ └── views.py │ └── yearly_overview │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── migrations │ │ └── __init__.py │ │ ├── urls.py │ │ └── views.py ├── fixtures │ └── demo_data.json ├── import_presets │ ├── .gitkeep │ ├── cajamar │ │ ├── config.yml │ │ └── manifest.json │ └── nuconta │ │ ├── config.yml │ │ └── manifest.json ├── locale │ ├── de │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── en │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── nl │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── pt │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── sv │ │ └── LC_MESSAGES │ │ │ └── django.po │ └── uk │ │ └── LC_MESSAGES │ │ └── django.po ├── manage.py ├── static │ ├── img │ │ ├── favicon │ │ │ ├── android-icon-144x144.png │ │ │ ├── android-icon-192x192.png │ │ │ ├── android-icon-36x36.png │ │ │ ├── android-icon-48x48.png │ │ │ ├── android-icon-72x72.png │ │ │ ├── android-icon-96x96.png │ │ │ ├── apple-icon-114x114.png │ │ │ ├── apple-icon-120x120.png │ │ │ ├── apple-icon-144x144.png │ │ │ ├── apple-icon-152x152.png │ │ │ ├── apple-icon-180x180.png │ │ │ ├── apple-icon-57x57.png │ │ │ ├── apple-icon-60x60.png │ │ │ ├── apple-icon-72x72.png │ │ │ ├── apple-icon-76x76.png │ │ │ ├── apple-icon-precomposed.png │ │ │ ├── apple-icon.png │ │ │ ├── browserconfig.xml │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── favicon.ico │ │ │ ├── ms-icon-144x144.png │ │ │ ├── ms-icon-150x150.png │ │ │ ├── ms-icon-310x310.png │ │ │ └── ms-icon-70x70.png │ │ ├── logo-icon.svg │ │ ├── logo.svg │ │ └── pwa │ │ │ ├── splash-640x1136.png │ │ │ └── splash-750x1334.png │ └── sounds │ │ ├── pop.mp3 │ │ └── success.mp3 └── templates │ ├── account_groups │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ └── share.html │ └── pages │ │ └── index.html │ ├── accounts │ ├── fragments │ │ ├── account_reconciliation.html │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ └── share.html │ └── pages │ │ └── index.html │ ├── admin │ └── base_site.html │ ├── calendar_view │ ├── fragments │ │ ├── list.html │ │ └── list_transactions.html │ └── pages │ │ └── calendar.html │ ├── categories │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ ├── share.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── common │ └── fragments │ │ ├── month_year_picker.html │ │ └── toasts.html │ ├── cotton │ ├── amount │ │ └── display.html │ ├── config │ │ └── search.html │ ├── msg │ │ └── empty.html │ ├── transaction │ │ ├── item.html │ │ └── small_item.html │ └── ui │ │ ├── account_card.html │ │ ├── card.html │ │ ├── currency_card.html │ │ ├── deleted_transactions_action_bar.html │ │ ├── help_icon.html │ │ ├── info_card.html │ │ ├── percentage_distribution.html │ │ ├── quick_transactions_buttons.html │ │ └── transactions_action_bar.html │ ├── currencies │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ └── list.html │ └── pages │ │ └── index.html │ ├── dca │ ├── fragments │ │ ├── entry │ │ │ ├── add.html │ │ │ └── edit.html │ │ └── strategy │ │ │ ├── add.html │ │ │ ├── details.html │ │ │ ├── edit.html │ │ │ ├── list.html │ │ │ └── share.html │ └── pages │ │ ├── strategy_detail_index.html │ │ └── strategy_index.html │ ├── entities │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ ├── share.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── exchange_rates │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── exchange_rates_services │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── export_app │ ├── fragments │ │ ├── export.html │ │ └── restore.html │ └── pages │ │ └── index.html │ ├── extends │ └── offcanvas.html │ ├── import_app │ ├── fragments │ │ ├── profiles │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── list.html │ │ │ └── list_presets.html │ │ └── runs │ │ │ ├── add.html │ │ │ ├── list.html │ │ │ └── log.html │ └── pages │ │ └── profiles_index.html │ ├── includes │ ├── head │ │ └── favicons.html │ ├── navbar.html │ ├── navbar │ │ └── user_menu.html │ ├── offcanvas.html │ ├── placeholders.html │ ├── scripts.html │ ├── scripts │ │ └── hyperscript │ │ │ ├── autosize.html │ │ │ ├── hide_amount.html │ │ │ ├── htmx_error_handler.html │ │ │ ├── init_date_picker.html │ │ │ ├── init_tom_select.html │ │ │ ├── sounds.html │ │ │ ├── swal.html │ │ │ └── tooltip.html │ ├── styles.html │ ├── toasts.html │ └── tools │ │ └── calculator.html │ ├── insights │ ├── fragments │ │ ├── category_explorer │ │ │ ├── charts │ │ │ │ ├── account.html │ │ │ │ └── currency.html │ │ │ └── index.html │ │ ├── category_overview │ │ │ └── index.html │ │ ├── emergency_fund.html │ │ ├── late_transactions.html │ │ ├── latest_transactions.html │ │ └── sankey.html │ └── pages │ │ └── index.html │ ├── installment_plans │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ ├── list_transactions.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── layouts │ ├── base.html │ └── base_auth.html │ ├── mini_tools │ ├── currency_converter │ │ ├── converted_value.html │ │ └── currency_converter.html │ └── unit_price_calculator.html │ ├── monthly_overview │ ├── fragments │ │ ├── list.html │ │ ├── monthly_account_summary.html │ │ ├── monthly_currency_summary.html │ │ └── monthly_summary.html │ └── pages │ │ └── overview.html │ ├── net_worth │ └── net_worth.html │ ├── offline.html │ ├── pwa │ └── serviceworker.js │ ├── recurring_transactions │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ ├── list_transactions.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── rules │ ├── fragments │ │ ├── list.html │ │ ├── share.html │ │ └── transaction_rule │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── transaction_rule_action │ │ │ ├── add.html │ │ │ └── edit.html │ │ │ ├── update_or_create_transaction_rule_action │ │ │ ├── add.html │ │ │ └── edit.html │ │ │ └── view.html │ └── pages │ │ └── index.html │ ├── tags │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ ├── share.html │ │ └── table.html │ └── pages │ │ └── index.html │ ├── transactions │ ├── fragments │ │ ├── add.html │ │ ├── add_installment_plan.html │ │ ├── all_account_summary.html │ │ ├── all_currency_summary.html │ │ ├── bulk_edit.html │ │ ├── edit.html │ │ ├── edit_installment_plan.html │ │ ├── item.html │ │ ├── list_all.html │ │ ├── summary.html │ │ ├── transfer.html │ │ └── trash_list.html │ ├── pages │ │ ├── add.html │ │ ├── transactions.html │ │ └── trash.html │ └── widgets │ │ ├── income_expense_toggle_buttons.html │ │ ├── paid_toggle_button.html │ │ ├── transaction_type_filter_buttons.html │ │ ├── unselectable_income_expense_toggle_buttons.html │ │ └── unselectable_paid_toggle_button.html │ ├── users │ ├── fragments │ │ ├── add.html │ │ ├── edit.html │ │ ├── list.html │ │ └── user_settings.html │ ├── generic │ │ ├── hide_amounts.html │ │ ├── mute_sounds.html │ │ ├── play_sounds.html │ │ └── show_amounts.html │ ├── login.html │ └── pages │ │ └── index.html │ └── yearly_overview │ ├── fragments │ ├── account_data.html │ └── currency_data.html │ └── pages │ ├── overview_by_account.html │ └── overview_by_currency.html ├── docker-compose.dev.yml ├── docker-compose.prod.yml ├── docker ├── dev │ ├── django │ │ ├── Dockerfile │ │ └── start │ ├── procrastinate │ │ └── start │ ├── supervisord │ │ ├── start │ │ └── supervisord.conf │ └── webpack │ │ └── Dockerfile └── prod │ ├── django │ ├── Dockerfile │ └── start │ ├── procrastinate │ └── start │ └── supervisord │ ├── start │ └── supervisord.conf ├── frontend ├── .babelrc ├── .browserslistrc ├── .eslintrc ├── .gitignore ├── .nvmrc ├── .stylelintrc.json ├── README.md ├── assets │ └── images │ │ └── .gitkeep ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── application │ │ ├── _htmx.js │ │ ├── autosize.js │ │ ├── bootstrap.js │ │ ├── charts.js │ │ ├── datepicker.js │ │ ├── htmx.js │ │ ├── jquery.js │ │ ├── select.js │ │ ├── style.js │ │ └── sweetalert2.js │ └── styles │ │ ├── _animations.scss │ │ ├── _bootstrap.scss │ │ ├── _datepicker.scss │ │ ├── _font-awesome.scss │ │ ├── _scrollbar.scss │ │ ├── _tailwind.scss │ │ ├── _tom-select.scss │ │ ├── _variables.scss │ │ └── style.scss ├── tailwind.config.js ├── vendors │ └── .gitkeep └── webpack │ ├── webpack.common.js │ ├── webpack.config.dev.js │ ├── webpack.config.prod.js │ └── webpack.config.watch.js └── requirements.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: eitchtee 4 | custom: ["https://www.paypal.com/donate/?hosted_button_id=FFWM4W9NQDMM6"] 5 | -------------------------------------------------------------------------------- /.github/img/all_transactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/all_transactions.png -------------------------------------------------------------------------------- /.github/img/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/calendar.png -------------------------------------------------------------------------------- /.github/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/logo.png -------------------------------------------------------------------------------- /.github/img/monthly_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/monthly_view.png -------------------------------------------------------------------------------- /.github/img/networth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/networth.png -------------------------------------------------------------------------------- /.github/img/readme_balance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_balance.png -------------------------------------------------------------------------------- /.github/img/readme_calculator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_calculator.gif -------------------------------------------------------------------------------- /.github/img/readme_dca_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_dca_1.png -------------------------------------------------------------------------------- /.github/img/readme_dca_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_dca_2.png -------------------------------------------------------------------------------- /.github/img/readme_installment_plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_installment_plan.png -------------------------------------------------------------------------------- /.github/img/readme_recurring_transaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_recurring_transaction.png -------------------------------------------------------------------------------- /.github/img/readme_transaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_transaction.png -------------------------------------------------------------------------------- /.github/img/readme_transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_transfer.png -------------------------------------------------------------------------------- /.github/img/readme_unit_price_calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/readme_unit_price_calculator.png -------------------------------------------------------------------------------- /.github/img/yearly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/.github/img/yearly.png -------------------------------------------------------------------------------- /app/WYGIWYH/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/WYGIWYH/__init__.py -------------------------------------------------------------------------------- /app/WYGIWYH/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for WYGIWYH project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "WYGIWYH.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /app/WYGIWYH/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for WYGIWYH project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "WYGIWYH.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/__init__.py -------------------------------------------------------------------------------- /app/apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/__init__.py -------------------------------------------------------------------------------- /app/apps/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/accounts/__init__.py -------------------------------------------------------------------------------- /app/apps/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from apps.accounts.models import Account, AccountGroup 4 | from apps.common.admin import SharedObjectModelAdmin 5 | 6 | 7 | @admin.register(Account) 8 | class AccountModelAdmin(SharedObjectModelAdmin): 9 | pass 10 | 11 | 12 | @admin.register(AccountGroup) 13 | class AccountGroupModelAdmin(SharedObjectModelAdmin): 14 | pass 15 | -------------------------------------------------------------------------------- /app/apps/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.accounts" 7 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0002_account_exchange_currency.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-04 03:33 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("accounts", "0001_initial"), 11 | ("currencies", "0004_exchangerate"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="account", 17 | name="exchange_currency", 18 | field=models.ForeignKey( 19 | blank=True, 20 | help_text="Default currency for exchange calculations", 21 | null=True, 22 | on_delete=django.db.models.deletion.SET_NULL, 23 | related_name="exchange_accounts", 24 | to="currencies.currency", 25 | verbose_name="Exchange Currency", 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0004_account_group.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-08 23:47 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0003_accountgroup_alter_account_name'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='account', 16 | name='group', 17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.accountgroup', verbose_name='Account Group'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0005_account_archived.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-28 00:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0004_account_group'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='account', 15 | name='archived', 16 | field=models.BooleanField(default=False, help_text="Archived accounts don't show up nor count towards your net worth", verbose_name='Archived'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0006_rename_archived_account_is_archived_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-28 00:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0005_account_archived'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='account', 15 | old_name='archived', 16 | new_name='is_archived', 17 | ), 18 | migrations.AlterField( 19 | model_name='account', 20 | name='is_asset', 21 | field=models.BooleanField(default=False, help_text='Asset accounts count towards your Net Worth, but not towards your month.', verbose_name='Asset account'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0007_make_account_names_unique.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | def make_names_unique(apps, schema_editor): 5 | Account = apps.get_model("accounts", "Account") 6 | 7 | # Get all accounts ordered by id 8 | accounts = Account.objects.all().order_by("id") 9 | 10 | # Track seen names 11 | seen_names = {} 12 | 13 | for account in accounts: 14 | original_name = account.name 15 | counter = seen_names.get(original_name, 0) 16 | 17 | while account.name in seen_names: 18 | counter += 1 19 | account.name = f"{original_name} ({counter})" 20 | 21 | seen_names[account.name] = counter 22 | account.save() 23 | 24 | 25 | def reverse_migration(apps, schema_editor): 26 | # Can't restore original names, so do nothing 27 | pass 28 | 29 | 30 | class Migration(migrations.Migration): 31 | 32 | dependencies = [ 33 | ("accounts", "0006_rename_archived_account_is_archived_and_more"), 34 | ] 35 | 36 | operations = [ 37 | migrations.RunPython(make_names_unique, reverse_migration), 38 | ] 39 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0008_alter_account_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-24 00:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0007_make_account_names_unique'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='account', 15 | name='name', 16 | field=models.CharField(max_length=255, unique=True, verbose_name='Name'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0011_alter_account_owner_alter_accountgroup_owner.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-05 04:19 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0010_alter_account_managers_alter_accountgroup_managers_and_more'), 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='account', 18 | name='owner', 19 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_accounts', to=settings.AUTH_USER_MODEL, verbose_name='Owner'), 20 | ), 21 | migrations.AlterField( 22 | model_name='accountgroup', 23 | name='owner', 24 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_account_groups', to=settings.AUTH_USER_MODEL, verbose_name='Owner'), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0013_alter_account_visibility_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-06 01:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0012_alter_account_managers_alter_accountgroup_managers_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='account', 15 | name='visibility', 16 | field=models.CharField(choices=[('private', 'Private'), ('public', 'Public')], default='private', max_length=10), 17 | ), 18 | migrations.AlterField( 19 | model_name='accountgroup', 20 | name='visibility', 21 | field=models.CharField(choices=[('private', 'Private'), ('public', 'Public')], default='private', max_length=10), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/0014_alter_account_options_alter_accountgroup_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-03-09 21:54 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0013_alter_account_visibility_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='account', 15 | options={'ordering': ['name', 'id'], 'verbose_name': 'Account', 'verbose_name_plural': 'Accounts'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='accountgroup', 19 | options={'ordering': ['name', 'id'], 'verbose_name': 'Account Group', 'verbose_name_plural': 'Account Groups'}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /app/apps/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/accounts/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .accounts import * 2 | from .account_groups import * 3 | from .balance import * 4 | -------------------------------------------------------------------------------- /app/apps/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/api/__init__.py -------------------------------------------------------------------------------- /app/apps/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.api" 7 | -------------------------------------------------------------------------------- /app/apps/api/custom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/api/custom/__init__.py -------------------------------------------------------------------------------- /app/apps/api/custom/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | 3 | 4 | class CustomPageNumberPagination(PageNumberPagination): 5 | page_size = 100 6 | page_size_query_param = "page_size" 7 | -------------------------------------------------------------------------------- /app/apps/api/fields/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/api/fields/__init__.py -------------------------------------------------------------------------------- /app/apps/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/api/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/api/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | from django.conf import settings 3 | 4 | 5 | class NotInDemoMode(BasePermission): 6 | def has_permission(self, request, view): 7 | if settings.DEMO and not request.user.is_superuser: 8 | return False 9 | else: 10 | return True 11 | -------------------------------------------------------------------------------- /app/apps/api/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .transactions import * 2 | from .accounts import * 3 | from .currencies import * 4 | from .dca import * 5 | -------------------------------------------------------------------------------- /app/apps/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | 4 | from apps.api import views 5 | 6 | router = routers.DefaultRouter() 7 | router.register(r"transactions", views.TransactionViewSet) 8 | router.register(r"categories", views.TransactionCategoryViewSet) 9 | router.register(r"tags", views.TransactionTagViewSet) 10 | router.register(r"entities", views.TransactionEntityViewSet) 11 | router.register(r"installment-plans", views.InstallmentPlanViewSet) 12 | router.register(r"recurring-transactions", views.RecurringTransactionViewSet) 13 | router.register(r"account-groups", views.AccountGroupViewSet) 14 | router.register(r"accounts", views.AccountViewSet) 15 | router.register(r"currencies", views.CurrencyViewSet) 16 | router.register(r"exchange-rates", views.ExchangeRateViewSet) 17 | router.register(r"dca/strategies", views.DCAStrategyViewSet) 18 | router.register(r"dca/entries", views.DCAEntryViewSet) 19 | 20 | urlpatterns = [ 21 | path("", include(router.urls)), 22 | ] 23 | -------------------------------------------------------------------------------- /app/apps/api/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .transactions import * 2 | from .accounts import * 3 | from .currencies import * 4 | from .dca import * 5 | -------------------------------------------------------------------------------- /app/apps/api/views/accounts.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | 3 | from apps.api.custom.pagination import CustomPageNumberPagination 4 | from apps.accounts.models import AccountGroup, Account 5 | from apps.api.serializers import AccountGroupSerializer, AccountSerializer 6 | 7 | 8 | class AccountGroupViewSet(viewsets.ModelViewSet): 9 | queryset = AccountGroup.objects.all() 10 | serializer_class = AccountGroupSerializer 11 | pagination_class = CustomPageNumberPagination 12 | 13 | def get_queryset(self): 14 | return AccountGroup.objects.all().order_by("id") 15 | 16 | 17 | class AccountViewSet(viewsets.ModelViewSet): 18 | queryset = Account.objects.all() 19 | serializer_class = AccountSerializer 20 | pagination_class = CustomPageNumberPagination 21 | 22 | def get_queryset(self): 23 | return ( 24 | Account.objects.all() 25 | .order_by("id") 26 | .select_related("group", "currency", "exchange_currency") 27 | ) 28 | -------------------------------------------------------------------------------- /app/apps/api/views/currencies.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | 3 | from apps.api.serializers import ExchangeRateSerializer 4 | from apps.api.serializers import CurrencySerializer 5 | from apps.currencies.models import Currency 6 | from apps.currencies.models import ExchangeRate 7 | 8 | 9 | class CurrencyViewSet(viewsets.ModelViewSet): 10 | queryset = Currency.objects.all() 11 | serializer_class = CurrencySerializer 12 | 13 | 14 | class ExchangeRateViewSet(viewsets.ModelViewSet): 15 | queryset = ExchangeRate.objects.all() 16 | serializer_class = ExchangeRateSerializer 17 | -------------------------------------------------------------------------------- /app/apps/calendar_view/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/calendar_view/__init__.py -------------------------------------------------------------------------------- /app/apps/calendar_view/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CalendarViewConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.calendar_view" 7 | -------------------------------------------------------------------------------- /app/apps/calendar_view/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/calendar_view/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/calendar_view/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("calendar/", views.index, name="calendar_index"), 7 | path( 8 | "calendar///list/", 9 | views.calendar_list, 10 | name="calendar_list", 11 | ), 12 | path( 13 | "calendar////transactions/", 14 | views.calendar_transactions_list, 15 | name="calendar_transactions_list", 16 | ), 17 | path( 18 | "calendar///", 19 | views.calendar, 20 | name="calendar", 21 | ), 22 | # path( 23 | # "calendar/available_dates/", 24 | # views.month_year_picker, 25 | # name="available_dates", 26 | # ), 27 | ] 28 | -------------------------------------------------------------------------------- /app/apps/calendar_view/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/calendar_view/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/__init__.py -------------------------------------------------------------------------------- /app/apps/common/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | 4 | class SharedObjectModelAdmin(admin.ModelAdmin): 5 | def get_queryset(self, request): 6 | # Use the all_objects manager to show all transactions, including deleted ones 7 | return self.model.all_objects.all() 8 | -------------------------------------------------------------------------------- /app/apps/common/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommonConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.common" 7 | -------------------------------------------------------------------------------- /app/apps/common/decorators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/decorators/__init__.py -------------------------------------------------------------------------------- /app/apps/common/decorators/demo.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from django.conf import settings 4 | from django.core.exceptions import PermissionDenied 5 | 6 | 7 | def disabled_on_demo(view): 8 | @wraps(view) 9 | def _view(request, *args, **kwargs): 10 | if settings.DEMO and not request.user.is_superuser: 11 | raise PermissionDenied 12 | 13 | return view(request, *args, **kwargs) 14 | 15 | return _view 16 | -------------------------------------------------------------------------------- /app/apps/common/decorators/htmx.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from django.core.exceptions import PermissionDenied 4 | 5 | 6 | def only_htmx(view): 7 | @wraps(view) 8 | def _view(request, *args, **kwargs): 9 | if not request.META.get("HTTP_HX_REQUEST"): 10 | raise PermissionDenied 11 | 12 | return view(request, *args, **kwargs) 13 | 14 | return _view 15 | -------------------------------------------------------------------------------- /app/apps/common/fields/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/fields/__init__.py -------------------------------------------------------------------------------- /app/apps/common/fields/forms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/fields/forms/__init__.py -------------------------------------------------------------------------------- /app/apps/common/functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/functions/__init__.py -------------------------------------------------------------------------------- /app/apps/common/functions/dates.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import calendar 3 | 4 | 5 | def remaining_days_in_month(year, month, current_date: datetime.date): 6 | # Get the number of days in the given month 7 | _, days_in_month = calendar.monthrange(year, month) 8 | 9 | # Check if the given month and year match the current month and year 10 | if current_date.year == year and current_date.month == month: 11 | # Calculate remaining days 12 | remaining_days = days_in_month - current_date.day + 1 13 | else: 14 | # If not the current month, return the total number of days in the month 15 | remaining_days = days_in_month 16 | 17 | return remaining_days 18 | -------------------------------------------------------------------------------- /app/apps/common/functions/decimals.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal, ROUND_DOWN 2 | 3 | 4 | def truncate_decimal(value, decimal_places): 5 | """ 6 | Truncate a Decimal value to n decimal places without rounding. 7 | 8 | :param value: The Decimal value to truncate 9 | :param decimal_places: The number of decimal places to keep 10 | :return: Truncated Decimal value 11 | """ 12 | multiplier = Decimal(10**decimal_places) 13 | return (value * multiplier).to_integral_value(rounding=ROUND_DOWN) / multiplier 14 | -------------------------------------------------------------------------------- /app/apps/common/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/management/__init__.py -------------------------------------------------------------------------------- /app/apps/common/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/management/commands/__init__.py -------------------------------------------------------------------------------- /app/apps/common/middleware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/middleware/__init__.py -------------------------------------------------------------------------------- /app/apps/common/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/common/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/templatetags/__init__.py -------------------------------------------------------------------------------- /app/apps/common/templatetags/decimal.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from decimal import Decimal, DecimalException 3 | 4 | from django.utils.formats import number_format 5 | from django.utils.translation import to_locale, get_language 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter 11 | def drop_trailing_zeros(value): 12 | if not isinstance(value, (float, Decimal, str)): 13 | return value 14 | 15 | try: 16 | decimal_value = Decimal(str(value)) 17 | return decimal_value.normalize() 18 | except Exception: 19 | return value 20 | 21 | 22 | @register.filter 23 | def localize_number(value, decimal_places=None): 24 | if value is None: 25 | return value 26 | 27 | try: 28 | value = Decimal(str(value)) 29 | except (TypeError, ValueError, DecimalException): 30 | return value 31 | 32 | return number_format( 33 | value, 34 | decimal_pos=decimal_places or abs(value.as_tuple().exponent), 35 | use_l10n=True, 36 | force_grouping=True, 37 | ) 38 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/formats.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from apps.common.functions.format import get_format 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.simple_tag 9 | def get_thousand_separator(): 10 | return get_format("THOUSAND_SEPARATOR") 11 | 12 | 13 | @register.simple_tag 14 | def get_decimal_separator(): 15 | return get_format("DECIMAL_SEPARATOR") 16 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/json.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import template 4 | 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter("json") 10 | def convert_to_json(value): 11 | return json.dumps(value) 12 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/month_name.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | 3 | from django.template.loader_tags import register 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | 7 | @register.filter 8 | def month_name(month_number): 9 | return _(calendar.month_name[month_number]) 10 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/settings.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag(name="settings") 8 | def settings_value(name): 9 | return getattr(settings, name, "") 10 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/title.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter 8 | def site_title(value): 9 | value = value.strip() 10 | if value: 11 | return f"{value} {settings.TITLE_SEPARATOR or '::'} {settings.SITE_TITLE or 'SITE_TITLE NOT SET'}" 12 | else: 13 | return settings.SITE_TITLE or "SITE_TITLE NOT SET" 14 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/toast_bg.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter 8 | def toast_bg(tags): 9 | if "success" in tags: 10 | return "success" 11 | elif "warning" in tags: 12 | return "warning" 13 | elif "error" in tags: 14 | return "danger" 15 | elif "info" in tags: 16 | return "info" 17 | 18 | 19 | @register.filter 20 | def toast_icon(tags): 21 | if "success" in tags: 22 | return "fa-solid fa-circle-check" 23 | elif "warning" in tags: 24 | return "fa-solid fa-circle-exclamation" 25 | elif "error" in tags: 26 | return "fa-solid fa-circle-xmark" 27 | elif "info" in tags: 28 | return "fa-solid fa-circle-info" 29 | 30 | 31 | @register.filter 32 | def toast_title(tags): 33 | if "success" in tags: 34 | return _("Success") 35 | elif "warning" in tags: 36 | return _("Warning") 37 | elif "error" in tags: 38 | return _("Error") 39 | elif "info" in tags: 40 | return _("Info") 41 | -------------------------------------------------------------------------------- /app/apps/common/templatetags/tools.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.filter 7 | def get_dict_item(obj, key): 8 | if isinstance(obj, dict): 9 | return obj.get(key) 10 | 11 | return obj 12 | -------------------------------------------------------------------------------- /app/apps/common/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path( 7 | "toasts/", 8 | views.toasts, 9 | name="toasts", 10 | ), 11 | path( 12 | "ui/month-year-picker/", 13 | views.month_year_picker, 14 | name="month_year_picker", 15 | ), 16 | path( 17 | "cache/invalidate/", 18 | views.invalidate_cache, 19 | name="invalidate_cache", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /app/apps/common/utils/dicts.py: -------------------------------------------------------------------------------- 1 | def remove_falsey_entries(data: dict, key: str) -> dict: 2 | return {k: v for k, v in data.items() if v.get(key)} 3 | -------------------------------------------------------------------------------- /app/apps/common/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/widgets/__init__.py -------------------------------------------------------------------------------- /app/apps/common/widgets/crispy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/common/widgets/crispy/__init__.py -------------------------------------------------------------------------------- /app/apps/common/widgets/month_year.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, date 2 | 3 | from django import forms 4 | 5 | 6 | class MonthYearWidget(forms.DateInput): 7 | """ 8 | Custom widget to display a month-year picker. 9 | """ 10 | 11 | input_type = "month" # Set the input type to 'month' 12 | 13 | def format_value(self, value): 14 | if isinstance(value, (datetime, date)): 15 | return value.strftime("%Y-%m") 16 | return value 17 | -------------------------------------------------------------------------------- /app/apps/currencies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/currencies/__init__.py -------------------------------------------------------------------------------- /app/apps/currencies/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService 4 | 5 | 6 | @admin.register(Currency) 7 | class CurrencyAdmin(admin.ModelAdmin): 8 | def formfield_for_dbfield(self, db_field, request, **kwargs): 9 | if db_field.name == "suffix" or db_field.name == "prefix": 10 | kwargs["strip"] = False 11 | return super().formfield_for_dbfield(db_field, request, **kwargs) 12 | 13 | 14 | @admin.register(ExchangeRateService) 15 | class ExchangeRateServiceAdmin(admin.ModelAdmin): 16 | list_display = [ 17 | "name", 18 | "service_type", 19 | "is_active", 20 | "interval_type", 21 | "fetch_interval", 22 | "last_fetch", 23 | ] 24 | list_filter = ["is_active", "service_type"] 25 | search_fields = ["name"] 26 | filter_horizontal = ["target_currencies"] 27 | 28 | 29 | admin.site.register(ExchangeRate) 30 | -------------------------------------------------------------------------------- /app/apps/currencies/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CurrenciesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.currencies" 7 | -------------------------------------------------------------------------------- /app/apps/currencies/exchange_rates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/currencies/exchange_rates/__init__.py -------------------------------------------------------------------------------- /app/apps/currencies/exchange_rates/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from decimal import Decimal 3 | from typing import List, Tuple, Optional 4 | from django.db.models import QuerySet 5 | 6 | from apps.currencies.models import Currency 7 | 8 | 9 | class ExchangeRateProvider(ABC): 10 | rates_inverted = False 11 | 12 | def __init__(self, api_key: Optional[str] = None): 13 | self.api_key = api_key 14 | 15 | @abstractmethod 16 | def get_rates( 17 | self, target_currencies: QuerySet, exchange_currencies: set 18 | ) -> List[Tuple[Currency, Currency, Decimal]]: 19 | """Fetch exchange rates for multiple currency pairs""" 20 | raise NotImplementedError("Subclasses must implement get_rates method") 21 | 22 | @classmethod 23 | def requires_api_key(cls) -> bool: 24 | """Return True if the service requires an API key""" 25 | return True 26 | 27 | @staticmethod 28 | def invert_rate(rate: Decimal) -> Decimal: 29 | """Invert the given rate.""" 30 | return Decimal("1") / rate 31 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-19 13:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Currency', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('code', models.CharField(max_length=10, unique=True, verbose_name='Currency Code')), 19 | ('name', models.CharField(max_length=50, verbose_name='Currency Name')), 20 | ('decimal_places', models.PositiveIntegerField(default=2, verbose_name='Decimal Places')), 21 | ], 22 | options={ 23 | 'verbose_name': 'Currency', 24 | 'verbose_name_plural': 'Currencies', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0002_currency_prefix_currency_suffix.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-21 03:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('currencies', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='currency', 15 | name='prefix', 16 | field=models.CharField(blank=True, max_length=10, verbose_name='Prefix'), 17 | ), 18 | migrations.AddField( 19 | model_name='currency', 20 | name='suffix', 21 | field=models.CharField(blank=True, max_length=10, verbose_name='Suffix'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0003_alter_currency_decimal_places.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-23 04:05 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('currencies', '0002_currency_prefix_currency_suffix'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='currency', 16 | name='decimal_places', 17 | field=models.PositiveIntegerField(default=2, validators=[django.core.validators.MaxValueValidator(30), django.core.validators.MinValueValidator(0)], verbose_name='Decimal Places'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0005_alter_currency_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-14 14:06 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("currencies", "0004_exchangerate"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="currency", 15 | name="name", 16 | field=models.CharField( 17 | max_length=50, unique=True, verbose_name="Currency Name" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0006_currency_exchange_currency.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-11-09 05:03 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('currencies', '0005_alter_currency_name'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='currency', 16 | name='exchange_currency', 17 | field=models.ForeignKey(blank=True, help_text='Default currency for exchange calculations', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='exchange_currencies', to='currencies.currency', verbose_name='Exchange Currency'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0010_alter_currency_code.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-03 03:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('currencies', '0009_alter_exchangerateservice_target_accounts_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='currency', 15 | name='code', 16 | field=models.CharField(max_length=255, verbose_name='Currency Code'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0012_alter_exchangerateservice_service_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-02 01:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('currencies', '0011_remove_exchangerateservice_fetch_interval_hours_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='exchangerateservice', 15 | name='service_type', 16 | field=models.CharField(choices=[('synth_finance', 'Synth Finance'), ('synth_finance_stock', 'Synth Finance Stock'), ('coingecko_free', 'CoinGecko (Demo/Free)'), ('coingecko_pro', 'CoinGecko (Pro)')], max_length=255, verbose_name='Service Type'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0013_alter_exchangerateservice_service_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-02 01:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('currencies', '0012_alter_exchangerateservice_service_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='exchangerateservice', 15 | name='service_type', 16 | field=models.CharField(choices=[('synth_finance', 'Synth Finance'), ('synth_finance_stock', 'Synth Finance Stock'), ('coingecko_free', 'CoinGecko (Demo/Free)'), ('coingecko_pro', 'CoinGecko (Pro)'), ('transitive', 'Transitive (Calculated from Existing Rates)')], max_length=255, verbose_name='Service Type'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/0014_alter_currency_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-03-09 21:54 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('currencies', '0013_alter_exchangerateservice_service_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='currency', 15 | options={'ordering': ['name', 'id'], 'verbose_name': 'Currency', 'verbose_name_plural': 'Currencies'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /app/apps/currencies/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/currencies/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/currencies/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from procrastinate.contrib.django import app 4 | 5 | from apps.currencies.exchange_rates.fetcher import ExchangeRateFetcher 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @app.periodic(cron="0 * * * *") # Run every hour 11 | @app.task(name="automatic_fetch_exchange_rates") 12 | def automatic_fetch_exchange_rates(timestamp=None): 13 | """Fetch exchange rates for all due services""" 14 | fetcher = ExchangeRateFetcher() 15 | 16 | try: 17 | fetcher.fetch_due_rates() 18 | except Exception as e: 19 | logger.error(e, exc_info=True) 20 | 21 | 22 | @app.task(name="manual_fetch_exchange_rates") 23 | def manual_fetch_exchange_rates(timestamp=None): 24 | """Fetch exchange rates for all due services""" 25 | fetcher = ExchangeRateFetcher() 26 | 27 | try: 28 | fetcher.fetch_due_rates(force=True) 29 | except Exception as e: 30 | logger.error(e, exc_info=True) 31 | -------------------------------------------------------------------------------- /app/apps/currencies/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/currencies/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/currencies/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .currencies import * 2 | from .exchange_rates import * 3 | from .exchange_rates_services import * 4 | -------------------------------------------------------------------------------- /app/apps/dca/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/dca/__init__.py -------------------------------------------------------------------------------- /app/apps/dca/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from apps.dca.models import DCAStrategy, DCAEntry 4 | from apps.common.admin import SharedObjectModelAdmin 5 | 6 | 7 | admin.site.register(DCAEntry) 8 | 9 | 10 | @admin.register(DCAStrategy) 11 | class TransactionEntityModelAdmin(SharedObjectModelAdmin): 12 | def get_queryset(self, request): 13 | return DCAStrategy.all_objects.all() 14 | -------------------------------------------------------------------------------- /app/apps/dca/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DcaConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.dca" 7 | -------------------------------------------------------------------------------- /app/apps/dca/migrations/0002_alter_dcaentry_amount_paid_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-11-13 03:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dca', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='dcaentry', 15 | name='amount_paid', 16 | field=models.DecimalField(decimal_places=30, max_digits=42, verbose_name='Amount Paid'), 17 | ), 18 | migrations.AlterField( 19 | model_name='dcaentry', 20 | name='amount_received', 21 | field=models.DecimalField(decimal_places=30, max_digits=42, verbose_name='Amount Received'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/dca/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/dca/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/dca/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/apps/export_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/export_app/__init__.py -------------------------------------------------------------------------------- /app/apps/export_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/apps/export_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ExportConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.export_app" 7 | -------------------------------------------------------------------------------- /app/apps/export_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/export_app/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/export_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /app/apps/export_app/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/export_app/resources/__init__.py -------------------------------------------------------------------------------- /app/apps/export_app/resources/accounts.py: -------------------------------------------------------------------------------- 1 | from import_export import fields, resources, widgets 2 | 3 | from apps.accounts.models import Account, AccountGroup 4 | from apps.export_app.widgets.foreign_key import AutoCreateForeignKeyWidget 5 | from apps.currencies.models import Currency 6 | 7 | 8 | class AccountResource(resources.ModelResource): 9 | group = fields.Field( 10 | attribute="group", 11 | column_name="group", 12 | widget=AutoCreateForeignKeyWidget(AccountGroup, "name"), 13 | ) 14 | currency = fields.Field( 15 | attribute="currency", 16 | column_name="currency", 17 | widget=widgets.ForeignKeyWidget(Currency, "name"), 18 | ) 19 | exchange_currency = fields.Field( 20 | attribute="exchange_currency", 21 | column_name="exchange_currency", 22 | widget=widgets.ForeignKeyWidget(Currency, "name"), 23 | ) 24 | 25 | class Meta: 26 | model = Account 27 | 28 | def get_queryset(self): 29 | return Account.all_objects.all() 30 | -------------------------------------------------------------------------------- /app/apps/export_app/resources/import_app.py: -------------------------------------------------------------------------------- 1 | from import_export import resources 2 | 3 | from apps.import_app.models import ImportProfile 4 | 5 | 6 | class ImportProfileResource(resources.ModelResource): 7 | class Meta: 8 | model = ImportProfile 9 | -------------------------------------------------------------------------------- /app/apps/export_app/resources/rules.py: -------------------------------------------------------------------------------- 1 | from import_export import fields, resources 2 | from import_export.widgets import ForeignKeyWidget 3 | 4 | from apps.export_app.widgets.foreign_key import AutoCreateForeignKeyWidget 5 | from apps.export_app.widgets.many_to_many import AutoCreateManyToManyWidget 6 | from apps.rules.models import ( 7 | TransactionRule, 8 | TransactionRuleAction, 9 | UpdateOrCreateTransactionRuleAction, 10 | ) 11 | 12 | 13 | class TransactionRuleResource(resources.ModelResource): 14 | class Meta: 15 | model = TransactionRule 16 | 17 | 18 | class TransactionRuleActionResource(resources.ModelResource): 19 | class Meta: 20 | model = TransactionRuleAction 21 | 22 | 23 | class UpdateOrCreateTransactionRuleResource(resources.ModelResource): 24 | class Meta: 25 | model = UpdateOrCreateTransactionRuleAction 26 | -------------------------------------------------------------------------------- /app/apps/export_app/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/apps/export_app/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | import apps.export_app.views as views 3 | 4 | urlpatterns = [ 5 | path("export/", views.export_index, name="export_index"), 6 | path("export/form/", views.export_form, name="export_form"), 7 | path("export/restore/", views.import_form, name="restore_form"), 8 | ] 9 | -------------------------------------------------------------------------------- /app/apps/export_app/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/export_app/widgets/__init__.py -------------------------------------------------------------------------------- /app/apps/export_app/widgets/foreign_key.py: -------------------------------------------------------------------------------- 1 | from import_export.widgets import ForeignKeyWidget 2 | 3 | 4 | class AutoCreateForeignKeyWidget(ForeignKeyWidget): 5 | def clean(self, value, row=None, *args, **kwargs): 6 | if value: 7 | try: 8 | return super().clean(value, row, **kwargs) 9 | except self.model.DoesNotExist: 10 | return self.model.objects.create(name=value) 11 | return None 12 | 13 | 14 | class SkipMissingForeignKeyWidget(ForeignKeyWidget): 15 | def clean(self, value, row=None, *args, **kwargs): 16 | if not value: 17 | return None 18 | 19 | try: 20 | return super().clean(value, row, *args, **kwargs) 21 | except self.model.DoesNotExist: 22 | return None 23 | -------------------------------------------------------------------------------- /app/apps/export_app/widgets/many_to_many.py: -------------------------------------------------------------------------------- 1 | from import_export.widgets import ManyToManyWidget 2 | 3 | 4 | class AutoCreateManyToManyWidget(ManyToManyWidget): 5 | def clean(self, value, row=None, *args, **kwargs): 6 | if not value: 7 | return [] 8 | 9 | values = value.split(self.separator) 10 | cleaned_values = [] 11 | 12 | for val in values: 13 | val = val.strip() 14 | if val: 15 | try: 16 | obj = self.model.objects.get(**{self.field: val}) 17 | except self.model.DoesNotExist: 18 | obj = self.model.objects.create(name=val) 19 | cleaned_values.append(obj) 20 | 21 | return cleaned_values 22 | -------------------------------------------------------------------------------- /app/apps/export_app/widgets/numbers.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from import_export.widgets import NumberWidget 4 | 5 | 6 | class UniversalDecimalWidget(NumberWidget): 7 | def clean(self, value, row=None, *args, **kwargs): 8 | if self.is_empty(value): 9 | return None 10 | # Replace comma with dot if present 11 | if isinstance(value, str): 12 | value = value.replace(",", ".") 13 | return Decimal(str(value)) 14 | 15 | def render(self, value, obj=None, **kwargs): 16 | if value is None: 17 | return "" 18 | return str(value).replace(",", ".") 19 | -------------------------------------------------------------------------------- /app/apps/export_app/widgets/string.py: -------------------------------------------------------------------------------- 1 | from import_export import fields 2 | 3 | 4 | class EmptyStringToNoneField(fields.Field): 5 | def clean(self, data, **kwargs): 6 | value = super().clean(data) 7 | return None if value == "" else value 8 | -------------------------------------------------------------------------------- /app/apps/import_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/import_app/__init__.py -------------------------------------------------------------------------------- /app/apps/import_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from apps.import_app import models 3 | 4 | # Register your models here. 5 | admin.site.register(models.ImportRun) 6 | admin.site.register(models.ImportProfile) 7 | -------------------------------------------------------------------------------- /app/apps/import_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ImportConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.import_app" 7 | -------------------------------------------------------------------------------- /app/apps/import_app/migrations/0002_alter_importprofile_name_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-23 03:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('import_app', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='importprofile', 15 | name='name', 16 | field=models.CharField(max_length=100, unique=True, verbose_name='Name'), 17 | ), 18 | migrations.AlterField( 19 | model_name='importprofile', 20 | name='yaml_config', 21 | field=models.TextField(verbose_name='YAML Configuration'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/import_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/import_app/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/import_app/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | import apps.import_app.schemas.v1 as version_1 2 | -------------------------------------------------------------------------------- /app/apps/import_app/services/__init__.py: -------------------------------------------------------------------------------- 1 | from apps.import_app.services.v1 import ImportService as ImportServiceV1 2 | 3 | from apps.import_app.services.presets import PresetService 4 | -------------------------------------------------------------------------------- /app/apps/import_app/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.contrib.auth import get_user_model 4 | from procrastinate.contrib.django import app 5 | 6 | from apps.common.middleware.thread_local import write_current_user, delete_current_user 7 | from apps.import_app.models import ImportRun 8 | from apps.import_app.services import ImportServiceV1 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | @app.task(name="process_import") 14 | def process_import(import_run_id: int, file_path: str, user_id: int): 15 | user = get_user_model().objects.get(id=user_id) 16 | write_current_user(user) 17 | 18 | try: 19 | import_run = ImportRun.objects.get(id=import_run_id) 20 | import_service = ImportServiceV1(import_run) 21 | import_service.process_file(file_path) 22 | delete_current_user() 23 | except ImportRun.DoesNotExist: 24 | delete_current_user() 25 | raise ValueError(f"ImportRun with id {import_run_id} not found") 26 | -------------------------------------------------------------------------------- /app/apps/import_app/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/apps/insights/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/insights/__init__.py -------------------------------------------------------------------------------- /app/apps/insights/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/apps/insights/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class InsightsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.insights" 7 | -------------------------------------------------------------------------------- /app/apps/insights/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/insights/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/insights/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /app/apps/insights/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/apps/insights/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/insights/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/mini_tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/mini_tools/__init__.py -------------------------------------------------------------------------------- /app/apps/mini_tools/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/apps/mini_tools/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MiniToolsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.mini_tools" 7 | -------------------------------------------------------------------------------- /app/apps/mini_tools/forms.py: -------------------------------------------------------------------------------- 1 | from crispy_forms.helper import FormHelper 2 | from crispy_forms.layout import Layout, Row, Column 3 | from django import forms 4 | from apps.currencies.models import Currency 5 | from apps.common.widgets.tom_select import TomSelect 6 | 7 | 8 | class CurrencyConverterForm(forms.Form): 9 | from_currency = forms.ModelChoiceField( 10 | queryset=Currency.objects.all(), 11 | label="", 12 | widget=TomSelect(clear_button=False), 13 | ) 14 | 15 | to_currency = forms.ModelChoiceField( 16 | queryset=Currency.objects.all(), 17 | label="", 18 | widget=TomSelect(clear_button=False), 19 | ) 20 | 21 | def __init__(self, *args, **kwargs): 22 | super(CurrencyConverterForm, self).__init__(*args, **kwargs) 23 | 24 | self.helper = FormHelper() 25 | self.helper.form_tag = False 26 | self.helper.layout = Layout( 27 | "from_currency", 28 | "to_currency", 29 | ) 30 | -------------------------------------------------------------------------------- /app/apps/mini_tools/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/mini_tools/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/mini_tools/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /app/apps/mini_tools/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/apps/mini_tools/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path( 7 | "tools/unit-price-calculator/", 8 | views.unit_price_calculator, 9 | name="unit_price_calculator", 10 | ), 11 | path( 12 | "tools/currency-converter/", 13 | views.currency_converter, 14 | name="currency_converter", 15 | ), 16 | path( 17 | "tools/currency-converter/convert/", 18 | views.currency_converter_convert, 19 | name="currency_converter_convert", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /app/apps/mini_tools/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/mini_tools/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/monthly_overview/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/monthly_overview/__init__.py -------------------------------------------------------------------------------- /app/apps/monthly_overview/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/apps/monthly_overview/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MonthlyOverviewConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.monthly_overview" 7 | -------------------------------------------------------------------------------- /app/apps/monthly_overview/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/monthly_overview/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/monthly_overview/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/monthly_overview/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/net_worth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/net_worth/__init__.py -------------------------------------------------------------------------------- /app/apps/net_worth/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/apps/net_worth/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NetWorthConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.net_worth" 7 | -------------------------------------------------------------------------------- /app/apps/net_worth/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/net_worth/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/net_worth/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /app/apps/net_worth/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/apps/net_worth/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("net-worth/current/", views.net_worth_current, name="net_worth_current"), 7 | path("net-worth/projected/", views.net_worth_projected, name="net_worth_projected"), 8 | ] 9 | -------------------------------------------------------------------------------- /app/apps/net_worth/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/net_worth/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/rules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/rules/__init__.py -------------------------------------------------------------------------------- /app/apps/rules/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from apps.rules.models import ( 4 | TransactionRule, 5 | TransactionRuleAction, 6 | UpdateOrCreateTransactionRuleAction, 7 | ) 8 | 9 | 10 | admin.site.register(TransactionRule) 11 | admin.site.register(TransactionRuleAction) 12 | admin.site.register(UpdateOrCreateTransactionRuleAction) 13 | -------------------------------------------------------------------------------- /app/apps/rules/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RulesConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.rules" 7 | 8 | def ready(self): 9 | import apps.rules.signals 10 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0003_alter_transactionruleaction_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-24 01:16 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rules', '0002_transactionrule_active_transactionrule_on_create_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name='transactionruleaction', 15 | unique_together={('rule', 'field')}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0004_alter_transactionruleaction_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2024-11-30 20:10 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rules', '0003_alter_transactionruleaction_unique_together'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='transactionruleaction', 15 | name='field', 16 | field=models.CharField(choices=[('account', 'Account'), ('type', 'Type'), ('is_paid', 'Paid'), ('date', 'Date'), ('reference_date', 'Reference Date'), ('amount', 'Amount'), ('description', 'Description'), ('notes', 'Notes'), ('category', 'Category'), ('tags', 'Tags'), ('entities', 'Entities')], max_length=50, verbose_name='Field'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0005_alter_transactionruleaction_rule.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2024-12-27 05:09 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('rules', '0004_alter_transactionruleaction_field'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='transactionruleaction', 16 | name='rule', 17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transaction_actions', to='rules.transactionrule', verbose_name='Rule'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0007_alter_updateorcreatetransactionruleaction_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-08 04:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rules', '0006_updateorcreatetransactionruleaction'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='updateorcreatetransactionruleaction', 15 | options={'verbose_name': 'Update or Create Transaction Action', 'verbose_name_plural': 'Update or Create Transaction Action Actions'}, 16 | ), 17 | migrations.AddField( 18 | model_name='updateorcreatetransactionruleaction', 19 | name='filter', 20 | field=models.TextField(blank=True, help_text='Generic expression to enable or disable execution. Should evaluate to True or False', verbose_name='Filter'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0009_alter_transactionrule_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-08 06:40 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rules', '0008_updateorcreatetransactionruleaction_search_entities_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='transactionrule', 15 | options={'verbose_name': 'Transaction rule', 'verbose_name_plural': 'Transaction rules'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='transactionruleaction', 19 | options={'verbose_name': 'Edit transaction action', 'verbose_name_plural': 'Edit transaction actions'}, 20 | ), 21 | migrations.AlterModelOptions( 22 | name='updateorcreatetransactionruleaction', 23 | options={'verbose_name': 'Update or create transaction action', 'verbose_name_plural': 'Update or create transaction actions'}, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0011_alter_updateorcreatetransactionruleaction_set_is_paid.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-02-09 20:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rules', '0010_alter_updateorcreatetransactionruleaction_search_account_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='updateorcreatetransactionruleaction', 15 | name='set_is_paid', 16 | field=models.TextField(blank=True, verbose_name='Paid'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/0013_transactionrule_on_delete.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-03-09 03:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('rules', '0012_transactionrule_owner_transactionrule_shared_with_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='transactionrule', 15 | name='on_delete', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/rules/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/rules/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/transactions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/transactions/__init__.py -------------------------------------------------------------------------------- /app/apps/transactions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TransactionsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.transactions" 7 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0003_transaction_type_alter_transaction_account.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-20 03:14 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0001_initial'), 11 | ('transactions', '0002_transaction_account_transaction_amount_and_more'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='transaction', 17 | name='type', 18 | field=models.CharField(choices=[('IN', 'Income'), ('EX', 'Expense')], default='EX', max_length=2, verbose_name='Type'), 19 | ), 20 | migrations.AlterField( 21 | model_name='transaction', 22 | name='account', 23 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.account', verbose_name='Account'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0004_alter_transaction_amount.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-23 04:05 2 | 3 | import apps.transactions.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('transactions', '0003_transaction_type_alter_transaction_account'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='transaction', 16 | name='amount', 17 | field=models.DecimalField(decimal_places=30, max_digits=42, validators=[apps.transactions.validators.validate_non_negative, apps.transactions.validators.validate_decimal_places], verbose_name='Amount'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0006_transaction_category.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-24 17:10 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('transactions', '0005_transactioncategory_alter_transaction_options_and_more'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='transaction', 16 | name='category', 17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='transactions.transactioncategory', verbose_name='Category'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0008_rename_transactiontags_transactiontag.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-25 03:11 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0007_transactiontags_alter_transactioncategory_table_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameModel( 14 | old_name='TransactionTags', 15 | new_name='TransactionTag', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0009_alter_transaction_tags.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-27 19:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0008_rename_transactiontags_transactiontag'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='transaction', 15 | name='tags', 16 | field=models.ManyToManyField(blank=True, to='transactions.transactiontag', verbose_name='Tags'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0011_remove_installmentplan_start_date_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-05 16:17 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("transactions", "0010_installmentplan_transaction_installment_plan"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="installmentplan", 15 | name="start_date", 16 | ), 17 | migrations.RemoveField( 18 | model_name="installmentplan", 19 | name="total_amount", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0012_alter_transaction_category.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-06 22:25 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("transactions", "0011_remove_installmentplan_start_date_and_more"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="transaction", 16 | name="category", 17 | field=models.ForeignKey( 18 | blank=True, 19 | null=True, 20 | on_delete=django.db.models.deletion.SET_NULL, 21 | to="transactions.transactioncategory", 22 | verbose_name="Category", 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0013_alter_transactioncategory_name_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-07 14:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("transactions", "0012_alter_transaction_category"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="transactioncategory", 15 | name="name", 16 | field=models.CharField(max_length=255, unique=True, verbose_name="Name"), 17 | ), 18 | migrations.AlterField( 19 | model_name="transactiontag", 20 | name="name", 21 | field=models.CharField(max_length=255, unique=True, verbose_name="Name"), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0017_recurringtransaction_reference_date_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-17 01:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0016_recurringtransaction_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='recurringtransaction', 15 | name='reference_date', 16 | field=models.DateField(blank=True, null=True, verbose_name='Reference Date'), 17 | ), 18 | migrations.AlterField( 19 | model_name='recurringtransaction', 20 | name='recurrence_interval', 21 | field=models.PositiveIntegerField(verbose_name='Recurrence Interval'), 22 | ), 23 | migrations.AlterField( 24 | model_name='recurringtransaction', 25 | name='recurrence_type', 26 | field=models.CharField(choices=[('day', 'day(s)'), ('week', 'week(s)'), ('month', 'month(s)'), ('year', 'year(s)')], max_length=7, verbose_name='Recurrence Type'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0018_recurringtransaction_last_generated_reference_date.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-17 02:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0017_recurringtransaction_reference_date_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='recurringtransaction', 15 | name='last_generated_reference_date', 16 | field=models.DateField(blank=True, null=True, verbose_name='Last Generated Reference Date'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0019_recurringtransaction_paused.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-18 21:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0018_recurringtransaction_last_generated_reference_date'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='recurringtransaction', 15 | name='paused', 16 | field=models.BooleanField(default=False, verbose_name='Paused'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0020_installmentplan_notes_recurringtransaction_notes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-24 17:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0019_recurringtransaction_paused'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='installmentplan', 15 | name='notes', 16 | field=models.TextField(blank=True, verbose_name='Notes'), 17 | ), 18 | migrations.AddField( 19 | model_name='recurringtransaction', 20 | name='notes', 21 | field=models.TextField(blank=True, verbose_name='Notes'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0021_alter_transaction_account.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-11-02 04:23 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0006_rename_archived_account_is_archived_and_more'), 11 | ('transactions', '0020_installmentplan_notes_recurringtransaction_notes'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='transaction', 17 | name='account', 18 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to='accounts.account', verbose_name='Account'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0022_rename_paused_recurringtransaction_is_paused.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-11-05 02:26 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0021_alter_transaction_account'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='recurringtransaction', 15 | old_name='paused', 16 | new_name='is_paused', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0024_installmentplan_entities_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2024-11-30 20:10 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0023_transactionentity_transaction_entities'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='installmentplan', 15 | name='entities', 16 | field=models.ManyToManyField(blank=True, to='transactions.transactionentity', verbose_name='Entities'), 17 | ), 18 | migrations.AddField( 19 | model_name='recurringtransaction', 20 | name='entities', 21 | field=models.ManyToManyField(blank=True, to='transactions.transactionentity', verbose_name='Entities'), 22 | ), 23 | migrations.AlterField( 24 | model_name='transaction', 25 | name='entities', 26 | field=models.ManyToManyField(blank=True, related_name='transactions', to='transactions.transactionentity', verbose_name='Entities'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0025_transactioncategory_active_transactiontag_active.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-01-04 19:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0024_installmentplan_entities_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='transactioncategory', 15 | name='active', 16 | field=models.BooleanField(default=True, help_text="Deactivated categories won't be able to be selected when creating new transactions", verbose_name='Active'), 17 | ), 18 | migrations.AddField( 19 | model_name='transactiontag', 20 | name='active', 21 | field=models.BooleanField(default=True, help_text="Deactivated tags won't be able to be selected when creating new transactions", verbose_name='Active'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0026_transactionentity_active.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.3 on 2025-01-04 19:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0025_transactioncategory_active_transactiontag_active'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='transactionentity', 15 | name='active', 16 | field=models.BooleanField(default=True, help_text="Deactivated entities won't be able to be selected when creating new transactions", verbose_name='Active'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0027_alter_transaction_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.4 on 2025-01-14 12:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0026_transactionentity_active'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='transaction', 15 | name='description', 16 | field=models.CharField(blank=True, max_length=500, verbose_name='Description'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0028_transaction_internal_note.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-19 00:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0027_alter_transaction_description'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='transaction', 15 | name='internal_note', 16 | field=models.TextField(blank=True, verbose_name='Internal Note'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0029_alter_transaction_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-19 14:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0028_transaction_internal_note'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='transaction', 15 | options={'default_manager_name': 'objects', 'verbose_name': 'Transaction', 'verbose_name_plural': 'Transactions'}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0030_transaction_deleted_transaction_deleted_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-19 14:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0029_alter_transaction_options'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='transaction', 15 | name='deleted', 16 | field=models.BooleanField(default=False, verbose_name='Deleted'), 17 | ), 18 | migrations.AddField( 19 | model_name='transaction', 20 | name='deleted_at', 21 | field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0031_alter_transaction_deleted.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-19 15:14 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0030_transaction_deleted_transaction_deleted_at'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='transaction', 15 | name='deleted', 16 | field=models.BooleanField(db_index=True, default=False, verbose_name='Deleted'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0032_transaction_created_at_transaction_updated_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-19 16:48 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('transactions', '0031_alter_transaction_deleted'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='transaction', 16 | name='created_at', 17 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 18 | preserve_default=False, 19 | ), 20 | migrations.AddField( 21 | model_name='transaction', 22 | name='updated_at', 23 | field=models.DateTimeField(auto_now=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0033_transaction_internal_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-21 01:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("transactions", "0032_transaction_created_at_transaction_updated_at"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="transaction", 15 | name="internal_id", 16 | field=models.TextField( 17 | blank=True, null=True, unique=True, verbose_name="Internal ID" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0038_transaction_owner.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-07 03:14 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('transactions', '0037_alter_transactioncategory_visibility_and_more'), 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='transaction', 18 | name='owner', 19 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_owned', to=settings.AUTH_USER_MODEL), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0039_alter_transaction_internal_id_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-07 03:16 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('transactions', '0038_transaction_owner'), 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='transaction', 17 | name='internal_id', 18 | field=models.TextField(blank=True, null=True, verbose_name='Internal ID'), 19 | ), 20 | migrations.AlterUniqueTogether( 21 | name='transaction', 22 | unique_together={('owner', 'internal_id')}, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0040_alter_transaction_unique_together_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-03-07 03:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0039_alter_transaction_internal_id_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name='transaction', 15 | unique_together=set(), 16 | ), 17 | migrations.AlterField( 18 | model_name='transaction', 19 | name='internal_id', 20 | field=models.TextField(blank=True, null=True, unique=True, verbose_name='Internal ID'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/0042_alter_transactioncategory_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.7 on 2025-03-09 21:54 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('transactions', '0041_installmentplan_add_description_to_transaction_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='transactioncategory', 15 | options={'ordering': ['name', 'id'], 'verbose_name': 'Transaction Category', 'verbose_name_plural': 'Transaction Categories'}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='transactionentity', 19 | options={'ordering': ['name', 'id'], 'verbose_name': 'Entity', 'verbose_name_plural': 'Entities'}, 20 | ), 21 | migrations.AlterModelOptions( 22 | name='transactiontag', 23 | options={'ordering': ['name', 'id'], 'verbose_name': 'Transaction Tags', 'verbose_name_plural': 'Transaction Tags'}, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /app/apps/transactions/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/transactions/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/transactions/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/transactions/templatetags/__init__.py -------------------------------------------------------------------------------- /app/apps/transactions/templatetags/currency_display.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from django import template 4 | from django.utils.formats import number_format 5 | 6 | 7 | register = template.Library() 8 | 9 | 10 | def _format_string(prefix, amount, decimal_places, suffix): 11 | if isinstance(amount, (int, float, Decimal)): 12 | formatted_amount = number_format( 13 | value=abs(amount), decimal_pos=decimal_places, force_grouping=True 14 | ) 15 | if amount < 0: 16 | return f"-{prefix}{formatted_amount}{suffix}" 17 | else: 18 | return f"{prefix}{formatted_amount}{suffix}" 19 | else: 20 | return "ERR" 21 | 22 | 23 | @register.simple_tag(name="currency_display") 24 | def currency_display(amount, prefix, suffix, decimal_places): 25 | return _format_string(prefix, amount, decimal_places, suffix) 26 | -------------------------------------------------------------------------------- /app/apps/transactions/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/transactions/utils/__init__.py -------------------------------------------------------------------------------- /app/apps/transactions/validators.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | def validate_decimal_places(value): 6 | if abs(value.as_tuple().exponent) > 30: 7 | raise ValidationError( 8 | _("%(value)s has too many decimal places. Maximum is 30."), 9 | params={"value": value}, 10 | ) 11 | 12 | 13 | def validate_non_negative(value): 14 | if value < 0: 15 | raise ValidationError( 16 | _("%(value)s is not a non-negative number"), 17 | params={"value": value}, 18 | ) 19 | -------------------------------------------------------------------------------- /app/apps/transactions/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .transactions import * 2 | from .tags import * 3 | from .entities import * 4 | from .categories import * 5 | from .actions import * 6 | from .installment_plans import * 7 | from .recurring_transactions import * 8 | -------------------------------------------------------------------------------- /app/apps/users/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "apps.users.apps.UsersConfig" 2 | -------------------------------------------------------------------------------- /app/apps/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.users" 7 | 8 | def ready(self): 9 | import apps.users.signals 10 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0002_usersettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-07 00:17 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='UserSettings', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('hide_amounts', models.BooleanField(default=False)), 20 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to=settings.AUTH_USER_MODEL)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0005_alter_usersettings_language.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-11 01:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0004_alter_usersettings_timezone'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='language', 16 | field=models.CharField(choices=[('auto', 'Auto'), ('en', 'English'), ('pt-br', 'Português (Brasil)')], default='en', max_length=10, verbose_name='Language'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0006_alter_usersettings_language.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-11 01:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0005_alter_usersettings_language'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='language', 16 | field=models.CharField(choices=[('auto', 'Auto'), ('en', 'English'), ('pt-br', 'Português (Brasil)')], default='auto', max_length=10, verbose_name='Language'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0007_usersettings_mute_sounds.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-10-14 14:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0006_alter_usersettings_language"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="usersettings", 15 | name="mute_sounds", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0008_usersettings_start_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-21 01:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0007_usersettings_mute_sounds'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usersettings', 15 | name='start_page', 16 | field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Overview > Monthly'), ('YEARLY_OVERVIEW', 'Overview > Yearly')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0009_alter_usersettings_start_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-22 15:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0008_usersettings_start_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='start_page', 16 | field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly Overview'), ('YEARLY_OVERVIEW', 'Yearly Overview'), ('NETWORTH', 'Net Worth')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0010_alter_usersettings_start_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-28 00:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0009_alter_usersettings_start_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='start_page', 16 | field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly Overview'), ('YEARLY_OVERVIEW', 'Yearly Overview'), ('NETWORTH', 'Net Worth'), ('ALL_TRANSACTIONS', 'All Transactions')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0011_alter_usersettings_start_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-10-30 03:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0010_alter_usersettings_start_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='start_page', 16 | field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly Overview'), ('YEARLY_OVERVIEW', 'Yearly Overview'), ('NETWORTH', 'Net Worth'), ('ALL_TRANSACTIONS', 'All Transactions'), ('CALENDAR', 'Calendar')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0012_alter_usersettings_start_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.2 on 2024-11-02 04:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0011_alter_usersettings_start_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='start_page', 16 | field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly'), ('YEARLY_OVERVIEW_CURRENCY', 'Yearly by currency'), ('YEARLY_OVERVIEW_ACCOUNT', 'Yearly by account'), ('NETWORTH', 'Net Worth'), ('ALL_TRANSACTIONS', 'All Transactions'), ('CALENDAR', 'Calendar')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0013_usersettings_date_format_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-20 17:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0012_alter_usersettings_start_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usersettings', 15 | name='date_format', 16 | field=models.CharField(default='SHORT_DATE_FORMAT', max_length=100), 17 | ), 18 | migrations.AddField( 19 | model_name='usersettings', 20 | name='datetime_format', 21 | field=models.CharField(default='SHORT_DATETIME_FORMAT', max_length=100), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0015_alter_usersettings_language.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-24 19:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0014_alter_usersettings_date_format_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='language', 16 | field=models.CharField(choices=[('auto', 'Auto'), ('en', 'English'), ('pt-br', 'Português (Brasil)')], default='auto', max_length=10, verbose_name='Language'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0016_alter_usersettings_language.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-25 18:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0015_alter_usersettings_language'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='language', 16 | field=models.CharField(choices=[('auto', 'Auto'), ('en', 'English'), ('nl', 'Nederlands'), ('pt-br', 'Português (Brasil)')], default='auto', max_length=10, verbose_name='Language'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0017_usersettings_number_format.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-01-27 12:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0016_alter_usersettings_language'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usersettings', 15 | name='number_format', 16 | field=models.CharField(default='AA', max_length=2, verbose_name='Number Format'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0018_alter_usersettings_start_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-02 02:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0017_usersettings_number_format'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='start_page', 16 | field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly'), ('YEARLY_OVERVIEW_CURRENCY', 'Yearly by currency'), ('YEARLY_OVERVIEW_ACCOUNT', 'Yearly by account'), ('NETWORTH_CURRENT', 'Current Net Worth'), ('NETWORTH_PROJECTED', 'Projected Net Worth'), ('ALL_TRANSACTIONS', 'All Transactions'), ('CALENDAR', 'Calendar')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/0019_alter_usersettings_language.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-02-24 19:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0018_alter_usersettings_start_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='usersettings', 15 | name='language', 16 | field=models.CharField(choices=[('auto', 'Auto'), ('de', 'Deutsch'), ('en', 'English'), ('nl', 'Nederlands'), ('pt-br', 'Português (Brasil)')], default='auto', max_length=10, verbose_name='Language'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /app/apps/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/users/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/users/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save 2 | from django.dispatch import receiver 3 | from django.contrib.auth import get_user_model 4 | 5 | from apps.users.models import UserSettings 6 | 7 | User = get_user_model() 8 | 9 | 10 | @receiver(post_save, sender=User) 11 | def create_user_settings(sender, instance, created, **kwargs): 12 | if created: 13 | UserSettings.objects.create(user=instance) 14 | -------------------------------------------------------------------------------- /app/apps/yearly_overview/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/yearly_overview/__init__.py -------------------------------------------------------------------------------- /app/apps/yearly_overview/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class YearlyOverviewConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "apps.yearly_overview" 7 | -------------------------------------------------------------------------------- /app/apps/yearly_overview/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/apps/yearly_overview/migrations/__init__.py -------------------------------------------------------------------------------- /app/apps/yearly_overview/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("yearly/currency/", views.index_by_currency, name="yearly_index_currency"), 7 | path("yearly/account/", views.index_by_account, name="yearly_index_account"), 8 | path( 9 | "yearly/currency//", 10 | views.index_yearly_overview_by_currency, 11 | name="yearly_overview_currency", 12 | ), 13 | path( 14 | "yearly-overview//currency/data/", 15 | views.yearly_overview_by_currency, 16 | name="yearly_overview_currency_data", 17 | ), 18 | path( 19 | "yearly/account//", 20 | views.index_yearly_overview_by_account, 21 | name="yearly_overview_account", 22 | ), 23 | path( 24 | "yearly-overview//account/data/", 25 | views.yearly_overview_by_account, 26 | name="yearly_overview_account_data", 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /app/import_presets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/import_presets/.gitkeep -------------------------------------------------------------------------------- /app/import_presets/cajamar/config.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | file_type: xls 3 | skip_errors: true 4 | trigger_transaction_rules: true 5 | importing: transactions 6 | start_row: 1 7 | sheets: "*" 8 | 9 | mapping: 10 | account: 11 | target: account 12 | default: "" 13 | type: name 14 | 15 | type: 16 | source: Importe 17 | target: type 18 | detection_method: sign 19 | 20 | internal_id: 21 | target: internal_id 22 | transformations: 23 | - type: hash 24 | fields: ["Fecha", "Concepto", "Importe", "Saldo"] 25 | date: 26 | source: "Fecha" 27 | target: date 28 | format: "%d-%m-%Y" 29 | 30 | description: 31 | source: Concepto 32 | target: description 33 | 34 | amount: 35 | source: Importe 36 | target: amount 37 | 38 | is_paid: 39 | target: is_paid 40 | detection_method: always_paid 41 | 42 | deduplication: 43 | - type: compare 44 | fields: 45 | - internal_id 46 | match_type: strict 47 | -------------------------------------------------------------------------------- /app/import_presets/cajamar/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "eitchtee,Pablo Hinojosa", 3 | "description": "Importe sus movimientos desde su cuenta de Cajamar", 4 | "schema_version": 1, 5 | "name": "Grupo Cooperativo Cajamar", 6 | "message": "Cambia '' por el nombre de tu cuenta de Cajamar dentro de WYGIWYH" 7 | } 8 | -------------------------------------------------------------------------------- /app/import_presets/nuconta/config.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | file_type: csv 3 | delimiter: "," 4 | encoding: utf-8 5 | skip_lines: 0 6 | importing: transactions 7 | trigger_transaction_rules: true 8 | skip_errors: true 9 | 10 | mapping: 11 | account: 12 | target: account 13 | default: 14 | type: name 15 | 16 | date: 17 | target: date 18 | source: Data 19 | format: "%d/%m/%Y" 20 | 21 | amount: 22 | target: amount 23 | source: Valor 24 | 25 | description: 26 | target: description 27 | source: Descrição 28 | transformations: 29 | - type: split 30 | separator: " - " 31 | index: 0 32 | 33 | type: 34 | source: "Valor" 35 | target: "type" 36 | detection_method: sign 37 | 38 | notes: 39 | target: notes 40 | source: Notes 41 | 42 | internal_id: 43 | target: internal_id 44 | source: Identificador 45 | 46 | is_paid: 47 | target: is_paid 48 | detection_method: always_paid 49 | 50 | deduplicate: 51 | - type: compare 52 | fields: 53 | - internal_id 54 | match_type: lax 55 | -------------------------------------------------------------------------------- /app/import_presets/nuconta/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "eitchtee", 3 | "description": "Importe suas transações da conta corrente do Nubank", 4 | "schema_version": 1, 5 | "name": "Nubank - Conta Corrente", 6 | "message": "Mude '' para o nome da sua Nuconta dentro do WYGIWYH" 7 | } 8 | -------------------------------------------------------------------------------- /app/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "WYGIWYH.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /app/static/img/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /app/static/img/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /app/static/img/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /app/static/img/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /app/static/img/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /app/static/img/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /app/static/img/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/apple-icon.png -------------------------------------------------------------------------------- /app/static/img/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 4 | -------------------------------------------------------------------------------- /app/static/img/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /app/static/img/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /app/static/img/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /app/static/img/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/favicon.ico -------------------------------------------------------------------------------- /app/static/img/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /app/static/img/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /app/static/img/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /app/static/img/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /app/static/img/logo-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/static/img/pwa/splash-640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/pwa/splash-640x1136.png -------------------------------------------------------------------------------- /app/static/img/pwa/splash-750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/img/pwa/splash-750x1334.png -------------------------------------------------------------------------------- /app/static/sounds/pop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/sounds/pop.mp3 -------------------------------------------------------------------------------- /app/static/sounds/success.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/app/static/sounds/success.mp3 -------------------------------------------------------------------------------- /app/templates/account_groups/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add account group' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/account_groups/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit account group' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/account_groups/fragments/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/account_groups/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Account Groups' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/accounts/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add account' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/accounts/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit account' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/accounts/fragments/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/accounts/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Accounts' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base_site.html" %} 2 | 3 | {% block extrahead %} 4 | {{ block.super }} 5 | 6 | {% include 'includes/head/favicons.html' %} 7 | 8 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /app/templates/calendar_view/fragments/list_transactions.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Transactions on' %} {{ date|date:"SHORT_DATE_FORMAT" }}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% for transaction in transactions %} 10 | 11 | {% empty %} 12 | 14 | {% endfor %} 15 | {# Floating bar #} 16 | 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /app/templates/categories/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add category' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/categories/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit category' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/categories/fragments/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/categories/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Categories' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/common/fragments/toasts.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load toast_bg %} 3 | {% if messages %} 4 | {% for message in messages %} 5 | 19 | {% endfor %} 20 | {% endif %} 21 | -------------------------------------------------------------------------------- /app/templates/cotton/amount/display.html: -------------------------------------------------------------------------------- 1 | {% load currency_display %} 2 | 3 | {% if not divless %} 4 |
5 | {% endif %} 6 | 9 | {{ slot }} 10 | {% if not divless %} 11 |
12 | {% endif %} -------------------------------------------------------------------------------- /app/templates/cotton/config/search.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 12 | -------------------------------------------------------------------------------- /app/templates/cotton/msg/empty.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

{{ title }}

6 |

{{ subtitle }}

7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /app/templates/cotton/ui/card.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ slot }} 4 |
5 |
-------------------------------------------------------------------------------- /app/templates/cotton/ui/help_icon.html: -------------------------------------------------------------------------------- 1 | {% spaceless %} 2 | {% load i18n %} 3 | 6 | 7 | 8 | {% endspaceless %} 9 | -------------------------------------------------------------------------------- /app/templates/cotton/ui/info_card.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% if icon %}{% else %}{{ title.0 }}{% endif %} 4 |
5 |
6 |
{{ title }}{% if help_text %}{% endif %}
7 | {{ slot }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /app/templates/currencies/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add currency' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/currencies/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit currency' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/currencies/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Currencies' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/dca/fragments/entry/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add DCA entry' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/dca/fragments/entry/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit DCA entry' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/dca/fragments/strategy/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add DCA strategy' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/dca/fragments/strategy/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit DCA strategy' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/dca/fragments/strategy/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/dca/pages/strategy_detail_index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{{ strategy.name }} :: {% translate 'Dollar Cost Average Strategy' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/dca/pages/strategy_index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Dollar Cost Average Strategies' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/entities/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add entity' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/entities/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit entity' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/entities/fragments/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/entities/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Entities' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/exchange_rates/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add exchange rate' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/exchange_rates/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit exchange rate' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/exchange_rates/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Exchange Rates' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/exchange_rates_services/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add exchange rate' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/exchange_rates_services/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit exchange rate' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/exchange_rates_services/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Automatic Exchange Rates' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/export_app/fragments/export.html: -------------------------------------------------------------------------------- 1 | {% extends "extends/offcanvas.html" %} 2 | {% load crispy_forms_tags %} 3 | {% load i18n %} 4 | 5 | {% block title %}{% translate 'Export' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 |
10 | {% crispy form %} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /app/templates/export_app/fragments/restore.html: -------------------------------------------------------------------------------- 1 | {% extends "extends/offcanvas.html" %} 2 | {% load crispy_forms_tags %} 3 | {% load i18n %} 4 | 5 | {% block title %}{% translate 'Restore' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 |
14 | {% crispy form %} 15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /app/templates/extends/offcanvas.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load webpack_loader %} 3 |
4 |
{% block title %}{% endblock %}
5 | 6 |
7 |
10 | {% block body %}{% endblock %} 11 |
12 | -------------------------------------------------------------------------------- /app/templates/import_app/fragments/profiles/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load json %} 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block title %}{% translate 'Add new import profile' %}{% endblock %} 7 | 8 | {% block body %} 9 | {% if message %} 10 | 15 | {% endif %} 16 |
17 | {% crispy form %} 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /app/templates/import_app/fragments/profiles/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit import profile' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/import_app/fragments/runs/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Import file with profile' %} {{ profile.name }}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/import_app/fragments/runs/log.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Logs for' %} #{{ run.id }}{% endblock %} 6 | 7 | {% block body %} 8 |
9 |
10 | {{ run.logs|linebreaks }} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /app/templates/import_app/pages/profiles_index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Import Profiles' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/includes/placeholders.html: -------------------------------------------------------------------------------- 1 | {# We use this to preload dynamically generated tailwind classes so the compiler can build them ahead of time #} 2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /app/templates/includes/scripts.html: -------------------------------------------------------------------------------- 1 | {% load webpack_loader %} 2 | 3 | {% javascript_pack 'bootstrap' attrs="defer" %} 4 | {% javascript_pack 'sweetalert2' attrs="defer" %} 5 | {% javascript_pack 'select' attrs="defer" %} 6 | {% javascript_pack 'datepicker' %} 7 | {% javascript_pack 'autosize' attrs="defer" %} 8 | 9 | {% include 'includes/scripts/hyperscript/init_tom_select.html' %} 10 | {% include 'includes/scripts/hyperscript/init_date_picker.html' %} 11 | {% include 'includes/scripts/hyperscript/hide_amount.html' %} 12 | {% include 'includes/scripts/hyperscript/tooltip.html' %} 13 | {% include 'includes/scripts/hyperscript/autosize.html' %} 14 | {% include 'includes/scripts/hyperscript/htmx_error_handler.html' %} 15 | {% include 'includes/scripts/hyperscript/sounds.html' %} 16 | {% include 'includes/scripts/hyperscript/swal.html' %} 17 | 18 | {% javascript_pack 'htmx' attrs="defer" %} 19 | {% javascript_pack 'charts' %} 20 | 21 | 28 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/autosize.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/hide_amount.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/init_date_picker.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/init_tom_select.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/sounds.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/swal.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 23 | -------------------------------------------------------------------------------- /app/templates/includes/scripts/hyperscript/tooltip.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /app/templates/includes/styles.html: -------------------------------------------------------------------------------- 1 | {% load webpack_loader %} 2 | 3 | {% stylesheet_pack 'style' %} 4 | {#{% stylesheet_pack 'select' %}#} 5 | -------------------------------------------------------------------------------- /app/templates/includes/toasts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 | -------------------------------------------------------------------------------- /app/templates/insights/fragments/late_transactions.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 |
6 | {% if transactions %} 7 | {% for transaction in transactions %} 8 | 9 | {% endfor %} 10 | {# Floating bar #} 11 | 12 | {% else %} 13 | 17 | {% endif %} 18 |
19 | -------------------------------------------------------------------------------- /app/templates/insights/fragments/latest_transactions.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 |
6 | {% if transactions %} 7 | {% for transaction in transactions %} 8 | 9 | {% endfor %} 10 | {# Floating bar #} 11 | 12 | {% else %} 13 | 15 | {% endif %} 16 |
17 | -------------------------------------------------------------------------------- /app/templates/installment_plans/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add installment plan' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/installment_plans/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit installment plan' %}{% endblock %} 6 | 7 | {% block body %} 8 |
11 | {% crispy form %} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /app/templates/installment_plans/fragments/list_transactions.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Installments' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% for transaction in transactions %} 10 | 11 | {% endfor %} 12 | {# Floating bar #} 13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /app/templates/installment_plans/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Installment Plans' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/layouts/base_auth.html: -------------------------------------------------------------------------------- 1 | {% load pwa %} 2 | {% load title %} 3 | {% load webpack_loader %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% filter site_title %} 11 | {% block title %} 12 | {% endblock title %} 13 | {% endfilter %} 14 | 15 | 16 | {% include 'includes/head/favicons.html' %} 17 | {% progressive_web_app_meta %} 18 | 19 | {% include 'includes/styles.html' %} 20 | {% block extra_styles %}{% endblock %} 21 | 22 | 23 |
24 | {% block content %}{% endblock %} 25 |
26 | 27 | {% include 'includes/scripts.html' %} 28 | {% block extra_js %}{% endblock %} 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/templates/mini_tools/currency_converter/converted_value.html: -------------------------------------------------------------------------------- 1 | {% load decimal %} 2 | {% load currency_display %} 3 | {% load formats %} 4 | 10 | -------------------------------------------------------------------------------- /app/templates/monthly_overview/fragments/monthly_account_summary.html: -------------------------------------------------------------------------------- 1 | {% load tools %} 2 | {% load i18n %} 3 | {% load currency_display %} 4 | 5 |
6 | {% for account_id, account in account_data.items %} 7 |
8 | 10 |
11 | {% empty %} 12 |
13 | 15 | {% endfor %} 16 |
17 |
18 | -------------------------------------------------------------------------------- /app/templates/monthly_overview/fragments/monthly_currency_summary.html: -------------------------------------------------------------------------------- 1 | {% load tools %} 2 | {% load i18n %} 3 | {% load currency_display %} 4 |
5 | {% for currency_id, currency in currency_data.items %} 6 |
7 | 9 |
10 | {% empty %} 11 |
12 | 14 |
15 | {% endfor %} 16 |
17 | -------------------------------------------------------------------------------- /app/templates/recurring_transactions/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add recurring transaction' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/recurring_transactions/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit recurring transaction' %}{% endblock %} 6 | 7 | {% block body %} 8 |
11 | {% crispy form %} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /app/templates/recurring_transactions/fragments/list_transactions.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Transactions' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% for transaction in transactions %} 10 | 11 | {% endfor %} 12 | {# Floating bar #} 13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /app/templates/recurring_transactions/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Recurring Transactions' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/transaction_rule/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add transaction rule' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/transaction_rule/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit transaction rule' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/transaction_rule/transaction_rule_action/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add action to transaction rule' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/transaction_rule/transaction_rule_action/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit transaction rule action' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/transaction_rule/update_or_create_transaction_rule_action/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add action to transaction rule' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/fragments/transaction_rule/update_or_create_transaction_rule_action/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit transaction rule action' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/rules/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Rules' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/tags/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add tag' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/tags/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit tag' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/tags/fragments/share.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Share settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/tags/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Tags' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'New transaction' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/add_installment_plan.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add Installment Plan' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/all_account_summary.html: -------------------------------------------------------------------------------- 1 | {% load tools %} 2 | {% load i18n %} 3 | {% load currency_display %} 4 | 5 |
6 | {% for account_id, account in account_data.items %} 7 |
8 | 10 |
11 | {% empty %} 12 |
13 | 15 | {% endfor %} 16 |
17 |
18 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/all_currency_summary.html: -------------------------------------------------------------------------------- 1 | {% load tools %} 2 | {% load i18n %} 3 | {% load currency_display %} 4 |
5 | {% for currency_id, currency in currency_data.items %} 6 |
7 | 9 |
10 | {% empty %} 11 |
12 | 14 |
15 | {% endfor %} 16 |
17 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/bulk_edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Bulk Editing' %}{% endblock %} 6 | 7 | {% block body %} 8 |

{% trans 'Editing' %} {{ transactions|length }} {% trans 'transactions' %}

9 |
10 | {% for transaction in transactions %} 11 | 12 | {% endfor %} 13 |
14 |
15 | {% crispy form %} 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit transaction' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/edit_installment_plan.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit transaction' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/item.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/transfer.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'New transfer' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/transactions/fragments/trash_list.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
3 | {% for transaction in transactions %} 4 | 5 | {% empty %} 6 | 8 | {% endfor %} 9 | {# Floating bar #} 10 | 11 |
12 | -------------------------------------------------------------------------------- /app/templates/transactions/pages/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'layouts/base.html' %} 2 | {% load crispy_forms_tags %} 3 | {% load i18n %} 4 | 5 | {% block title %}{% translate 'New transaction' %}{% endblock %} 6 | 7 | {% block content %} 8 |
11 |
12 | {% crispy form form.helper_simple %} 13 |
14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /app/templates/transactions/pages/trash.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Deleted transactions' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 |
9 |
{% translate 'Deleted transactions' %}
10 |
11 | 12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /app/templates/transactions/widgets/paid_toggle_button.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load crispy_forms_field %} 3 | 4 |
5 |
6 | 8 | 9 | 10 | 12 | 13 |
14 | 15 | {% if field.help_text %} 16 |
{{ field.help_text|safe }}
17 | {% endif %} 18 |
19 | -------------------------------------------------------------------------------- /app/templates/transactions/widgets/transaction_type_filter_buttons.html: -------------------------------------------------------------------------------- 1 | {% load crispy_forms_field %} 2 | 3 |
11 | {% for choice in field.field.choices %} 12 | 18 | 22 | {% endfor %} 23 |
24 | -------------------------------------------------------------------------------- /app/templates/users/fragments/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Add user' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/users/fragments/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'Edit user' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/users/fragments/user_settings.html: -------------------------------------------------------------------------------- 1 | {% extends 'extends/offcanvas.html' %} 2 | {% load i18n %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}{% translate 'User Settings' %}{% endblock %} 6 | 7 | {% block body %} 8 |
9 | {% crispy form %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /app/templates/users/generic/hide_amounts.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% translate 'Hide amounts' %} 3 | -------------------------------------------------------------------------------- /app/templates/users/generic/mute_sounds.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% translate 'Mute sounds' %} 3 | -------------------------------------------------------------------------------- /app/templates/users/generic/play_sounds.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% translate 'Play sounds' %} 3 | 4 | -------------------------------------------------------------------------------- /app/templates/users/generic/show_amounts.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% translate 'Show amounts' %} 3 | 4 | -------------------------------------------------------------------------------- /app/templates/users/pages/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% translate 'Users' %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /app/templates/yearly_overview/fragments/account_data.html: -------------------------------------------------------------------------------- 1 | {% load tools %} 2 | {% load i18n %} 3 |
4 | {% for account_id, account in totals.items %} 5 |
6 | 8 |
9 | {% empty %} 10 |
11 | 13 |
14 | {% endfor %} 15 |
16 | -------------------------------------------------------------------------------- /app/templates/yearly_overview/fragments/currency_data.html: -------------------------------------------------------------------------------- 1 | {% load tools %} 2 | {% load i18n %} 3 |
4 | {% for currency_id, currency in totals.items %} 5 |
6 | 8 |
9 | {% empty %} 10 |
11 | 13 |
14 | {% endfor %} 15 |
16 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: eitchtee/wygiwyh:latest 4 | container_name: ${SERVER_NAME} 5 | command: /start-single 6 | ports: 7 | - "${OUTBOUND_PORT}:8000" 8 | env_file: 9 | - .env 10 | depends_on: 11 | - db 12 | restart: unless-stopped 13 | 14 | db: 15 | image: postgres:15 16 | container_name: ${DB_NAME} 17 | restart: unless-stopped 18 | volumes: 19 | - ./postgres_data:/var/lib/postgresql/data/ 20 | environment: 21 | - POSTGRES_USER=${SQL_USER} 22 | - POSTGRES_PASSWORD=${SQL_PASSWORD} 23 | - POSTGRES_DB=${SQL_DATABASE} 24 | -------------------------------------------------------------------------------- /docker/dev/django/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | rm -f /tmp/migrations_complete 8 | 9 | python manage.py migrate 10 | 11 | # Create flag file to signal migrations are complete 12 | touch /tmp/migrations_complete 13 | 14 | python manage.py setup_users 15 | 16 | exec python manage.py runserver 0.0.0.0:8000 17 | -------------------------------------------------------------------------------- /docker/dev/procrastinate/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | 6 | 7 | # Wait for migrations to complete 8 | until [ -f /tmp/migrations_complete ]; do 9 | echo "Procastinate is waiting for web app to start..." 10 | sleep 2 11 | done 12 | 13 | rm -f /tmp/migrations_complete 14 | 15 | exec watchfiles --filter python "python manage.py procrastinate worker" 16 | -------------------------------------------------------------------------------- /docker/dev/supervisord/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | export TASK_WORKERS=${TASK_WORKERS:=1} 8 | 9 | exec supervisord -c /etc/supervisor/conf.d/supervisord.conf 10 | -------------------------------------------------------------------------------- /docker/dev/supervisord/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/null 4 | logfile_maxbytes=0 5 | pidfile=/tmp/supervisord.pid 6 | user=root 7 | 8 | [supervisorctl] 9 | serverurl=unix:///run/supervisord.sock 10 | 11 | [rpcinterface:supervisor] 12 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 13 | 14 | [unix_http_server] 15 | file=/run/supervisord.sock 16 | chmod=0700 17 | 18 | [program:web] 19 | directory=/usr/src/app 20 | command=/bin/bash /start 21 | stdout_logfile=/dev/fd/1 22 | stdout_logfile_maxbytes=0 23 | stderr_logfile=/dev/fd/1 24 | stderr_logfile_maxbytes=0 25 | autorestart=true 26 | startretries=5 27 | 28 | [program:procrastinate] 29 | directory=/usr/src/app 30 | command=/bin/bash /start-procrastinate 31 | process_name=%(program_name)s_%(process_num)02d 32 | numprocs=%(ENV_TASK_WORKERS)s 33 | numprocs_start=1 34 | stdout_logfile=/dev/fd/1 35 | stdout_logfile_maxbytes=0 36 | stderr_logfile=/dev/fd/1 37 | stderr_logfile_maxbytes=0 38 | autorestart=true 39 | startretries=5 40 | -------------------------------------------------------------------------------- /docker/dev/webpack/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/node:18-bookworm-slim 2 | 3 | WORKDIR /usr/src/frontend 4 | 5 | COPY ./frontend/package.json . 6 | 7 | RUN npm install --verbose && npm cache clean --force 8 | 9 | ENV PATH ./node_modules/.bin/:$PATH 10 | -------------------------------------------------------------------------------- /docker/prod/django/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | # Remove flag file if it exists from previous run 8 | rm -f /tmp/migrations_complete 9 | 10 | python manage.py collectstatic --noinput 11 | python manage.py migrate 12 | 13 | # Create flag file to signal migrations are complete 14 | touch /tmp/migrations_complete 15 | 16 | python manage.py setup_users 17 | 18 | exec gunicorn WYGIWYH.wsgi:application --bind 0.0.0.0:8000 --timeout 600 19 | -------------------------------------------------------------------------------- /docker/prod/procrastinate/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | 6 | 7 | # Wait for migrations to complete 8 | until [ -f /tmp/migrations_complete ]; do 9 | echo "Procastinate is waiting for web app to start..." 10 | sleep 2 11 | done 12 | 13 | exec python manage.py procrastinate worker 14 | -------------------------------------------------------------------------------- /docker/prod/supervisord/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | export TASK_WORKERS=${TASK_WORKERS:=1} 8 | 9 | exec supervisord -c /etc/supervisor/conf.d/supervisord.conf 10 | -------------------------------------------------------------------------------- /docker/prod/supervisord/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/null 4 | logfile_maxbytes=0 5 | pidfile=/tmp/supervisord.pid 6 | 7 | [supervisorctl] 8 | serverurl=unix:///tmp/supervisord.sock 9 | 10 | [unix_http_server] 11 | file=/tmp/supervisord.sock 12 | chmod=0700 13 | 14 | [program:web] 15 | user=app 16 | directory=/usr/src/app 17 | command=/bin/bash /start 18 | stdout_logfile=/dev/fd/1 19 | stdout_logfile_maxbytes=0 20 | stderr_logfile=/dev/fd/1 21 | stderr_logfile_maxbytes=0 22 | autorestart=true 23 | startretries=5 24 | 25 | [program:procrastinate] 26 | user=app 27 | directory=/usr/src/app 28 | command=/bin/bash /start-procrastinate 29 | process_name=%(program_name)s_%(process_num)02d 30 | numprocs=%(ENV_TASK_WORKERS)s 31 | numprocs_start=1 32 | stdout_logfile=/dev/fd/1 33 | stdout_logfile_maxbytes=0 34 | stderr_logfile=/dev/fd/1 35 | stderr_logfile_maxbytes=0 36 | autorestart=true 37 | startretries=5 38 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": "3.0.0" 8 | } 9 | ] 10 | ], 11 | "plugins": [ 12 | "@babel/plugin-syntax-dynamic-import", 13 | "@babel/plugin-proposal-class-properties" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | [production staging] 2 | >5% 3 | last 2 versions 4 | not ie > 0 5 | not ie_mob > 0 6 | Firefox ESR 7 | 8 | [development] 9 | last 1 chrome version 10 | last 1 firefox version 11 | last 1 edge version 12 | -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 8, 11 | "sourceType": "module", 12 | "requireConfigFile": false 13 | }, 14 | "rules": { 15 | "semi": 2 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | build 6 | 7 | # misc 8 | .DS_Store 9 | 10 | npm-debug.log 11 | yarn-error.log 12 | yarn.lock 13 | .yarnclean 14 | .vscode 15 | .idea 16 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/hydrogen 2 | -------------------------------------------------------------------------------- /frontend/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard-scss", 3 | "rules": { 4 | "at-rule-no-unknown": null, 5 | "scss/at-rule-no-unknown": [ 6 | true, 7 | { 8 | "ignoreAtRules": [ 9 | "tailwind" 10 | ] 11 | } 12 | ], 13 | "scss/at-import-partial-extension": null 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This project was created with [python-webpack-boilerplate](https://github.com/AccordBox/python-webpack-boilerplate) 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm run start` 10 | 11 | `npm run start` will launch a server process, which makes `live reloading` possible. 12 | 13 | If you change JS or SCSS files, the web page would auto refresh after the change. Now the server is working on port 9091 by default, but you can change it in `webpack/webpack.config.dev.js` 14 | 15 | ### `npm run watch` 16 | 17 | run webpack in `watch` mode. 18 | 19 | ### `npm run build` 20 | 21 | [production mode](https://webpack.js.org/guides/production/), Webpack would focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. 22 | -------------------------------------------------------------------------------- /frontend/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/frontend/assets/images/.gitkeep -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | const postcssPresetEnv = require("postcss-preset-env"); 2 | const tailwindcss = require('tailwindcss'); 3 | 4 | module.exports = { 5 | plugins: [postcssPresetEnv({ 6 | /* use stage 2 features + disable logical properties and values rule */ 7 | stage: 2, 8 | features: { 9 | 'logical-properties-and-values': false 10 | } 11 | }), tailwindcss], 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/application/_htmx.js: -------------------------------------------------------------------------------- 1 | import htmx from "htmx.org"; 2 | window.htmx = htmx; 3 | -------------------------------------------------------------------------------- /frontend/src/application/autosize.js: -------------------------------------------------------------------------------- 1 | import autosize from "autosize/dist/autosize"; 2 | 3 | window.autosize = autosize; 4 | -------------------------------------------------------------------------------- /frontend/src/application/bootstrap.js: -------------------------------------------------------------------------------- 1 | // Import all of Bootstrap's JS 2 | import * as bootstrap from 'bootstrap'; // eslint-disable-line no-unused-vars 3 | window.bootstrap = bootstrap; 4 | 5 | function initiateToasts() { 6 | const toastElList = document.querySelectorAll('.toast'); 7 | const toastList = [...toastElList].map(toastEl => new bootstrap.Toast(toastEl)); // eslint-disable-line no-undef 8 | 9 | for (let i = 0; i < toastList.length; i++) { 10 | if (toastList[i].isShown() === false) { 11 | toastList[i].show(); 12 | toastList[i]._element.addEventListener('hidden.bs.toast', (event) => { 13 | event.target.remove(); 14 | }); 15 | } 16 | } 17 | } 18 | 19 | document.addEventListener('DOMContentLoaded', initiateToasts, false); 20 | document.addEventListener('htmx:afterSwap', initiateToasts, false); 21 | initiateToasts(); 22 | -------------------------------------------------------------------------------- /frontend/src/application/charts.js: -------------------------------------------------------------------------------- 1 | import Chart from 'chart.js/auto'; 2 | import {SankeyController, Flow} from 'chartjs-chart-sankey'; 3 | 4 | Chart.register(SankeyController, Flow); 5 | window.Chart = Chart; 6 | -------------------------------------------------------------------------------- /frontend/src/application/jquery.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | window.jQuery = $; 3 | window.$ = $; 4 | -------------------------------------------------------------------------------- /frontend/src/application/style.js: -------------------------------------------------------------------------------- 1 | // Import our custom CSS 2 | import '../styles/style.scss'; 3 | -------------------------------------------------------------------------------- /frontend/src/application/sweetalert2.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | window.Swal = Swal; 3 | -------------------------------------------------------------------------------- /frontend/src/styles/_font-awesome.scss: -------------------------------------------------------------------------------- 1 | @import "@fortawesome/fontawesome-free/css/fontawesome.min.css"; 2 | @import "@fortawesome/fontawesome-free/css/solid.min.css"; 3 | @import "@fortawesome/fontawesome-free/css/regular.min.css"; 4 | @import "@fortawesome/fontawesome-free/css/brands.min.css"; 5 | -------------------------------------------------------------------------------- /frontend/src/styles/_scrollbar.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | /* custom scrollbar */ 4 | ::-webkit-scrollbar { 5 | width: 10px; 6 | } 7 | 8 | ::-webkit-scrollbar-track { 9 | background-color: transparent; 10 | } 11 | 12 | ::-webkit-scrollbar-thumb { 13 | background-color: $primary; 14 | border-radius: 20px; 15 | border: 4px solid transparent; 16 | background-clip: content-box; 17 | } 18 | 19 | ::-webkit-scrollbar-thumb:hover { 20 | background-color: #ae8000; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/styles/_tailwind.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /frontend/src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable scss/dollar-variable-empty-line-before */ 2 | 3 | $white: #fff !default; 4 | $gray-100: #f8f9fa !default; 5 | $gray-200: #ebebeb !default; 6 | $gray-300: #dee2e6 !default; 7 | $gray-400: #ced4da !default; 8 | $gray-500: #adb5bd !default; 9 | $gray-600: #888 !default; 10 | $gray-700: #444 !default; 11 | $gray-800: #303030 !default; 12 | $gray-900: #222 !default; 13 | $black: #000 !default; 14 | 15 | $blue: #375a7f !default; 16 | $indigo: #6610f2 !default; 17 | $purple: #6f42c1 !default; 18 | $pink: #e83e8c !default; 19 | $red: #e74c3c !default; 20 | $orange: #fd7e14 !default; 21 | $yellow: #fbb700 !default; 22 | $green: #00bc8c !default; 23 | $teal: #20c997 !default; 24 | $cyan: #3498db !default; 25 | 26 | $primary: $yellow !default; 27 | $secondary: $gray-700 !default; 28 | $success: $green !default; 29 | $info: $cyan !default; 30 | $warning: $yellow !default; 31 | $danger: $red !default; 32 | $dark: $gray-700 !default; 33 | 34 | $min-contrast-ratio: 1.9 !default; 35 | 36 | $nav-pills-link-active-color: $gray-900; 37 | $dropdown-link-active-color: $gray-900; 38 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['../app/templates/**/*.html'], 3 | theme: { 4 | extend: {}, 5 | }, 6 | variants: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | prefix: 'tw-', 11 | corePlugins: { 12 | preflight: false, 13 | }, 14 | daisyui: { 15 | themes: false, // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"] 16 | darkTheme: "dark", // name of one of the included themes for dark mode 17 | base: false, // applies background color and foreground color for root element by default 18 | styled: true, // include daisyUI colors and design decisions for all components 19 | utils: true, // adds responsive and modifier utility classes 20 | prefix: "ds-", // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors) 21 | logs: true, // Shows info about daisyUI version and used config in the console when building your CSS 22 | themeRoot: ":root", // The element that receives theme color CSS variables 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/vendors/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eitchtee/WYGIWYH/99f746b6be2ca8efbc35fb07f8d242cd3a5ae6ab/frontend/vendors/.gitkeep -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django~=5.1 2 | psycopg[binary]==3.2.6 3 | python-webpack-boilerplate==1.0.4 4 | django-crispy-forms==2.3 5 | crispy-bootstrap5==2025.4 6 | django-browser-reload==1.18.0 7 | django-hijack==3.7.1 8 | django-filter==25.1 9 | django-debug-toolbar==4.4.6 10 | django-cachalot~=2.7.0 11 | django-cotton~=1.5.2 12 | django-pwa~=2.0.1 13 | djangorestframework~=3.16.0 14 | drf-spectacular~=0.28.0 15 | django-import-export~=4.3.7 16 | 17 | gunicorn==23.0.0 18 | whitenoise[brotli]==6.9.0 19 | 20 | watchfiles==0.24.0 # https://github.com/samuelcolvin/watchfiles 21 | procrastinate[django]~=2.15.1 22 | 23 | requests~=2.32.3 24 | 25 | pytz 26 | python-dateutil~=2.9.0.post0 27 | simpleeval~=1.0.3 28 | pydantic~=2.11.3 29 | PyYAML~=6.0.2 30 | mistune~=3.1.3 31 | openpyxl~=3.1.5 32 | xlrd~=2.0.1 33 | --------------------------------------------------------------------------------