Render ControlPlane.api_credentials in admin as separate fields
This commit is contained in:
parent
81396297f9
commit
8a8745f1fd
3 changed files with 103 additions and 1 deletions
|
@ -1,6 +1,7 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from servala.core.forms import ControlPlaneAdminForm
|
||||||
from servala.core.models import (
|
from servala.core.models import (
|
||||||
BillingEntity,
|
BillingEntity,
|
||||||
CloudProvider,
|
CloudProvider,
|
||||||
|
@ -110,11 +111,32 @@ class CloudProviderAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(ControlPlane)
|
@admin.register(ControlPlane)
|
||||||
class ControlPlaneAdmin(admin.ModelAdmin):
|
class ControlPlaneAdmin(admin.ModelAdmin):
|
||||||
|
form = ControlPlaneAdminForm
|
||||||
list_display = ("name", "cloud_provider", "k8s_api_endpoint")
|
list_display = ("name", "cloud_provider", "k8s_api_endpoint")
|
||||||
list_filter = ("cloud_provider",)
|
list_filter = ("cloud_provider",)
|
||||||
search_fields = ("name", "description", "k8s_api_endpoint")
|
search_fields = ("name", "description", "k8s_api_endpoint")
|
||||||
autocomplete_fields = ("cloud_provider",)
|
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)
|
@admin.register(Plan)
|
||||||
class PlanAdmin(admin.ModelAdmin):
|
class PlanAdmin(admin.ModelAdmin):
|
||||||
|
|
77
src/servala/core/forms.py
Normal file
77
src/servala/core/forms.py
Normal 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)
|
|
@ -68,7 +68,10 @@ class ControlPlane(models.Model):
|
||||||
description = models.TextField(blank=True, verbose_name=_("Description"))
|
description = models.TextField(blank=True, verbose_name=_("Description"))
|
||||||
k8s_api_endpoint = models.URLField(verbose_name=_("Kubernetes API endpoint"))
|
k8s_api_endpoint = models.URLField(verbose_name=_("Kubernetes API endpoint"))
|
||||||
# TODO: schema
|
# 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(
|
cloud_provider = models.ForeignKey(
|
||||||
to="CloudProvider",
|
to="CloudProvider",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue