Build dynamic model and modelform generation

This commit is contained in:
Tobias Kunze 2025-03-25 17:13:43 +01:00
parent af64d5468f
commit 234ff8e1d6

92
src/servala/core/crd.py Normal file
View file

@ -0,0 +1,92 @@
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.db import models
from django.forms.models import ModelForm, ModelFormMetaclass
def generate_django_model(crd, group, version, kind):
"""
Generates a virtual Django model from a Kubernetes CRD's OpenAPI v3 schema.
"""
schema = crd.spec.versions[0].schema.open_apiv3_schema
properties = schema.properties
required_fields = schema.required
model_fields = {"__module__": "crd_models"}
defaults = {"apiVersion": f"{group}/{version}", "kind": kind}
for prop_name, prop_schema in properties.items():
is_required = prop_name in required_fields
field = get_django_field(
prop_schema, is_required, default=defaults.get(prop_name)
)
model_fields[prop_name] = field
meta_class = type("Meta", (), {"app_label": "crd_models"})
model_fields["Meta"] = meta_class
# create the model class
model_name = crd.spec.names.kind
model_class = type(model_name, (models.Model,), model_fields)
return model_class
def get_django_field(prop_schema, is_required=False, default=None):
field_type = prop_schema.type or "string"
format = prop_schema.format
kwargs = {
"blank": not is_required,
"null": not is_required,
"help_text": prop_schema.description or "",
"validators": [],
"default": default,
# TODO: verbose_name?
}
if prop_schema.minimum:
kwargs["validators"].append(MinValueValidator(prop_schema.minimum))
if prop_schema.maximum:
kwargs["validators"].append(MaxValueValidator(prop_schema.maximum))
if field_type == "string":
if format == "date-time":
return models.DateTimeField(**kwargs)
elif format == "date":
return models.DateField(**kwargs)
else:
max_length = prop_schema.max_length or 255
if prop_schema.pattern:
kwargs["validators"].append(RegexValidator(regex=prop_schema.pattern))
return models.CharField(max_length=max_length, **kwargs)
elif field_type == "integer":
return models.IntegerField(**kwargs)
elif field_type == "number":
return models.FloatField(**kwargs)
elif field_type == "boolean":
return models.BooleanField(**kwargs)
elif field_type == "object":
kwargs["help_text"] += " (JSON object)"
return models.JSONField(**kwargs)
elif field_type == "array":
kwargs["help_text"] += " (JSON array)"
return models.JSONField(**kwargs)
return models.CharField(max_length=255, **kwargs)
class CrdModelFormMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["apiVersion"].disabled = True
def generate_model_form_class(model):
meta_attrs = {
"model": model,
"fields": "__all__",
}
fields = {
"Meta": type("Meta", (object,), meta_attrs),
"__module__": "crd_models",
}
class_name = f"{model.__name__}ModelForm"
return ModelFormMetaclass(class_name, (CrdModelFormMixin, ModelForm), fields)