feat: auto-populate slug fields on save

Add clubmanager.base.unique_slugify(instance, value, scope=...): slugify a
source value, truncate to the field's max_length, and append -2/-3/... to
stay unique within a scope. ClubScopedModel gains a slug_source hook that
fills a blank slug (unique per club) on save.

Wire it up so every SlugField auto-populates from its natural source when
left blank (explicit values are always kept):
- shop.Product.slug   <- name   (per club)
- formbuilder.Form.slug <- title (per club)
- formbuilder.Field.key <- label (per form)
Club.slug already auto-populated; refactor it onto the shared helper.

Also fix shop.Product.slug's multi-tenancy bug: it was globally unique
(unique=True); make it unique per club like the others. Migrations added.
Full suite at 100% coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 21:41:59 +02:00
parent 57f20fe544
commit 54aace8abb
9 changed files with 220 additions and 18 deletions

View File

@@ -1 +1,17 @@
# Create your models here.
from django.db import models
from django.db.models import UniqueConstraint
from django.utils.translation import gettext_lazy as _
from clubmanager.base import ClubScopedModel
class Product(ClubScopedModel):
name = models.CharField(_("name"), max_length=255)
slug = models.SlugField(_("slug"), max_length=255, blank=True)
slug_source = "name"
class Meta:
constraints = [
UniqueConstraint(fields=["club", "slug"], name="unique_product_slug_per_club"),
]