Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ coverage.xml
.static_storage/
.media/
media/vector/*
media/scenarios/*
local_settings.py

# Flask stuff:
Expand Down
64 changes: 63 additions & 1 deletion proenergia/datasets/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from django.contrib import admin, messages
from django.forms import ModelForm
from unfold.admin import ModelAdmin

from .models import VectorDataset, VectorFile
from .models import DataModel, Scenario, ScenarioFile, VectorDataset, VectorFile


class PermissionBasedModelAdmin(ModelAdmin):
Expand Down Expand Up @@ -107,3 +108,64 @@ def formfield_for_foreignkey(self, db_field, request, **kwargs):
kwargs["initial"] = request.user.id

return super().formfield_for_foreignkey(db_field, request, **kwargs)


class DataModelAdminForm(ModelForm):
class Meta:
model = DataModel
fields = ["name", "filter_fields", "popup_fields"]

def clean(self):
cleaned_data = super().clean()
filter_fields = cleaned_data.get("filter_fields")
popup_fields = cleaned_data.get("popup_fields")

if filter_fields:
if type(filter_fields) is not list:
self.add_error("filter_fields", "Content should be a list.")
else:
for i in enumerate(filter_fields):
keys = i[1].keys()
if (
"label" not in keys
or "description" not in keys
or "column" not in keys
):
self.add_error("filter_fields", "Missing a required key.")

if popup_fields:
if type(popup_fields) is not list:
self.add_error("popup_fields", "Content should be a list")
else:
for i in enumerate(popup_fields):
keys = i[1].keys()
if (
"label" not in keys
or "description" not in keys
or "column" not in keys
):
self.add_error("popup_fields", "Missing a required key.")


@admin.register(DataModel)
class DataModelAdmin(ModelAdmin):
form = DataModelAdminForm


@admin.register(Scenario)
class ScenarioAdmin(ModelAdmin):
list_display = ["id", "name", "model"]
fields = ["name", "model", "vector_dataset"]


@admin.register(ScenarioFile)
class ScenarioFileAdmin(ModelAdmin):
list_display = ["id", "scenario", "created", "status"]
fields = ["scenario", "file"]

def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user

obj.last_updated_by = request.user
super().save_model(request, obj, form, change)
140 changes: 140 additions & 0 deletions proenergia/datasets/migrations/0003_datamodel_scenario_scenariofile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Generated by Django 5.2.9 on 2026-01-09 12:47

import django.core.validators
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("datasets", "0002_vectorfile"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="DataModel",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=155, unique=True)),
(
"filter_fields",
models.JSONField(
default=[],
help_text="A list containing JSON objects following this structure: {'label': 'Field label', 'description': 'Field description', 'column': 'File/Database column name'}",
),
),
(
"popup_fields",
models.JSONField(
default=[],
help_text="A list containing JSON objects following this structure: {'label': 'Field label', 'description': 'Field description', 'column': 'File/Database column name'}",
),
),
],
options={
"ordering": ["id"],
},
),
migrations.CreateModel(
name="Scenario",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=155, unique=True)),
(
"model",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="datasets.datamodel",
),
),
(
"vector_dataset",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="datasets.vectordataset",
),
),
],
options={
"ordering": ["id"],
},
),
migrations.CreateModel(
name="ScenarioFile",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
(
"status",
models.CharField(
choices=[
("created", "Created"),
("processing", "Processing"),
("ready", "Ready"),
("error", "Error"),
],
default="created",
max_length=155,
),
),
(
"file",
models.FileField(
unique=True,
upload_to="scenarios/",
validators=[
django.core.validators.FileExtensionValidator(
allowed_extensions=["csv"]
)
],
),
),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="scenario_files",
to=settings.AUTH_USER_MODEL,
),
),
(
"scenario",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="files",
to="datasets.scenario",
),
),
],
options={
"ordering": ["id"],
},
),
]
50 changes: 50 additions & 0 deletions proenergia/datasets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,53 @@ def delete_vector_file(sender, instance, **kwargs):
# Using default_storage for better compatibility with different storage backends
if default_storage.exists(instance.file.name):
default_storage.delete(instance.file.name)


class DataModel(models.Model):
name = models.CharField(max_length=155, unique=True)
filter_fields = models.JSONField(
default=list(),
help_text="A list containing JSON objects following this structure: {'label': 'Field label', 'description': 'Field description', 'column': 'File/Database column name'}",
)
popup_fields = models.JSONField(
default=list(),
help_text="A list containing JSON objects following this structure: {'label': 'Field label', 'description': 'Field description', 'column': 'File/Database column name'}",
)

def __str__(self):
return f"{self.name}"

class Meta:
ordering = ["id"]


class Scenario(models.Model):
name = models.CharField(max_length=155, unique=True)
model = models.ForeignKey(DataModel, on_delete=models.PROTECT)
vector_dataset = models.ForeignKey(VectorDataset, on_delete=models.PROTECT)

def __str__(self):
return f"{self.name}"

class Meta:
ordering = ["id"]


class ScenarioFile(models.Model):
scenario = models.ForeignKey(Scenario, models.PROTECT, related_name="files")
created = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, models.PROTECT, related_name="scenario_files"
)
status = models.CharField(max_length=155, choices=STATUS, default="created")
file = models.FileField(
upload_to="scenarios/",
unique=True,
validators=[FileExtensionValidator(allowed_extensions=["csv"])],
)

def __str__(self):
return f"{self.scenario} ({self.created})"

class Meta:
ordering = ["id"]
28 changes: 27 additions & 1 deletion proenergia/datasets/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers

from .models import VectorDataset, VectorFile
from .models import Scenario, ScenarioFile, VectorDataset, VectorFile


class VectorDatasetSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -31,3 +31,29 @@ def get_raw_file(self, obj):
return vector_file.file.name
except VectorFile.DoesNotExist:
return None


class ScenarioSerializer(serializers.ModelSerializer):
model_file = serializers.SerializerMethodField()
model = serializers.ReadOnlyField(source="model.name")
filter_fields = serializers.ReadOnlyField(source="model.filter_fields")
popup_fields = serializers.ReadOnlyField(source="model.popup_fields")

class Meta:
model = Scenario
fields = [
"id",
"name",
"model",
"model_file",
"filter_fields",
"popup_fields",
]

def get_model_file(self, obj):
try:
# update status to ready when we have the file conversion working
model_file = obj.files.latest("created")
return model_file.file.name
except ScenarioFile.DoesNotExist:
return None
Loading