Move field validation to model, for API compatibility

This commit is contained in:
Tobias Kunze 2025-03-21 16:52:02 +01:00
parent 8a8745f1fd
commit 0eb01457f4
2 changed files with 37 additions and 21 deletions

View file

@ -1,5 +1,4 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from servala.core.models import ControlPlane
@ -40,35 +39,31 @@ class ControlPlaneAdminForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
# Get the three credential fields
ca_data = cleaned_data.get("certificate_authority_data")
server = cleaned_data.get("server")
token = cleaned_data.get("token")
# Check if any of the fields are filled
has_ca = bool(ca_data)
has_server = bool(server)
has_token = bool(token)
# If some but not all fields are filled, raise validation error
if (has_ca or has_server or has_token) and not (
has_ca and has_server and has_token
):
raise ValidationError(
_(
"All credential fields (certificate authority data, server, and token) must be provided together or left empty."
)
)
# If all fields are filled, create the api_credentials JSON
if has_ca and has_server and has_token:
if ca_data and server and token:
cleaned_data["api_credentials"] = {
"certificate-authority-data": ca_data,
"server": server,
"token": token,
}
else:
cleaned_data["api_credentials"] = {}
if not (ca_data or server or token):
cleaned_data["api_credentials"] = {}
else:
# Some fields are filled but not all - validation will fail at model level,
# as model field validators are also called by the API.
# We still create the JSON with whatever we have so the model validator can run.
credentials = {}
if ca_data:
credentials["certificate-authority-data"] = ca_data
if server:
credentials["server"] = server
if token:
credentials["token"] = token
cleaned_data["api_credentials"] = credentials
return cleaned_data

View file

@ -1,3 +1,4 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
from encrypted_fields.fields import EncryptedJSONField
@ -63,14 +64,34 @@ class Service(models.Model):
return self.name
def validate_api_credentials(value):
"""
Validates that api_credentials either contains all required fields or is empty.
"""
# If empty dict, that's valid
if not value:
return
# Check for required fields
required_fields = ("certificate-authority-data", "server", "token")
missing_fields = required_fields - set(value)
if missing_fields:
raise ValidationError(
_("Missing required fields in API credentials: %(fields)s"),
params={"fields": ", ".join(missing_fields)},
)
class ControlPlane(models.Model):
name = models.CharField(max_length=100, verbose_name=_("Name"))
description = models.TextField(blank=True, verbose_name=_("Description"))
k8s_api_endpoint = models.URLField(verbose_name=_("Kubernetes API endpoint"))
# TODO: schema
# Either contains the fields "certificate_authority_data", "server" and "token", or is empty
api_credentials = EncryptedJSONField(
verbose_name=_("API credentials"),
help_text="Required fields: certificate-authority-data, server (URL), token",
validators=[validate_api_credentials],
)
cloud_provider = models.ForeignKey(