From 981ba011abb4bdf9230048bda4e9af2a96be40cf Mon Sep 17 00:00:00 2001 From: Valentin Gagarin <valentin.gagarin@tweag.io> Date: Wed, 5 Mar 2025 09:25:48 +0100 Subject: [PATCH] store versioned configurations Test manually: ```shell-session $ manage shell >>> from panel.models import Configuration >>> Configuration().value '{"enable":false,"domain":"fediversity.eu"}' >>> Configuration().save() >>> Configuration.objects.first().parsed_value Configuration(enable=False, domain=<Domain.EU: 'fediversity.eu'>) ``` --- panel/src/panel/configuration/__init__.py | 31 ++++++++++++++++++++++ panel/src/panel/configuration/v1.py | 19 +++++++++++++ panel/src/panel/migrations/0001_initial.py | 26 ++++++++++++++++++ panel/src/panel/migrations/__init__.py | 0 panel/src/panel/models.py | 29 ++++++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 panel/src/panel/configuration/__init__.py create mode 100644 panel/src/panel/configuration/v1.py create mode 100644 panel/src/panel/migrations/0001_initial.py create mode 100644 panel/src/panel/migrations/__init__.py create mode 100644 panel/src/panel/models.py diff --git a/panel/src/panel/configuration/__init__.py b/panel/src/panel/configuration/__init__.py new file mode 100644 index 00000000..9104cf94 --- /dev/null +++ b/panel/src/panel/configuration/__init__.py @@ -0,0 +1,31 @@ +import os +import re +import sys + +from importlib import import_module +from importlib.util import find_spec +from django.utils.functional import classproperty + +from django.apps import apps +from django.db import models + + +class Version(): + + model: models.Model + + @classproperty + def latest(cls): + current_dir = os.path.dirname(os.path.abspath(__file__)) + version_pattern = re.compile(r'v(\d+)\.py$') + versions = [] + for filename in os.listdir(current_dir): + match = version_pattern.match(filename) + if match: + versions.append(int(match.group(1))) + + return max(versions) + + def __init__(self, version: int): + module = import_module(f"{__name__}.v{version}") + self.model = getattr(module, "Configuration") diff --git a/panel/src/panel/configuration/v1.py b/panel/src/panel/configuration/v1.py new file mode 100644 index 00000000..f15fc433 --- /dev/null +++ b/panel/src/panel/configuration/v1.py @@ -0,0 +1,19 @@ +from pydantic import BaseModel, Field +from enum import Enum + +class Configuration(BaseModel): + enable: bool = Field( + default=False, + description="Enable the configuration", + ) + + # XXX: hard-code available apex domains for now, + # they will be prefixed by the user name + class Domain(Enum): + EU = "fediversity.eu" + NET = "fediversity.net" + + domain: Domain = Field( + default=Domain.EU, + description="DNS domain where to expose services" + ) diff --git a/panel/src/panel/migrations/0001_initial.py b/panel/src/panel/migrations/0001_initial.py new file mode 100644 index 00000000..12fda70b --- /dev/null +++ b/panel/src/panel/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.16 on 2025-03-05 08:25 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Configuration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('version', models.PositiveIntegerField(default=1, help_text='Configuration schema version')), + ('value', models.JSONField(help_text='Stored configuration value', null=True)), + ('operator', models.ForeignKey(help_text='Operator who owns the configuration', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='configurations', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/panel/src/panel/migrations/__init__.py b/panel/src/panel/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/panel/src/panel/models.py b/panel/src/panel/models.py new file mode 100644 index 00000000..aba43aa0 --- /dev/null +++ b/panel/src/panel/models.py @@ -0,0 +1,29 @@ +from django.db import models +from django.contrib.auth.models import User + +from panel.configuration import Version + +from pydantic import BaseModel + +class Configuration(models.Model): + operator = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + related_name="configurations", + help_text="Operator who owns the configuration", + ) + + version = models.PositiveIntegerField( + help_text="Configuration schema version", + default=Version.latest, + ) + + value = models.JSONField( + help_text="Stored configuration value", + default=Version(Version.latest).model().model_dump_json, + ) + + @property + def parsed_value(self) -> BaseModel: + return Version(self.version).model.model_validate_json(self.value)