diff --git a/src/servala/core/admin.py b/src/servala/core/admin.py index 746d5da..7d110d1 100644 --- a/src/servala/core/admin.py +++ b/src/servala/core/admin.py @@ -65,7 +65,8 @@ class OrganizationAdmin(admin.ModelAdmin): def get_readonly_fields(self, request, obj=None): readonly_fields = list(super().get_readonly_fields(request, obj) or []) - readonly_fields.append("namespace") # Always read-only + if obj: # If this is an edit (not a new organization) + readonly_fields.append("namespace") return readonly_fields diff --git a/src/servala/core/migrations/0003_alter_organization_namespace.py b/src/servala/core/migrations/0003_alter_organization_namespace.py deleted file mode 100644 index 3a01990..0000000 --- a/src/servala/core/migrations/0003_alter_organization_namespace.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.2b1 on 2025-04-09 14:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("core", "0002_alter_controlplanecrd_options_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="organization", - name="namespace", - field=models.CharField( - help_text="This namespace will be used for all Kubernetes resources.", - max_length=20, - null=True, - unique=True, - verbose_name="Kubernetes Namespace", - ), - ), - ] diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index 7ebda11..62894d5 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -9,19 +9,19 @@ from django_scopes import ScopedManager, scopes_disabled from servala.core import rules as perms from servala.core.models.mixins import ServalaModelMixin +from servala.core.validators import kubernetes_name_validator class Organization(ServalaModelMixin, models.Model): name = models.CharField(max_length=100, verbose_name=_("Name")) - # The namespace is generated as "org-{id}" in accordance with RFC 1035 Label Names. - # It is nullable as we need to write to the database in order to read the ID, but should - # not be null in practical use. namespace = models.CharField( - max_length=20, + max_length=63, verbose_name=_("Kubernetes Namespace"), - null=True, unique=True, - help_text=_("This namespace will be used for all Kubernetes resources."), + help_text=_( + "This namespace will be used for all Kubernetes resources. Cannot be changed after creation." + ), + validators=[kubernetes_name_validator], ) billing_entity = models.ForeignKey( @@ -90,13 +90,6 @@ class Organization(ServalaModelMixin, models.Model): def __str__(self): return self.name - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - if not self.namespace: - # Set namespace after initial save to ensure we have an ID - self.namespace = f"org-{self.pk}" - self.save(update_fields=["namespace"]) - class BillingEntity(ServalaModelMixin, models.Model): """ diff --git a/src/servala/frontend/forms/organization.py b/src/servala/frontend/forms/organization.py index 41cc26c..d582ce9 100644 --- a/src/servala/frontend/forms/organization.py +++ b/src/servala/frontend/forms/organization.py @@ -1,4 +1,6 @@ +from django import forms from django.forms import ModelForm +from django.utils.translation import gettext_lazy as _ from servala.core.models import Organization from servala.frontend.forms.mixins import HtmxMixin @@ -7,4 +9,19 @@ from servala.frontend.forms.mixins import HtmxMixin class OrganizationForm(HtmxMixin, ModelForm): class Meta: model = Organization - fields = ("name",) + fields = ("name", "namespace") + widgets = { + "namespace": forms.TextInput( + attrs={ + "pattern": "[a-z0-9]([-a-z0-9]*[a-z0-9])?", + "title": _( + 'Lowercase alphanumeric characters or "-", must start and end with alphanumeric' + ), + } + ) + } + help_texts = { + "namespace": _( + "This namespace will be used for all resources and cannot be changed later." + ) + } diff --git a/src/servala/frontend/templates/frontend/organizations/update.html b/src/servala/frontend/templates/frontend/organizations/update.html index 0d55f22..993b76a 100644 --- a/src/servala/frontend/templates/frontend/organizations/update.html +++ b/src/servala/frontend/templates/frontend/organizations/update.html @@ -52,7 +52,7 @@