forked from Fediversity/Fediversity
WIP: add tests
This commit is contained in:
parent
066671d860
commit
19b0684ee7
7 changed files with 28 additions and 65 deletions
|
@ -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";
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Add table
Reference in a new issue