Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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 Model, 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 ModelAdminForm(ModelForm):
class Meta:
model = Model
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(Model)
class ModelAdmin(ModelAdmin):
form = ModelAdminForm


@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)
139 changes: 139 additions & 0 deletions proenergia/datasets/migrations/0003_model_scenario_scenariofile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Generated by Django 5.2.9 on 2026-01-09 00:59

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="Model",
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.model"
),
),
(
"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 Model(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(Model, 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