diff --git a/deployment/options.nix b/deployment/options.nix index 1de9265f..df04b994 100644 --- a/deployment/options.nix +++ b/deployment/options.nix @@ -13,6 +13,7 @@ let in { options = { + enable = lib.mkEnableOption "Fediversity configuration"; domain = mkOption { type = with types; @@ -22,6 +23,7 @@ in description = '' Apex domain under which the services will be deployed. ''; + default = "fediversity.net"; }; pixelfed = { enable = lib.mkEnableOption "Pixelfed"; diff --git a/panel/default.nix b/panel/default.nix index 724404ef..41655c61 100644 --- a/panel/default.nix +++ b/panel/default.nix @@ -13,7 +13,6 @@ let exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@ ''; jsonschema = pkgs.callPackage ./jsonschema.nix { } { - includeDefaults = false; }; frontend-options = jsonschema.parseModule ../deployment/options.nix; schema = with builtins; toFile "schema.json" (toJSON frontend-options); @@ -42,7 +41,7 @@ in DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3"; }; shellHook = '' - cp -f ${pydantic} ${builtins.toString ./src/panel/configuration/schema.py} + install -m 644 ${pydantic} ${builtins.toString ./src/panel/configuration/schema.py} ln -sf ${sources.htmx}/dist/htmx.js src/panel/static/htmx.min.js # in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd. diff --git a/panel/src/panel/models.py b/panel/src/panel/models.py index 67426cb1..4652e183 100644 --- a/panel/src/panel/models.py +++ b/panel/src/panel/models.py @@ -1,13 +1,13 @@ from django.db import models from django.contrib.auth.models import User -from panel.configuration import forms +from panel.configuration import schema from pydantic import BaseModel def get_default_config(): - return forms.Configuration().model_dump_json() + return schema.Model().model_dump_json() class Configuration(models.Model): @@ -26,4 +26,4 @@ class Configuration(models.Model): @property def parsed_value(self) -> BaseModel: - return forms.Configuration.model_validate_json(self.value) + return schema.Model.model_validate_json(self.value) diff --git a/panel/src/panel/templates/configuration_form.html b/panel/src/panel/templates/configuration_form.html index 8df2acd4..ac4e68c7 100644 --- a/panel/src/panel/templates/configuration_form.html +++ b/panel/src/panel/templates/configuration_form.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% load rest_framework %} {% block content %} -<form method="post" enctype="multipart/form-data" action="{% url 'save' %}"> +<form method="post" enctype="multipart/form-data" action="{% url 'configuration_form' %}"> {% csrf_token %} {% render_form serializer %} diff --git a/panel/src/panel/tests/test_configuration_form.py b/panel/src/panel/tests/test_configuration_form.py index 805b0e34..47baec09 100644 --- a/panel/src/panel/tests/test_configuration_form.py +++ b/panel/src/panel/tests/test_configuration_form.py @@ -13,7 +13,7 @@ class ConfigurationForm(TestCase): password=self.password ) - self.config_url = reverse('save') + self.config_url = reverse('configuration_form') def test_configuration_form_submission(self): config = Configuration.objects.create( @@ -27,15 +27,13 @@ class ConfigurationForm(TestCase): context = response.context[0] # configuration should be disabled by default - self.assertFalse(context['view'].get_object().parsed_value.enable) - # ...and be displayed as such - self.assertFalse(context['form'].initial["enable"]) + self.assertFalse(context['serializer'].instance.enable) - form_data = context['form'].initial.copy() - form_data.update( - enable=True, - mastodon_enable=True, - ) + form_data = context['serializer'].instance.dict().copy() + form_data = { + "enable": True, + "mastodon.enable": True, + } print(form_data) response = self.client.post(self.config_url, data=form_data) @@ -46,4 +44,4 @@ class ConfigurationForm(TestCase): self.assertTrue(config.parsed_value.enable) self.assertTrue(config.parsed_value.mastodon.enable) # this should not have changed - self.assertFalse(config.parsed_value.peertube.enable) + self.assertIsNone(config.parsed_value.peertube) diff --git a/panel/src/panel/urls.py b/panel/src/panel/urls.py index 011a2843..7865bad9 100644 --- a/panel/src/panel/urls.py +++ b/panel/src/panel/urls.py @@ -27,5 +27,4 @@ urlpatterns = [ path("services/", views.ServiceList.as_view(), name='service_list'), path("configuration/", views.ConfigurationForm.as_view(), name='configuration_form'), path("deployment/status/", views.DeploymentStatus.as_view(), name='deployment_status'), - path("save/", views.Save.as_view(), name='save'), ] diff --git a/panel/src/panel/views.py b/panel/src/panel/views.py index a72c437c..9c7e9de1 100644 --- a/panel/src/panel/views.py +++ b/panel/src/panel/views.py @@ -8,7 +8,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.views.generic import TemplateView, DetailView from django.views.generic.edit import FormView -from django.shortcuts import render +from django.shortcuts import render, redirect from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response @@ -48,59 +48,24 @@ class ConfigurationForm(LoginRequiredMixin, APIView): def get(self, request): config = self.get_object() + serializer = schema.Model.drf_serializer(instance=config.parsed_value) return Response({ - 'serializer': schema.Model.drf_serializer(instance=config.parsed_value), + 'serializer': serializer, }) - # TODO(@fricklerhandwerk): - # this should probably live somewhere else - def convert_enums_to_names(self, data_dict): - """ - Recursively convert all enum values in a dictionary to their string names. - This handles nested dictionaries and lists as well. - - Needed for converting a Pydantic `BaseModel` instance to a `Form` input. - """ - if isinstance(data_dict, dict): - result = {} - for key, value in data_dict.items(): - if isinstance(value, Enum): - # Convert Enum to its name - result[key] = value.name - elif isinstance(value, (dict, list)): - # Recursively process nested structures - result[key] = self.convert_enums_to_names(value) - else: - # Keep other values as is - result[key] = value - return result - elif isinstance(data_dict, list): - # Process each item in the list - return [self.convert_enums_to_names(item) for item in data_dict] - elif isinstance(data_dict, Enum): - # Convert single Enum value - return data_dict.name - else: - # Return non-dict, non-list, non-Enum values as is - return data_dict - - def get_initial(self): - initial = super().get_initial() + def post(self, request): config = self.get_object() - config_dict = config.parsed_value.model_dump() + serializer = schema.Model.drf_serializer( + instance=config.parsed_value, + data=request.data + ) - initial.update(self.convert_enums_to_names(config_dict)) - return initial - - -class Save(ConfigurationForm): - def form_valid(self, form): - obj = self.get_object() - obj.value = form.to_python().model_dump_json() - obj.save() - - return super().form_valid(form) + if not serializer.is_valid(): + return Response({'serializer': serializer}) + config.value = json.dumps(serializer.validated_data) + config.save() + return redirect(self.success_url) class DeploymentStatus(ConfigurationForm): def form_valid(self, form):