105 |
|
113 |
├── VERSION ├── swiftwind ├── bills │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_delete_bill.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── admin.py │ ├── views.py │ └── models.py ├── core │ ├── __init__.py │ ├── models.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── run_scheduler.py │ │ │ └── swiftwind_create_accounts.py │ ├── migrations │ │ └── __init__.py │ ├── templatetags │ │ ├── __init__.py │ │ ├── nav.py │ │ └── swiftwind_utilities.py │ ├── templates │ │ ├── hordak │ │ │ └── base.html │ │ ├── swiftwind │ │ │ ├── base.html │ │ │ └── base_email.html │ │ ├── adminlte │ │ │ └── lib │ │ │ │ ├── _main_header.html │ │ │ │ └── _main_sidebar.html │ │ └── registration │ │ │ └── login.html │ ├── exceptions.py │ ├── admin.py │ ├── static │ │ └── core │ │ │ └── css │ │ │ └── common.css │ └── tests.py ├── costs │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── enact_costs.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0017_recurringcost_archived.py │ │ ├── 0007_auto_20161009_0106.py │ │ ├── 0016_auto_20171207_1258.py │ │ ├── 0010_auto_20161009_1225.py │ │ ├── 0012_auto_20171129_2247.py │ │ ├── 0003_check_one_off_must_be_type_normal.py │ │ ├── 0015_auto_20171206_0145.py │ │ ├── 0004_check_total_billing_cycles_over_zero.py │ │ ├── 0011_check_fixed_amount_requires_type_normal.py │ │ ├── 0014_allow_initial_billing_cycle_and_disabled.py │ │ ├── 0009_check_disabled_xor_billing_cycle.py │ │ ├── 0006_auto_20161009_0019.py │ │ ├── 0013_auto_20171203_1516.py │ │ ├── 0005_check_recurring_costs_have_splits.py │ │ ├── 0008_check_cannot_create_recurred_cost_for_disabled_cost.py │ │ ├── 0002_auto_20161008_1841.py │ │ └── 0001_initial.py │ ├── templates │ │ └── costs │ │ │ ├── delete_recurring.html │ │ │ ├── create_recurring.html │ │ │ ├── one_off.html │ │ │ ├── create_one_off.html │ │ │ └── delete_oneoff.html │ ├── exceptions.py │ ├── admin.py │ ├── tasks.py │ ├── urls.py │ ├── plan.md │ ├── forms.py │ └── views.py ├── accounts │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── tellerio_import.py │ ├── migrations │ │ └── __init__.py │ ├── admin.py │ ├── templates │ │ ├── hordak │ │ │ └── accounts │ │ │ │ ├── account_list.html │ │ │ │ ├── account_transactions.html │ │ │ │ ├── account_create.html │ │ │ │ └── account_update.html │ │ └── accounts │ │ │ ├── reconciliation_required_email.html │ │ │ ├── statement_email.html │ │ │ └── overview.html │ ├── models.py │ ├── urls.py │ ├── tests.py │ ├── tasks.py │ └── views.py ├── dashboard │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── urls.py │ ├── tests.py │ └── views.py ├── housemates │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_auto_20161001_1707.py │ │ └── 0001_initial.py │ ├── admin.py │ ├── apps.py │ ├── urls.py │ ├── models.py │ ├── templates │ │ └── housemates │ │ │ ├── update.html │ │ │ ├── housemates_required_error.html │ │ │ ├── list.html │ │ │ └── create.html │ └── views.py ├── utilities │ ├── __init__.py │ ├── site.py │ ├── emails.py │ ├── testing.py │ └── formsets.py ├── billing_cycle │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── populate_billing_cycles.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0004_auto_20161001_1707.py │ │ ├── 0006_billingcycle_statements_sent.py │ │ ├── 0002_check_non_overlapping.py │ │ ├── 0005_auto_20171203_1516.py │ │ ├── 0001_initial.py │ │ └── 0003_check_adjacent.py │ ├── admin.py │ ├── exceptions.py │ ├── apps.py │ ├── tasks.py │ ├── urls.py │ ├── views.py │ ├── cycles.py │ └── templates │ │ └── billing_cycle │ │ └── list.html ├── system_setup │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── apps.py │ ├── urls.py │ ├── templates │ │ └── setup │ │ │ └── index.html │ ├── middleware.py │ ├── views.py │ ├── forms.py │ └── tests.py ├── transactions │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── views.py │ ├── apps.py │ └── templates │ │ └── hordak │ │ └── transactions │ │ ├── reconcile.html │ │ ├── transaction_create.html │ │ └── currency_trade.html ├── settings │ ├── migrations │ │ ├── __init__.py │ │ ├── 0005_settings_from_email.py │ │ ├── 0002_settings_tellerio_enable.py │ │ ├── 0003_auto_20171206_0145.py │ │ └── 0004_auto_20171207_0014.py │ ├── __init__.py │ ├── admin.py │ ├── tests.py │ ├── templates │ │ └── settings │ │ │ ├── general.html │ │ │ ├── email.html │ │ │ ├── technical.html │ │ │ ├── base.html │ │ │ └── teller.html │ ├── urls.py │ ├── views.py │ ├── apps.py │ ├── models.py │ └── forms.py ├── __init__.py ├── defaults.py └── urls.py ├── requirements.test.txt ├── example_project ├── requirements.txt ├── __init__.py ├── wsgi.py ├── celery.py ├── urls.py └── settings.py ├── .coveragerc ├── requirements.txt ├── deploy └── charts │ └── swiftwind │ ├── Chart.yaml │ ├── charts │ └── postgresql-0.8.4.tgz │ ├── requirements.yaml │ ├── requirements.lock │ ├── .helmignore │ ├── templates │ ├── _helpers.tpl │ ├── service.yaml │ ├── ingress.yaml │ ├── NOTES.txt │ ├── scheduler.yaml │ └── deployment.yaml │ └── values.yaml ├── .gitignore ├── .editorconfig ├── manage.py ├── MANIFEST.in ├── docker-compose.yaml ├── Dockerfile ├── LICENSE.txt ├── setup.py ├── README.rst └── .travis.yml /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 -------------------------------------------------------------------------------- /swiftwind/bills/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/core/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/costs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/dashboard/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/housemates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.test.txt: -------------------------------------------------------------------------------- 1 | freezegun 2 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/bills/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/core/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/costs/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/system_setup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/transactions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example_project/requirements.txt: -------------------------------------------------------------------------------- 1 | redis 2 | -------------------------------------------------------------------------------- /swiftwind/accounts/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/core/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/dashboard/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/housemates/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/settings/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/__init__.py: -------------------------------------------------------------------------------- 1 | from . import defaults 2 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/core/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/costs/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/system_setup/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/transactions/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/accounts/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | 3 | omit = 4 | */admin.py 5 | */migrations/* 6 | -------------------------------------------------------------------------------- /swiftwind/core/templates/hordak/base.html: -------------------------------------------------------------------------------- 1 | {% extends 'swiftwind/base.html' %} 2 | -------------------------------------------------------------------------------- /swiftwind/accounts/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib import admin 3 | -------------------------------------------------------------------------------- /swiftwind/core/exceptions.py: -------------------------------------------------------------------------------- 1 | class CannotCreateMultipleSettingsInstances(Exception): pass 2 | -------------------------------------------------------------------------------- /swiftwind/bills/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /swiftwind/dashboard/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/settings/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'swiftwind.settings.apps.SettingsConfig' 2 | -------------------------------------------------------------------------------- /swiftwind/bills/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib import admin 3 | 4 | 5 | -------------------------------------------------------------------------------- /swiftwind/bills/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /swiftwind/dashboard/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib import admin 3 | 4 | 5 | -------------------------------------------------------------------------------- /swiftwind/settings/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/settings/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /swiftwind/system_setup/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/transactions/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/transactions/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/housemates/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/system_setup/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/transactions/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /swiftwind/transactions/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | git+https://git@github.com/adamcharnock/django-hordak.git@2116230#egg=django-hordak 3 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | class CannotPopulateForDateOutsideExistingCycles(Exception): pass 3 | -------------------------------------------------------------------------------- /example_project/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from .celery import app as celery_app 4 | -------------------------------------------------------------------------------- /swiftwind/accounts/templates/hordak/accounts/account_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'hordak/accounts/account_list.html' %} 2 | 3 | -------------------------------------------------------------------------------- /deploy/charts/swiftwind/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: Swiftwind chart 3 | name: swiftwind 4 | version: 0.1.0 5 | -------------------------------------------------------------------------------- /swiftwind/accounts/templates/hordak/accounts/account_transactions.html: -------------------------------------------------------------------------------- 1 | {% extends 'hordak/accounts/account_transactions.html' %} 2 | -------------------------------------------------------------------------------- /swiftwind/housemates/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HousematesConfig(AppConfig): 5 | name = 'housemates' 6 | -------------------------------------------------------------------------------- /swiftwind/bills/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | from model_utils.models import TimeStampedModel 5 | -------------------------------------------------------------------------------- /swiftwind/system_setup/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SystemSetupConfig(AppConfig): 5 | name = 'system_setup' 6 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BillingCycleConfig(AppConfig): 5 | name = 'billing_cycle' 6 | -------------------------------------------------------------------------------- /swiftwind/transactions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TransactionsConfig(AppConfig): 5 | name = 'transactions' 6 | -------------------------------------------------------------------------------- /deploy/charts/swiftwind/charts/postgresql-0.8.4.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamcharnock/swiftwind/HEAD/deploy/charts/swiftwind/charts/postgresql-0.8.4.tgz -------------------------------------------------------------------------------- /swiftwind/system_setup/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^$', views.SetupView.as_view(), name='index'), 7 | ] 8 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | 3 | from .models import BillingCycle 4 | 5 | 6 | @shared_task 7 | def populate_billing_cycles(): 8 | BillingCycle.populate() 9 | -------------------------------------------------------------------------------- /swiftwind/accounts/models.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | from django.conf import settings 3 | from django.db import models 4 | 5 | # Create your models here. 6 | from model_utils.models import TimeStampedModel 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | .config.ini 4 | *.sqlite3 5 | *.pyc 6 | *.swp 7 | /static/ 8 | /.idea 9 | .bash_history 10 | /fixtures/*.live.json 11 | /build 12 | /dist 13 | /example_project/media 14 | -------------------------------------------------------------------------------- /deploy/charts/swiftwind/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | version: 0.8.4 4 | repository: https://kubernetes-charts.storage.googleapis.com/ 5 | condition: postgresql.enabled 6 | -------------------------------------------------------------------------------- /swiftwind/settings/templates/settings/general.html: -------------------------------------------------------------------------------- 1 | {% extends 'settings/base.html' %} 2 | {% load bootstrap3 %} 3 | 4 | {% block page_name %}General settings{% endblock %} 5 | {% block page_description %}{% endblock %} 6 | -------------------------------------------------------------------------------- /swiftwind/costs/templates/costs/delete_recurring.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |10 | Welcome to your new Swiftwind installation. We need a 11 | little information in order to get everything setup. 12 | We'll also set you up as a housemate and administrator. 13 |
14 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /deploy/charts/swiftwind/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | image: 3 | repository: adamcharnock/swiftwind 4 | tag: latest 5 | pullPolicy: IfNotPresent 6 | service: 7 | name: swiftwind 8 | type: ClusterIP 9 | externalPort: 80 10 | internalPort: 8000 11 | ingress: 12 | # NOTE: Ingress disabled by default: 13 | enabled: false 14 | 15 | # Used to create an Ingress record. 16 | hosts: 17 | - swiftwind.local 18 | annotations: 19 | kubernetes.io/ingress.class: nginx 20 | kubernetes.io/tls-acme: "true" 21 | tls: 22 | # Secrets must be manually created in the namespace. 23 | - secretName: swiftwind-tls 24 | hosts: 25 | - swiftwind.local 26 | 27 | postgresql: 28 | enabled: true 29 | postgresDatabase: swiftwind 30 | -------------------------------------------------------------------------------- /swiftwind/costs/tasks.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from celery import shared_task 4 | from django.db import transaction 5 | 6 | from swiftwind.billing_cycle.models import BillingCycle 7 | from swiftwind.costs.models import RecurringCost 8 | 9 | 10 | @shared_task 11 | @transaction.atomic() 12 | def enact_costs(as_of=None): 13 | if as_of is None: 14 | as_of = date.today() 15 | for billing_cycle in BillingCycle.objects.filter(start_date__lt=as_of, transactions_created=False): 16 | billing_cycle.enact_all_costs() 17 | 18 | 19 | @shared_task 20 | @transaction.atomic() 21 | def disable_costs(): 22 | """Disable any costs that have completed all their billing cycles""" 23 | RecurringCost.objects.all().disable_if_done() 24 | 25 | 26 | -------------------------------------------------------------------------------- /swiftwind/housemates/migrations/0002_auto_20161001_1707.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-01 17:07 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('housemates', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='housemate', 19 | name='user', 20 | field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='housemate', to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/0003_check_one_off_must_be_type_normal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-08 19:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('costs', '0002_auto_20161008_1841'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RunSQL( 16 | """ 17 | ALTER TABLE costs_recurringcost ADD CONSTRAINT one_off_costs_must_be_type_normal 18 | CHECK ("type" = 'normal' OR total_billing_cycles IS NULL) 19 | """, 20 | "ALTER TABLE costs_recurringcost DROP CONSTRAINT one_off_costs_must_be_type_normal" 21 | ) 22 | ] 23 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/0015_auto_20171206_0145.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-12-06 01:45 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('costs', '0014_allow_initial_billing_cycle_and_disabled'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='recurringcost', 18 | name='initial_billing_cycle', 19 | field=models.ForeignKey(default=-1, on_delete=django.db.models.deletion.CASCADE, to='billing_cycle.BillingCycle'), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /swiftwind/billing_cycle/migrations/0005_auto_20171203_1516.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-12-03 15:16 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.ranges 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing_cycle', '0004_auto_20161001_1707'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='billingcycle', 18 | name='date_range', 19 | field=django.contrib.postgres.fields.ranges.DateRangeField(db_index=True, help_text='The start and end date of this billing cycle. May not overlay with any other billing cycles.'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/0004_check_total_billing_cycles_over_zero.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-08 19:34 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('costs', '0003_check_one_off_must_be_type_normal'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RunSQL( 16 | """ 17 | ALTER TABLE costs_recurringcost ADD CONSTRAINT check_total_billing_cycles_over_zero 18 | CHECK (total_billing_cycles IS NULL or total_billing_cycles > 0) 19 | """, 20 | "ALTER TABLE costs_recurringcost DROP CONSTRAINT check_total_billing_cycles_over_zero" 21 | ) 22 | ] 23 | -------------------------------------------------------------------------------- /swiftwind/settings/templates/settings/base.html: -------------------------------------------------------------------------------- 1 | {% extends 'swiftwind/base.html' %} 2 | {% load bootstrap3 %} 3 | 4 | {% block content %} 5 |One-off costs allow you to spread miscellaneous costs between housemates. 16 | These costs can even be spread over several billing cycles if you wish. 17 | These will feature on each housemates' bill.
18 | 19 |Examples: new TV, house trip, getting a plumber
20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /swiftwind/costs/templates/costs/create_one_off.html: -------------------------------------------------------------------------------- 1 | {% extends 'swiftwind/base.html' %} 2 | {% load bootstrap3 %} 3 | 4 | {% block page_name %}Create a One-off Cost{% endblock %} 5 | {% block page_description %}{% endblock %} 6 | 7 | 8 | {% block content %} 9 |10 | A one-off cost will be billed to all housemates. You can choose to spread the cost over several 11 | months if you wish. 12 |
13 | 14 | 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /swiftwind/utilities/testing.py: -------------------------------------------------------------------------------- 1 | from hordak.models.core import Account 2 | from hordak.tests.utils import DataProvider as HordakDataProvider 3 | from swiftwind.housemates.models import Housemate 4 | 5 | 6 | class DataProvider(HordakDataProvider): 7 | 8 | def housemate(self, user=None, account=None, user_kwargs=None, account_kwargs=None): 9 | try: 10 | housemate_income = Account.objects.get(name='Housemate Income') 11 | except Account.DoesNotExist: 12 | housemate_income = None 13 | 14 | return Housemate.objects.create( 15 | user=user or self.user(**(user_kwargs or {})), 16 | account=account or self.account( 17 | type=Account.TYPES.income, 18 | parent=housemate_income, 19 | **(account_kwargs or {}) 20 | ), 21 | ) 22 | -------------------------------------------------------------------------------- /swiftwind/core/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "adminlte/login.html" %} 2 | {% load static bootstrap3 %} 3 | 4 | {% block logo_text %}{% firstof site.name 'Swiftwind' %}{% endblock %} 5 | 6 | {% block form %} 7 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /swiftwind/housemates/templates/housemates/housemates_required_error.html: -------------------------------------------------------------------------------- 1 | {% extends 'swiftwind/base.html' %} 2 | {% load bootstrap3 %} 3 | 4 | {% block page_name %}Housemates required{% endblock %} 5 | 6 | {% block content %} 7 |9 | Swiftwind can load you bank statement data in using the 10 | teller.io service. Once you setup 11 | an account you can enter the details below. 12 |
13 | 14 |15 | The teller token is the token teller providers you with 16 | once you signup. The account ID is the ID of your bank account 17 | as reported by their API. You will need to access their API in order to retrieve your 18 | account ID. 19 |
20 | 21 | 22 | {{ block.super }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/0011_check_fixed_amount_requires_type_normal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-09 12:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('costs', '0010_auto_20161009_1225'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RunSQL( 16 | """ 17 | ALTER TABLE costs_recurringcost ADD CONSTRAINT fixed_amount_requires_type_normal 18 | CHECK ( 19 | ("type" = 'normal' AND fixed_amount IS NOT NULL) 20 | OR 21 | ("type" != 'normal' AND fixed_amount IS NULL) 22 | ) 23 | """, 24 | "ALTER TABLE costs_recurringcost DROP CONSTRAINT fixed_amount_requires_type_normal" 25 | ) 26 | ] 27 | -------------------------------------------------------------------------------- /swiftwind/system_setup/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http.response import HttpResponseRedirect 3 | from django.urls import reverse 4 | 5 | from swiftwind.settings.models import Settings 6 | 7 | 8 | class CheckSetupDoneMiddleware(object): 9 | """Send the user to the setup UI if no settings exist""" 10 | 11 | def __init__(self, get_response): 12 | self.get_response = get_response 13 | 14 | def __call__(self, request): 15 | url = request.path_info 16 | ignore = ( 17 | url.startswith('/setup') or 18 | url.startswith(settings.STATIC_URL) or 19 | url.startswith(settings.MEDIA_URL) 20 | ) 21 | 22 | if not ignore: 23 | if not Settings.objects.exists(): 24 | return HttpResponseRedirect(reverse('setup:index')) 25 | 26 | return self.get_response(request) 27 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/0014_allow_initial_billing_cycle_and_disabled.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-12-06 01:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('costs', '0013_auto_20171203_1516'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RunSQL( 16 | "ALTER TABLE costs_recurringcost DROP CONSTRAINT check_disabled_xor_initial_billing_cycle", 17 | """ 18 | ALTER TABLE costs_recurringcost ADD CONSTRAINT check_disabled_xor_initial_billing_cycle 19 | CHECK ( 20 | (disabled AND initial_billing_cycle_id IS NULL) 21 | OR 22 | (NOT disabled AND initial_billing_cycle_id IS NOT NULL) 23 | ) 24 | """, 25 | ) 26 | ] 27 | -------------------------------------------------------------------------------- /swiftwind/transactions/templates/hordak/transactions/reconcile.html: -------------------------------------------------------------------------------- 1 | {% extends 'hordak/transactions/reconcile.html' %} 2 | {% load bootstrap3 %} 3 | 4 | {% block reconcile_form_content %} 5 | {% bootstrap_field transaction_form.description show_label=False %} 6 | {{ leg_formset.management_form }} 7 || {% bootstrap_field form.amount show_label=False show_help=False form_group_class='form-group money' %} | 11 |{% bootstrap_field form.description show_label=False show_help=False %} | 12 |{% bootstrap_field form.account show_label=False show_help=False %} | 13 |
13 | You need to import your bank data and reconcile transactions before we can send the 14 | household statements for this month. 15 |
16 | 17 || 21 | | 22 | 23 | Reconcile statements now 24 | 25 | | 26 |27 | |
You currently owe {{ balance|inv }}
10 | {% elif balance > 0 %} 11 |The house currently owes you {{ balance }}
12 | {% else %} 13 |Your account balance is {{ balance }}
14 | {% endif %} 15 | {% endwith %} 16 | {% endblock %} 17 | 18 | {% block content %} 19 | 20 |You currently owe {{ balance|inv }}
25 | {% elif balance > 0 %} 26 |The house currently owes you {{ balance }}
27 | {% else %} 28 |Your account balance is {{ balance }}
29 | {% endif %} 30 | {% endwith %} 31 | 32 || Recurring costs | 40 |{{ recurring_total|inv }} | 41 |
| One-off costs | 44 |{{ one_off_total|inv }} | 45 |
| Other transactions | 48 |{{ other_total|inv }} | 49 |
| Total for this period | 52 |{{ total|inv }} | 53 |
| 61 | | 62 | 63 | View full statement 64 | 65 | | 66 |67 | |
{{ payment_information|linebreaksbr }}
74 | {% endif %} 75 | 76 | 77 | {% endblock %} 78 | -------------------------------------------------------------------------------- /swiftwind/settings/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.postgres.fields import ArrayField 2 | from django.db import models 3 | 4 | # Create your models here. 5 | from djmoney.settings import CURRENCY_CHOICES 6 | 7 | from swiftwind.core.exceptions import CannotCreateMultipleSettingsInstances 8 | 9 | 10 | class SettingsManager(models.Manager): 11 | 12 | def get(self): 13 | # TODO: Pull from cache 14 | try: 15 | return super(SettingsManager, self).get() 16 | except Settings.DoesNotExist: 17 | return super(SettingsManager, self).create() 18 | 19 | 20 | class Settings(models.Model): 21 | """Store application-wide settings 22 | 23 | Each field is one setting. Only once instance of Settings can be created. 24 | 25 | The model is intentionally named Settings rather than Setting (as would 26 | be the Django convention), as a single model holds many settings. 27 | """ 28 | default_currency = models.CharField(max_length=3, choices=CURRENCY_CHOICES, default='EUR') 29 | additional_currencies = ArrayField(base_field=models.CharField(choices=CURRENCY_CHOICES, default=[], max_length=3), 30 | choices=CURRENCY_CHOICES, # needed? 31 | default=[], blank=True) 32 | payment_information = models.TextField(default='', blank=True, 33 | help_text='Enter information on how payment should be made, such as the ' 34 | 'bank account details housemates should pay into.') 35 | email_from_address = models.EmailField(default='', blank=True, 36 | help_text='What email address should emails appear to be sent from?') 37 | use_https = models.BooleanField(default=False) 38 | 39 | tellerio_enable = models.BooleanField(default=False, verbose_name='Enable daily teller.io imports') 40 | tellerio_token = models.CharField(max_length=100) 41 | tellerio_account_id = models.CharField(max_length=100) 42 | 43 | from_email = models.EmailField(default='', blank=True) 44 | smtp_host = models.CharField(max_length=100, default='', blank=True) 45 | smtp_port = models.IntegerField(default=None, blank=True, null=True) 46 | smtp_user = models.CharField(max_length=100, default='', blank=True) 47 | smtp_password = models.CharField(max_length=100, default='', blank=True) 48 | smtp_use_tls = models.BooleanField(default=False) 49 | smtp_use_ssl = models.BooleanField(default=False) 50 | smtp_subject_prefix = models.CharField(max_length=100, default='[swiftwind] ', blank=True) 51 | 52 | objects = SettingsManager() 53 | 54 | class Meta: 55 | verbose_name_plural = 'settings' 56 | 57 | def save(self, *args, **kwargs): 58 | if not self.pk and Settings.objects.exists(): 59 | raise CannotCreateMultipleSettingsInstances('Only one Settings instance maybe created') 60 | super(Settings, self).save(*args, **kwargs) 61 | # TODO: Push changes into cache (possibly following a refresh_from_db() call) 62 | 63 | @property 64 | def currencies(self): 65 | return sorted({self.default_currency} | set(self.additional_currencies)) 66 | -------------------------------------------------------------------------------- /swiftwind/costs/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.1 on 2016-10-01 17:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import django.utils.timezone 8 | import django_smalluuid.models 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('hordak', '0003_check_zero_amount_20160907_0921'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='RecurringCost', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('uuid', django_smalluuid.models.SmallUUIDField(default=django_smalluuid.models.UUIDDefault(), editable=False, unique=True)), 25 | ('timestamp', models.DateTimeField(default=django.utils.timezone.now, editable=False)), 26 | ('is_active', models.BooleanField(default=True)), 27 | ('fixed_amount', models.DecimalField(decimal_places=2, max_digits=13)), 28 | ('total_billing_cycles', models.PositiveIntegerField(blank=True, default=None, help_text='Stop billing after this many billing cycles.', null=True)), 29 | ('type', models.CharField(choices=[('normal', 'We will not have spent this yet. We will estimate a fixed amount per billing cycle.'), ('arrears_balance', "We will have already spent this in the previous billing cycle, so bill the account's balance."), ('arrears_balance', 'We will have already spent this in the previous cycle, so bill the total amount spent in the previous cycle.')], default='normal', max_length=20)), 30 | ], 31 | ), 32 | migrations.CreateModel( 33 | name='RecurringCostSplit', 34 | fields=[ 35 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 36 | ('uuid', django_smalluuid.models.SmallUUIDField(default=django_smalluuid.models.UUIDDefault(), editable=False, unique=True)), 37 | ('portion', models.DecimalField(decimal_places=2, default=1, max_digits=13)), 38 | ('from_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hordak.Account')), 39 | ('recurring_cost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='splits', to='costs.RecurringCost')), 40 | ], 41 | ), 42 | migrations.AddField( 43 | model_name='recurringcost', 44 | name='from_accounts', 45 | field=models.ManyToManyField(related_name='outbound_costs', through='costs.RecurringCostSplit', to='hordak.Account'), 46 | ), 47 | migrations.AddField( 48 | model_name='recurringcost', 49 | name='to_account', 50 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inbound_costs', to='hordak.Account'), 51 | ), 52 | migrations.AlterUniqueTogether( 53 | name='recurringcostsplit', 54 | unique_together=set([('recurring_cost', 'from_account')]), 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /swiftwind/accounts/templates/accounts/overview.html: -------------------------------------------------------------------------------- 1 | {% extends 'swiftwind/base.html' %} 2 | {% load bootstrap3 %} 3 | 4 | {% block page_name %}Accounts Overview{% endblock %} 5 | {% block page_description %}{% endblock %} 6 | 7 | {% block content %} 8 | 9 || 20 | | Balance | 21 |Last transaction | 22 |Payment since last bill? | 23 |24 | |
|---|---|---|---|---|
| 32 | 33 | {% firstof account.name 'Unnamed account' %} 34 | 35 | | 36 |{{ account.simple_balance }} | 37 |{% firstof account.latest_transaction_date '-' %} | 38 |39 | {% if account.payment_since_last_bill %} 40 | 41 | {% else %} 42 | 43 | {% endif %} 44 | | 45 |
| 64 | | Balance | 65 |Last transaction | 66 |67 | |
|---|---|---|---|
| 75 | {{ account.name }} 76 | | 77 |{{ account.simple_balance }} | 78 |{% firstof account.latest_transaction_date '-' %} | 79 |
| Start date | 14 |End date | 15 |Transactions created | 16 |Is reconciled | 17 |Statements sent | 18 |19 | | 20 | |
|---|---|---|---|---|---|---|
| {{ billing_cycle.date_range.lower }} | 26 |{{ billing_cycle.date_range.upper }} | 27 |28 | {% if billing_cycle.transactions_created %} 29 | 30 | {% else %} 31 | 32 | {% endif %} 33 | | 34 |35 | {% if billing_cycle.is_reconciled %} 36 | 37 | {% else %} 38 | 39 | {% endif %} 40 | | 41 |42 | {% if billing_cycle.statements_sent %} 43 | 44 | {% else %} 45 | 46 | {% endif %} 47 | | 48 |49 | {% if billing_cycle.transactions_created %} 50 | 63 | 66 | {% elif billing_cycle.can_create_transactions %} 67 | 71 | {% endif %} 72 | | 73 |74 | {% if billing_cycle.can_send_statements %} 75 | 79 | {% endif %} 80 | | 81 |
| 93 | |
94 |
95 |
96 |
97 |
100 |
120 |
|
121 | 122 | |