Skip to content

Commit 046e11e

Browse files
Ken LippoldKen Lippold
authored andcommitted
Added updated ETL models and endpoints
1 parent 9eadede commit 046e11e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2839
-62
lines changed

etl/admin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
from django.contrib import admin
2+
from etl.models import EtlSystemPlatform, EtlSystem, EtlConfiguration, DataSource, LinkedDatastream
23

3-
# Register your models here.
4+
5+
admin.site.register(EtlSystemPlatform)
6+
admin.site.register(EtlSystem)
7+
admin.site.register(EtlConfiguration)
8+
admin.site.register(DataSource)
9+
admin.site.register(LinkedDatastream)

etl/management/__init__.py

Whitespace-only changes.

etl/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.core.management.base import BaseCommand
2+
from django.core.management import call_command
3+
4+
5+
class Command(BaseCommand):
6+
help = "Loads test data from fixtures"
7+
8+
def handle(self, *args, **kwargs):
9+
fixtures = [
10+
"tests/fixtures/test_users.yaml",
11+
"tests/fixtures/test_workspaces.yaml",
12+
"tests/fixtures/test_roles.yaml",
13+
"tests/fixtures/test_collaborators.yaml",
14+
"tests/fixtures/test_things.yaml",
15+
"tests/fixtures/test_observed_properties.yaml",
16+
"tests/fixtures/test_processing_levels.yaml",
17+
"tests/fixtures/test_result_qualifiers.yaml",
18+
"tests/fixtures/test_sensors.yaml",
19+
"tests/fixtures/test_units.yaml",
20+
"tests/fixtures/test_datastreams.yaml",
21+
"tests/fixtures/test_etl_system_platforms.yaml",
22+
"tests/fixtures/test_etl_configurations.yaml",
23+
"tests/fixtures/test_etl_systems.yaml",
24+
"tests/fixtures/test_data_sources.yaml",
25+
]
26+
27+
for fixture in fixtures:
28+
self.stdout.write(self.style.NOTICE(f"Loading fixture: {fixture}"))
29+
try:
30+
call_command("loaddata", fixture)
31+
self.stdout.write(self.style.SUCCESS(f"Successfully loaded {fixture}"))
32+
except Exception as e:
33+
self.stderr.write(self.style.ERROR(f"Failed to load {fixture}: {e}"))

etl/migrations/0001_initial.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Generated by Django 5.2b1 on 2025-03-11 22:59
2+
3+
import django.db.models.deletion
4+
import iam.models.utils
5+
import uuid
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
initial = True
12+
13+
dependencies = [
14+
('iam', '0002_load_default_data'),
15+
('sta', '0001_initial'),
16+
]
17+
18+
operations = [
19+
migrations.CreateModel(
20+
name='EtlConfiguration',
21+
fields=[
22+
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
23+
('name', models.CharField(max_length=255)),
24+
('etl_configuration_type', models.CharField(max_length=255)),
25+
('etl_configuration_schema', models.JSONField()),
26+
],
27+
bases=(models.Model, iam.models.utils.PermissionChecker),
28+
),
29+
migrations.CreateModel(
30+
name='EtlSystem',
31+
fields=[
32+
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
33+
('name', models.CharField(max_length=255)),
34+
('workspace', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='etl_systems', to='iam.workspace')),
35+
],
36+
bases=(models.Model, iam.models.utils.PermissionChecker),
37+
),
38+
migrations.CreateModel(
39+
name='DataSource',
40+
fields=[
41+
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
42+
('name', models.CharField(max_length=255)),
43+
('interval', models.PositiveIntegerField(blank=True, null=True)),
44+
('interval_units', models.CharField(blank=True, max_length=255, null=True)),
45+
('crontab', models.CharField(blank=True, max_length=255, null=True)),
46+
('start_time', models.DateTimeField(blank=True, null=True)),
47+
('end_time', models.DateTimeField(blank=True, null=True)),
48+
('paused', models.BooleanField(default=False)),
49+
('last_run_successful', models.BooleanField(blank=True, null=True)),
50+
('last_run_message', models.TextField(blank=True, null=True)),
51+
('last_run', models.DateTimeField(blank=True, null=True)),
52+
('next_run', models.DateTimeField(blank=True, null=True)),
53+
('etl_configuration_settings', models.JSONField(blank=True, null=True)),
54+
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='data_sources', to='iam.workspace')),
55+
('etl_configuration', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='data_sources', to='etl.etlconfiguration')),
56+
('etl_system', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='data_sources', to='etl.etlsystem')),
57+
],
58+
bases=(models.Model, iam.models.utils.PermissionChecker),
59+
),
60+
migrations.CreateModel(
61+
name='EtlSystemPlatform',
62+
fields=[
63+
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
64+
('name', models.CharField(max_length=255)),
65+
('interval_schedule_supported', models.BooleanField(default=False)),
66+
('crontab_schedule_supported', models.BooleanField(default=False)),
67+
('workspace', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='etl_system_platforms', to='iam.workspace')),
68+
],
69+
bases=(models.Model, iam.models.utils.PermissionChecker),
70+
),
71+
migrations.AddField(
72+
model_name='etlsystem',
73+
name='etl_system_platform',
74+
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='etl_systems', to='etl.etlsystemplatform'),
75+
),
76+
migrations.AddField(
77+
model_name='etlconfiguration',
78+
name='etl_system_platform',
79+
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='etl_configurations', to='etl.etlsystemplatform'),
80+
),
81+
migrations.CreateModel(
82+
name='LinkedDatastream',
83+
fields=[
84+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
85+
('etl_configuration_settings', models.JSONField(blank=True, null=True)),
86+
('data_source', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='linked_datastreams', to='etl.datasource')),
87+
('datastream', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, related_name='data_source', to='sta.datastream')),
88+
('etl_configuration', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='linked_datastreams', to='etl.etlconfiguration')),
89+
],
90+
),
91+
]

etl/models/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .data_source import DataSource, LinkedDatastream
2+
from .etl_system_platform import EtlSystemPlatform
3+
from .etl_system import EtlSystem
4+
from .etl_configuration import EtlConfiguration

etl/models/data_source.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import uuid
2+
import typing
3+
from typing import Literal, Optional
4+
from django.db import models
5+
from django.db.models import Q
6+
from iam.models import Workspace
7+
from iam.models.utils import PermissionChecker
8+
from sta.models import Datastream
9+
from .etl_system import EtlSystem
10+
from .etl_configuration import EtlConfiguration
11+
12+
if typing.TYPE_CHECKING:
13+
from django.contrib.auth import get_user_model
14+
from iam.models import Workspace
15+
16+
User = get_user_model()
17+
18+
19+
class DataSourceQuerySet(models.QuerySet):
20+
def visible(self, user: "User"):
21+
if user.account_type == "admin":
22+
return self
23+
else:
24+
return self.filter(
25+
Q(workspace__owner=user) |
26+
Q(workspace__collaborators__user=user,
27+
workspace__collaborators__role__permissions__resource_type__in=["*", "DataSource"],
28+
workspace__collaborators__role__permissions__permission_type__in=["*", "view"])
29+
)
30+
31+
32+
class DataSource(models.Model, PermissionChecker):
33+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
34+
workspace = models.ForeignKey(Workspace, related_name="data_sources", on_delete=models.DO_NOTHING)
35+
name = models.CharField(max_length=255)
36+
etl_system = models.ForeignKey(EtlSystem, related_name="data_sources", on_delete=models.DO_NOTHING)
37+
interval = models.PositiveIntegerField(blank=True, null=True)
38+
interval_units = models.CharField(max_length=255, blank=True, null=True)
39+
crontab = models.CharField(max_length=255, blank=True, null=True)
40+
start_time = models.DateTimeField(blank=True, null=True)
41+
end_time = models.DateTimeField(blank=True, null=True)
42+
paused = models.BooleanField(default=False)
43+
last_run_successful = models.BooleanField(blank=True, null=True)
44+
last_run_message = models.TextField(blank=True, null=True)
45+
last_run = models.DateTimeField(blank=True, null=True)
46+
next_run = models.DateTimeField(blank=True, null=True)
47+
etl_configuration = models.ForeignKey(EtlConfiguration, related_name="data_sources", on_delete=models.DO_NOTHING,
48+
blank=True, null=True)
49+
etl_configuration_settings = models.JSONField(blank=True, null=True)
50+
51+
objects = DataSourceQuerySet.as_manager()
52+
53+
@classmethod
54+
def can_user_create(cls, user: Optional["User"], workspace: "Workspace"):
55+
return cls.check_create_permissions(user=user, workspace=workspace, resource_type="DataSource")
56+
57+
def get_user_permissions(self, user: Optional["User"]) -> list[Literal["edit", "delete", "view"]]:
58+
user_permissions = self.check_object_permissions(user=user, workspace=self.workspace,
59+
resource_type="DataSource")
60+
61+
return user_permissions
62+
63+
def delete(self, *args, **kwargs):
64+
self.delete_contents(filter_arg=self, filter_suffix="")
65+
super().delete(*args, **kwargs)
66+
67+
@staticmethod
68+
def delete_contents(filter_arg: models.Model, filter_suffix: Optional[str]):
69+
data_source_relation_filter = f"data_source__{filter_suffix}" if filter_suffix else "data_source"
70+
LinkedDatastream.objects.filter(**{data_source_relation_filter: filter_arg}).delete()
71+
72+
73+
class LinkedDatastream(models.Model):
74+
data_source = models.ForeignKey(DataSource, related_name="linked_datastreams", on_delete=models.DO_NOTHING)
75+
datastream = models.OneToOneField(Datastream, related_name="data_source", on_delete=models.DO_NOTHING)
76+
etl_configuration = models.ForeignKey(EtlConfiguration, related_name="linked_datastreams",
77+
on_delete=models.DO_NOTHING, blank=True, null=True)
78+
etl_configuration_settings = models.JSONField(blank=True, null=True)

etl/models/etl_configuration.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import uuid
2+
import typing
3+
from typing import Literal, Optional
4+
from django.db import models
5+
from django.db.models import Q
6+
from iam.models import Workspace
7+
from iam.models.utils import PermissionChecker
8+
from .etl_system_platform import EtlSystemPlatform
9+
10+
if typing.TYPE_CHECKING:
11+
from django.contrib.auth import get_user_model
12+
from iam.models import Workspace
13+
14+
User = get_user_model()
15+
16+
17+
class EtlConfigurationQuerySet(models.QuerySet):
18+
def visible(self, user: Optional["User"]):
19+
if user is None:
20+
return self.filter(
21+
Q(etl_system_platform__workspace__isnull=True)
22+
)
23+
elif user.account_type == "admin":
24+
return self
25+
else:
26+
return self.filter(
27+
Q(etl_system_platform__workspace__isnull=True) |
28+
Q(etl_system_platform__workspace__owner=user) |
29+
Q(etl_system_platform__workspace__collaborators__user=user,
30+
etl_system_platform__workspace__collaborators__role__permissions__resource_type__in=[
31+
"*", "EtlConfiguration"
32+
],
33+
etl_system_platform__workspace__collaborators__role__permissions__permission_type__in=["*", "view"])
34+
)
35+
36+
37+
class EtlConfiguration(models.Model, PermissionChecker):
38+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
39+
etl_system_platform = models.ForeignKey(EtlSystemPlatform, related_name="etl_configurations",
40+
on_delete=models.DO_NOTHING)
41+
name = models.CharField(max_length=255)
42+
etl_configuration_type = models.CharField(max_length=255)
43+
etl_configuration_schema = models.JSONField()
44+
45+
objects = EtlConfigurationQuerySet.as_manager()
46+
47+
@classmethod
48+
def can_user_create(cls, user: Optional["User"], workspace: "Workspace"):
49+
return cls.check_create_permissions(user=user, workspace=workspace, resource_type="EtlConfiguration")
50+
51+
def get_user_permissions(self, user: Optional["User"]) -> list[Literal["edit", "delete", "view"]]:
52+
user_permissions = self.check_object_permissions(user=user, workspace=self.etl_system_platform.workspace,
53+
resource_type="EtlConfiguration")
54+
55+
return user_permissions
56+
57+
def delete(self, *args, **kwargs):
58+
self.delete_contents(filter_arg=self, filter_suffix="")
59+
super().delete(*args, **kwargs)
60+
61+
@staticmethod
62+
def delete_contents(filter_arg: models.Model, filter_suffix: Optional[str]):
63+
from etl.models import DataSource, LinkedDatastream
64+
65+
etl_configuration_relation_filter = f"etl_configuration__{filter_suffix}" \
66+
if filter_suffix else "etl_configuration"
67+
68+
DataSource.objects.filter(
69+
**{etl_configuration_relation_filter: filter_arg}
70+
).update(etl_configuration=None)
71+
LinkedDatastream.objects.filter(
72+
**{etl_configuration_relation_filter: filter_arg}
73+
).update(etl_configuration=None)

etl/models/etl_system.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import uuid
2+
import typing
3+
from typing import Literal, Optional
4+
from django.db import models
5+
from django.db.models import Q
6+
from iam.models import Workspace
7+
from iam.models.utils import PermissionChecker
8+
from .etl_system_platform import EtlSystemPlatform
9+
10+
if typing.TYPE_CHECKING:
11+
from django.contrib.auth import get_user_model
12+
from iam.models import Workspace
13+
14+
User = get_user_model()
15+
16+
17+
class EtlSystemQuerySet(models.QuerySet):
18+
def visible(self, user: Optional["User"]):
19+
if user is None:
20+
return self.filter(
21+
Q(workspace__isnull=True)
22+
)
23+
elif user.account_type == "admin":
24+
return self
25+
else:
26+
return self.filter(
27+
Q(workspace__isnull=True) |
28+
Q(workspace__owner=user) |
29+
Q(workspace__collaborators__user=user,
30+
workspace__collaborators__role__permissions__resource_type__in=["*", "EtlSystem"],
31+
workspace__collaborators__role__permissions__permission_type__in=["*", "view"])
32+
)
33+
34+
35+
class EtlSystem(models.Model, PermissionChecker):
36+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
37+
workspace = models.ForeignKey(Workspace, related_name="etl_systems", on_delete=models.DO_NOTHING, blank=True,
38+
null=True)
39+
name = models.CharField(max_length=255)
40+
etl_system_platform = models.ForeignKey(EtlSystemPlatform, related_name="etl_systems", on_delete=models.DO_NOTHING)
41+
42+
objects = EtlSystemQuerySet.as_manager()
43+
44+
@classmethod
45+
def can_user_create(cls, user: Optional["User"], workspace: "Workspace"):
46+
return cls.check_create_permissions(user=user, workspace=workspace, resource_type="EtlSystem")
47+
48+
def get_user_permissions(self, user: Optional["User"]) -> list[Literal["edit", "delete", "view"]]:
49+
user_permissions = self.check_object_permissions(user=user, workspace=self.workspace,
50+
resource_type="EtlSystem")
51+
52+
return user_permissions
53+
54+
def delete(self, *args, **kwargs):
55+
self.delete_contents(filter_arg=self, filter_suffix="")
56+
super().delete(*args, **kwargs)
57+
58+
@staticmethod
59+
def delete_contents(filter_arg: models.Model, filter_suffix: Optional[str]):
60+
from etl.models import DataSource
61+
62+
etl_system_relation_filter = f"etl_system__{filter_suffix}" if filter_suffix else "etl_system"
63+
64+
DataSource.delete_contents(filter_arg=filter_arg, filter_suffix=etl_system_relation_filter)
65+
DataSource.objects.filter(**{etl_system_relation_filter: filter_arg}).delete()

0 commit comments

Comments
 (0)