Render ControlPlane.api_credentials in admin as separate fields

This commit is contained in:
Tobias Kunze 2025-03-21 16:02:42 +01:00
parent 81396297f9
commit 8a8745f1fd
3 changed files with 103 additions and 1 deletions

View file

@ -1,6 +1,7 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from servala.core.forms import ControlPlaneAdminForm
from servala.core.models import (
BillingEntity,
CloudProvider,
@ -110,11 +111,32 @@ class CloudProviderAdmin(admin.ModelAdmin):
@admin.register(ControlPlane)
class ControlPlaneAdmin(admin.ModelAdmin):
form = ControlPlaneAdminForm
list_display = ("name", "cloud_provider", "k8s_api_endpoint")
list_filter = ("cloud_provider",)
search_fields = ("name", "description", "k8s_api_endpoint")
autocomplete_fields = ("cloud_provider",)
fieldsets = (
(
None,
{"fields": ("name", "description", "k8s_api_endpoint", "cloud_provider")},
),
(
_("API Credentials"),
{
"fields": ("certificate_authority_data", "server", "token"),
"description": _(
"All three credential fields must be provided together or left empty."
),
},
),
)
def get_exclude(self, request, obj=None):
# Exclude the original api_credentials field as we're using our custom fields
return ["api_credentials"]
@admin.register(Plan)
class PlanAdmin(admin.ModelAdmin):

77
src/servala/core/forms.py Normal file
View file

@ -0,0 +1,77 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from servala.core.models import ControlPlane
class ControlPlaneAdminForm(forms.ModelForm):
certificate_authority_data = forms.CharField(
widget=forms.Textarea,
required=False,
help_text=_("Certificate authority data for the Kubernetes API"),
)
server = forms.URLField(
required=False,
help_text=_("Server URL for the Kubernetes API"),
)
token = forms.CharField(
widget=forms.Textarea,
required=False,
help_text=_("Authentication token for the Kubernetes API"),
)
class Meta:
model = ControlPlane
fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# If we have existing api_credentials, populate the individual fields
if self.instance.pk and self.instance.api_credentials:
creds = self.instance.api_credentials
self.fields["certificate_authority_data"].initial = creds.get(
"certificate-authority-data", ""
)
self.fields["server"].initial = creds.get("server", "")
self.fields["token"].initial = creds.get("token", "")
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:
cleaned_data["api_credentials"] = {
"certificate-authority-data": ca_data,
"server": server,
"token": token,
}
else:
cleaned_data["api_credentials"] = {}
return cleaned_data
def save(self, *args, **kwargs):
self.instance.api_credentials = self.cleaned_data["api_credentials"]
return super().save(*args, **kwargs)

View file

@ -68,7 +68,10 @@ class ControlPlane(models.Model):
description = models.TextField(blank=True, verbose_name=_("Description"))
k8s_api_endpoint = models.URLField(verbose_name=_("Kubernetes API endpoint"))
# TODO: schema
api_credentials = EncryptedJSONField(verbose_name=_("API credentials"))
api_credentials = EncryptedJSONField(
verbose_name=_("API credentials"),
help_text="Required fields: certificate-authority-data, server (URL), token",
)
cloud_provider = models.ForeignKey(
to="CloudProvider",