Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: beta import function #58

Merged
merged 47 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c171e04
feat: add import app boilerplate
eitchtee Jan 16, 2025
fbb26b8
feat: rename app, some work on schema
eitchtee Jan 17, 2025
86dac63
feat(import): improve schema definition
eitchtee Jan 19, 2025
a94e0b4
docs(requirements): add django_ace
eitchtee Jan 19, 2025
238f205
docker: add temp volume
eitchtee Jan 19, 2025
3ccb0e1
feat(transactions): soft delete
eitchtee Jan 19, 2025
f96d8d2
feat(transactions): soft delete
eitchtee Jan 19, 2025
2d88647
feat(import): disable cache when running
eitchtee Jan 19, 2025
ba0c547
feat(import): add migrations
eitchtee Jan 19, 2025
3ef6b0a
feat(settings): add KEEP_DELETED_TRANSACTIONS_FOR variable
eitchtee Jan 19, 2025
ae91c51
feat(transactions:tasks): add old deleted transactions cleanup task
eitchtee Jan 19, 2025
e73e1df
feat(import:v1:schema): add option for triggering rules
eitchtee Jan 19, 2025
8db13b0
feat(import): some layouts
eitchtee Jan 20, 2025
4cc32e3
feat(import): test yaml_config before saving
eitchtee Jan 20, 2025
b9810ce
feat(import): some layouts
eitchtee Jan 20, 2025
0fccdbe
feat(import): some views and urls
eitchtee Jan 20, 2025
02adfd8
feat(transactions): add internal_id field to transactions
eitchtee Jan 21, 2025
32b5864
feat(transactions): make deleted_at readonly on admin
eitchtee Jan 21, 2025
d96787c
feat(import): more UI and endpoints
eitchtee Jan 22, 2025
8992cd9
feat: add import app boilerplate
eitchtee Jan 16, 2025
493bf26
feat: rename app, some work on schema
eitchtee Jan 17, 2025
50efc51
feat(import): improve schema definition
eitchtee Jan 19, 2025
87345cf
docs(requirements): add django_ace
eitchtee Jan 19, 2025
3440d44
docker: add temp volume
eitchtee Jan 19, 2025
a52f682
feat(transactions): soft delete
eitchtee Jan 19, 2025
8a127a9
feat(transactions): soft delete
eitchtee Jan 19, 2025
2ff3352
feat(import): disable cache when running
eitchtee Jan 19, 2025
18d8e8e
feat(import): add migrations
eitchtee Jan 19, 2025
f2cc070
feat(settings): add KEEP_DELETED_TRANSACTIONS_FOR variable
eitchtee Jan 19, 2025
34e6914
feat(transactions:tasks): add old deleted transactions cleanup task
eitchtee Jan 19, 2025
76df16e
feat(import:v1:schema): add option for triggering rules
eitchtee Jan 19, 2025
61d5aba
feat(import): some layouts
eitchtee Jan 20, 2025
0f14fd0
feat(import): test yaml_config before saving
eitchtee Jan 20, 2025
07fcbe1
feat(import): some layouts
eitchtee Jan 20, 2025
6f096fd
feat(import): some views and urls
eitchtee Jan 20, 2025
00b8727
feat(transactions): add internal_id field to transactions
eitchtee Jan 21, 2025
a415e28
feat(transactions): make deleted_at readonly on admin
eitchtee Jan 21, 2025
ece44f2
feat(import): more UI and endpoints
eitchtee Jan 22, 2025
16fbead
Merge remote-tracking branch 'origin/41-import-export-function' into …
eitchtee Jan 22, 2025
cabd03e
feat: presets
eitchtee Jan 23, 2025
aaee602
refactor: remove django-ace for now
eitchtee Jan 23, 2025
a805880
git: keep import_presets folder
eitchtee Jan 23, 2025
d7de6c1
refactor: remove django-ace for now
eitchtee Jan 23, 2025
962a8ef
feat(navbar): add import to management menu
eitchtee Jan 23, 2025
4ef4609
fix(navbar): wrong active link for navbar import item
eitchtee Jan 23, 2025
e3d3a7c
feat: add new envs
eitchtee Jan 23, 2025
a852214
Merge remote-tracking branch 'origin/main' into 41-import-export-func…
eitchtee Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ SQL_PORT=5432

# Gunicorn
WEB_CONCURRENCY=4

# App Configs
# Enable this if you want to keep deleted transactions in the database
ENABLE_SOFT_DELETE=false
# If ENABLE_SOFT_DELETE is true, transactions deleted for more than KEEP_DELETED_TRANSACTIONS_FOR days will be truly deleted. Set to 0 to keep all.
KEEP_DELETED_TRANSACTIONS_FOR=365
4 changes: 4 additions & 0 deletions app/WYGIWYH/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"apps.accounts.apps.AccountsConfig",
"apps.common.apps.CommonConfig",
"apps.net_worth.apps.NetWorthConfig",
"apps.import_app.apps.ImportConfig",
"apps.api.apps.ApiConfig",
"cachalot",
"rest_framework",
Expand Down Expand Up @@ -376,3 +377,6 @@
"type": "image/png",
},
]

ENABLE_SOFT_DELETE = os.getenv("ENABLE_SOFT_DELETION", "false").lower() == "true"
KEEP_DELETED_TRANSACTIONS_FOR = int(os.getenv("KEEP_DELETED_ENTRIES_FOR", "365"))
1 change: 1 addition & 0 deletions app/WYGIWYH/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@
path("", include("apps.calendar_view.urls")),
path("", include("apps.dca.urls")),
path("", include("apps.mini_tools.urls")),
path("", include("apps.import_app.urls")),
]
11 changes: 11 additions & 0 deletions app/apps/common/templatetags/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import json

from django import template


register = template.Library()


@register.filter("json")
def convert_to_json(value):
return json.dumps(value)
Empty file added app/apps/import_app/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions app/apps/import_app/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin
from apps.import_app import models

# Register your models here.
admin.site.register(models.ImportRun)
admin.site.register(models.ImportProfile)
6 changes: 6 additions & 0 deletions app/apps/import_app/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class ImportConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.import_app"
64 changes: 64 additions & 0 deletions app/apps/import_app/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
Layout,
)
from django import forms
from django.utils.translation import gettext_lazy as _

from apps.import_app.models import ImportProfile
from apps.common.widgets.crispy.submit import NoClassSubmit


class ImportProfileForm(forms.ModelForm):
class Meta:
model = ImportProfile
fields = [
"name",
"version",
"yaml_config",
]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout("name", "version", "yaml_config")

if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
),
)


class ImportRunFileUploadForm(forms.Form):
file = forms.FileField(label=_("Select a file"))

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(
"file",
FormActions(
NoClassSubmit(
"submit", _("Import"), css_class="btn btn-outline-primary w-100"
),
),
)
51 changes: 51 additions & 0 deletions app/apps/import_app/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Generated by Django 5.1.5 on 2025-01-19 00:44

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('currencies', '0006_currency_exchange_currency'),
('transactions', '0028_transaction_internal_note'),
]

operations = [
migrations.CreateModel(
name='ImportProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('yaml_config', models.TextField(help_text='YAML configuration')),
('version', models.IntegerField(choices=[(1, 'Version 1')], default=1, verbose_name='Version')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='ImportRun',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('QUEUED', 'Queued'), ('PROCESSING', 'Processing'), ('FAILED', 'Failed'), ('FINISHED', 'Finished')], default='QUEUED', max_length=10, verbose_name='Status')),
('file_name', models.CharField(help_text='File name', max_length=10000)),
('logs', models.TextField(blank=True)),
('processed_rows', models.IntegerField(default=0)),
('total_rows', models.IntegerField(default=0)),
('successful_rows', models.IntegerField(default=0)),
('skipped_rows', models.IntegerField(default=0)),
('failed_rows', models.IntegerField(default=0)),
('started_at', models.DateTimeField(null=True)),
('finished_at', models.DateTimeField(null=True)),
('categories', models.ManyToManyField(related_name='import_runs', to='transactions.transactioncategory')),
('currencies', models.ManyToManyField(related_name='import_runs', to='currencies.currency')),
('entities', models.ManyToManyField(related_name='import_runs', to='transactions.transactionentity')),
('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='import_app.importprofile')),
('tags', models.ManyToManyField(related_name='import_runs', to='transactions.transactiontag')),
('transactions', models.ManyToManyField(related_name='import_runs', to='transactions.transaction')),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.1.5 on 2025-01-23 03:03

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('import_app', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='importprofile',
name='name',
field=models.CharField(max_length=100, unique=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='importprofile',
name='yaml_config',
field=models.TextField(verbose_name='YAML Configuration'),
),
]
Empty file.
87 changes: 87 additions & 0 deletions app/apps/import_app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import yaml

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from apps.import_app.schemas import version_1


class ImportProfile(models.Model):
class Versions(models.IntegerChoices):
VERSION_1 = 1, _("Version") + " 1"

name = models.CharField(max_length=100, verbose_name=_("Name"), unique=True)
yaml_config = models.TextField(verbose_name=_("YAML Configuration"))
version = models.IntegerField(
choices=Versions,
default=Versions.VERSION_1,
verbose_name=_("Version"),
)

def __str__(self):
return self.name

class Meta:
ordering = ["name"]

def clean(self):
if self.version and self.version == self.Versions.VERSION_1:
try:
yaml_data = yaml.safe_load(self.yaml_config)
version_1.ImportProfileSchema(**yaml_data)
except Exception as e:
raise ValidationError({"yaml_config": _("Invalid YAML Configuration")})


class ImportRun(models.Model):
class Status(models.TextChoices):
QUEUED = "QUEUED", _("Queued")
PROCESSING = "PROCESSING", _("Processing")
FAILED = "FAILED", _("Failed")
FINISHED = "FINISHED", _("Finished")

status = models.CharField(
max_length=10,
choices=Status,
default=Status.QUEUED,
verbose_name=_("Status"),
)
profile = models.ForeignKey(
ImportProfile,
on_delete=models.CASCADE,
)
file_name = models.CharField(
max_length=10000,
help_text=_("File name"),
)
transactions = models.ManyToManyField(
"transactions.Transaction", related_name="import_runs"
)
tags = models.ManyToManyField(
"transactions.TransactionTag", related_name="import_runs"
)
categories = models.ManyToManyField(
"transactions.TransactionCategory", related_name="import_runs"
)
entities = models.ManyToManyField(
"transactions.TransactionEntity", related_name="import_runs"
)
currencies = models.ManyToManyField(
"currencies.Currency", related_name="import_runs"
)

logs = models.TextField(blank=True)
processed_rows = models.IntegerField(default=0)
total_rows = models.IntegerField(default=0)
successful_rows = models.IntegerField(default=0)
skipped_rows = models.IntegerField(default=0)
failed_rows = models.IntegerField(default=0)
started_at = models.DateTimeField(null=True)
finished_at = models.DateTimeField(null=True)

@property
def progress(self):
if self.total_rows == 0:
return 0
return (self.processed_rows / self.total_rows) * 100
Empty file added app/apps/import_app/schemas.py
Empty file.
1 change: 1 addition & 0 deletions app/apps/import_app/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import apps.import_app.schemas.v1 as version_1
Loading