From c702e6eceae02d16bbb3ccfec18132ff0749a37b Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Tue, 7 Mar 2023 16:59:39 +0000 Subject: [PATCH] Allow configuring aggregator connections --- app/urls.py | 24 ++++- core/forms.py | 25 ++++- ...er_notificationsettings_user_aggregator.py | 35 +++++++ core/models.py | 8 ++ core/templates/base.html | 14 +++ core/templates/partials/aggregator-list.html | 94 +++++++++++++++++++ core/views/aggregators.py | 44 +++++++++ 7 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 core/migrations/0002_alter_notificationsettings_user_aggregator.py create mode 100644 core/templates/partials/aggregator-list.html create mode 100644 core/views/aggregators.py diff --git a/app/urls.py b/app/urls.py index be1c693..abd942b 100644 --- a/app/urls.py +++ b/app/urls.py @@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView from django.urls import include, path from two_factor.urls import urlpatterns as tf_urls -from core.views import base, notifications +from core.views import aggregators, base, notifications # from core.views.stripe_callbacks import Callback @@ -32,9 +32,31 @@ urlpatterns = [ path("", include(tf_urls)), path("accounts/signup/", base.Signup.as_view(), name="signup"), path("accounts/logout/", LogoutView.as_view(), name="logout"), + # Notifications path( "notifications//update/", notifications.NotificationsUpdate.as_view(), name="notifications_update", ), + # Aggregators + path( + "aggs//", + aggregators.AggregatorList.as_view(), + name="aggregators", + ), + path( + "aggs//create/", + aggregators.AggregatorCreate.as_view(), + name="aggregator_create", + ), + path( + "aggs//update//", + aggregators.AggregatorUpdate.as_view(), + name="aggregator_update", + ), + path( + "aggs//delete//", + aggregators.AggregatorDelete.as_view(), + name="aggregator_delete", + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/forms.py b/core/forms.py index 00b7ff3..27f3bba 100644 --- a/core/forms.py +++ b/core/forms.py @@ -4,7 +4,7 @@ from django.core.exceptions import FieldDoesNotExist from django.forms import ModelForm from mixins.restrictions import RestrictedFormMixin -from .models import NotificationSettings, User +from .models import Aggregator, NotificationSettings, User # flake8: noqa: E501 @@ -48,3 +48,26 @@ class NotificationSettingsForm(RestrictedFormMixin, ModelForm): "ntfy_topic": "The topic to send notifications to.", "ntfy_url": "Custom NTFY server. Leave blank to use the default server.", } + + +class AggregatorForm(RestrictedFormMixin, ModelForm): + def __init__(self, *args, **kwargs): + super(AggregatorForm, self).__init__(*args, **kwargs) + self.fields["secret_id"].label = "Secret ID" + + class Meta: + model = Aggregator + fields = ( + "name", + "service", + "secret_id", + "secret_key", + "poll_interval", + ) + help_texts = { + "name": "The name of the aggregator connection.", + "service": "The aggregator service to use.", + "secret_id": "The secret ID for the aggregator service.", + "secret_key": "The secret key for the aggregator service.", + "poll_interval": "The interval in seconds to poll the aggregator service.", + } diff --git a/core/migrations/0002_alter_notificationsettings_user_aggregator.py b/core/migrations/0002_alter_notificationsettings_user_aggregator.py new file mode 100644 index 0000000..f1ea295 --- /dev/null +++ b/core/migrations/0002_alter_notificationsettings_user_aggregator.py @@ -0,0 +1,35 @@ +# Generated by Django 4.1.7 on 2023-03-07 16:54 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='notificationsettings', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='Aggregator', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('service', models.CharField(choices=[('nordigen', 'Nordigen')], max_length=255)), + ('secret_id', models.CharField(blank=True, max_length=1024, null=True)), + ('secret_key', models.CharField(blank=True, max_length=1024, null=True)), + ('access_token', models.CharField(blank=True, max_length=1024, null=True)), + ('poll_interval', models.IntegerField(default=10)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/core/models.py b/core/models.py index fc6e19a..c8549f4 100644 --- a/core/models.py +++ b/core/models.py @@ -31,7 +31,15 @@ class NotificationSettings(models.Model): class Aggregator(models.Model): + """ + A connection to an API aggregator to pull transactions from bank accounts. + """ + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.ForeignKey(User, on_delete=models.CASCADE) name = models.CharField(max_length=255) service = models.CharField(max_length=255, choices=SERVICE_CHOICES) + secret_id = models.CharField(max_length=1024, null=True, blank=True) + secret_key = models.CharField(max_length=1024, null=True, blank=True) + access_token = models.CharField(max_length=1024, null=True, blank=True) + poll_interval = models.IntegerField(default=10) diff --git a/core/templates/base.html b/core/templates/base.html index d3f888b..f9132a5 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -219,6 +219,20 @@ Home {% if user.is_authenticated %} +