generate-module-options-rebase #1

Closed
kiara wants to merge 10 commits from generate-module-options-rebase into generate-module-options
7 changed files with 28 additions and 65 deletions
Showing only changes of commit 19b0684ee7 - Show all commits

View file

@ -13,6 +13,7 @@ let
in in
{ {
options = { options = {
enable = lib.mkEnableOption "Fediversity configuration";
domain = mkOption { domain = mkOption {
type = type =
with types; with types;
@ -22,6 +23,7 @@ in
description = '' description = ''
Apex domain under which the services will be deployed. Apex domain under which the services will be deployed.
''; '';
default = "fediversity.net";
}; };
pixelfed = { pixelfed = {
enable = lib.mkEnableOption "Pixelfed"; enable = lib.mkEnableOption "Pixelfed";

View file

@ -13,7 +13,6 @@ let
exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@ exec ${pkgs.lib.getExe pkgs.python3} ${toString ./src/manage.py} $@
''; '';
jsonschema = pkgs.callPackage ./jsonschema.nix { } { jsonschema = pkgs.callPackage ./jsonschema.nix { } {
includeDefaults = false;
}; };
frontend-options = jsonschema.parseModule ../deployment/options.nix; frontend-options = jsonschema.parseModule ../deployment/options.nix;
schema = with builtins; toFile "schema.json" (toJSON frontend-options); schema = with builtins; toFile "schema.json" (toJSON frontend-options);
@ -42,7 +41,7 @@ in
DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3"; DATABASE_URL = "sqlite:///${toString ./src}/db.sqlite3";
}; };
shellHook = '' 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 ln -sf ${sources.htmx}/dist/htmx.js src/panel/static/htmx.min.js
# in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd. # in production, secrets are passed via CREDENTIALS_DIRECTORY by systemd.

View file

@ -1,13 +1,13 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from panel.configuration import forms from panel.configuration import schema
from pydantic import BaseModel from pydantic import BaseModel
def get_default_config(): def get_default_config():
return forms.Configuration().model_dump_json() return schema.Model().model_dump_json()
class Configuration(models.Model): class Configuration(models.Model):
@ -26,4 +26,4 @@ class Configuration(models.Model):
@property @property
def parsed_value(self) -> BaseModel: def parsed_value(self) -> BaseModel:
return forms.Configuration.model_validate_json(self.value) return schema.Model.model_validate_json(self.value)

View file

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load rest_framework %} {% load rest_framework %}
{% block content %} {% 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 %} {% csrf_token %}
{% render_form serializer %} {% render_form serializer %}

View file

@ -13,7 +13,7 @@ class ConfigurationForm(TestCase):
password=self.password password=self.password
) )
self.config_url = reverse('save') self.config_url = reverse('configuration_form')
def test_configuration_form_submission(self): def test_configuration_form_submission(self):
config = Configuration.objects.create( config = Configuration.objects.create(
@ -27,15 +27,13 @@ class ConfigurationForm(TestCase):
context = response.context[0] context = response.context[0]
# configuration should be disabled by default # configuration should be disabled by default
self.assertFalse(context['view'].get_object().parsed_value.enable) self.assertFalse(context['serializer'].instance.enable)
# ...and be displayed as such
self.assertFalse(context['form'].initial["enable"])
form_data = context['form'].initial.copy() form_data = context['serializer'].instance.dict().copy()
form_data.update( form_data = {
enable=True, "enable": True,
mastodon_enable=True, "mastodon.enable": True,
) }
print(form_data) print(form_data)
response = self.client.post(self.config_url, data=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.enable)
self.assertTrue(config.parsed_value.mastodon.enable) self.assertTrue(config.parsed_value.mastodon.enable)
# this should not have changed # this should not have changed
self.assertFalse(config.parsed_value.peertube.enable) self.assertIsNone(config.parsed_value.peertube)

View file

@ -27,5 +27,4 @@ urlpatterns = [
path("services/", views.ServiceList.as_view(), name='service_list'), path("services/", views.ServiceList.as_view(), name='service_list'),
path("configuration/", views.ConfigurationForm.as_view(), name='configuration_form'), path("configuration/", views.ConfigurationForm.as_view(), name='configuration_form'),
path("deployment/status/", views.DeploymentStatus.as_view(), name='deployment_status'), path("deployment/status/", views.DeploymentStatus.as_view(), name='deployment_status'),
path("save/", views.Save.as_view(), name='save'),
] ]

View file

@ -8,7 +8,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.views.generic import TemplateView, DetailView from django.views.generic import TemplateView, DetailView
from django.views.generic.edit import FormView 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.renderers import TemplateHTMLRenderer
from rest_framework.response import Response from rest_framework.response import Response
@ -48,59 +48,24 @@ class ConfigurationForm(LoginRequiredMixin, APIView):
def get(self, request): def get(self, request):
config = self.get_object() config = self.get_object()
serializer = schema.Model.drf_serializer(instance=config.parsed_value)
return Response({ return Response({
'serializer': schema.Model.drf_serializer(instance=config.parsed_value), 'serializer': serializer,
}) })
# TODO(@fricklerhandwerk): def post(self, request):
# 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()
config = self.get_object() 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)) if not serializer.is_valid():
return initial return Response({'serializer': serializer})
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)
config.value = json.dumps(serializer.validated_data)
config.save()
return redirect(self.success_url)
class DeploymentStatus(ConfigurationForm): class DeploymentStatus(ConfigurationForm):
def form_valid(self, form): def form_valid(self, form):