Land CSOC-WP-0006 PageOps: member space/page CRUD, copy, transfer
Thin CONTENT_ROOT content plane (ADR-0003/0004) with space visual field, product UI for Title/Abstract/Visual, page list sidebar, and tests. Update capability model and smoke checklist; mark WP-0006 finished.
This commit is contained in:
parent
00dbb86c93
commit
affed64839
24 changed files with 1812 additions and 151 deletions
|
|
@ -1,4 +1,4 @@
|
|||
"""Space metadata — content bodies live in Forgejo (markdown), not here."""
|
||||
"""Space metadata index — page bodies live on the content plane (markdown)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -23,15 +23,24 @@ def validate_space_slug(value: str) -> None:
|
|||
class Space(models.Model):
|
||||
"""Tenant-scoped co-creation space.
|
||||
|
||||
Postgres holds metadata and Forgejo binding pointers only. Long-form pages
|
||||
are markdown in the bound repository (ADR-0002).
|
||||
Postgres holds metadata and optional Forgejo binding pointers. Long-form
|
||||
pages are markdown under CONTENT_ROOT (ADR-0003 / ADR-0004); Forgejo is an
|
||||
optional read/edit fallback (ADR-0002).
|
||||
"""
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
tenant_id = models.CharField(max_length=64, db_index=True)
|
||||
slug = models.SlugField(max_length=80, validators=[validate_space_slug])
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
description = models.TextField(
|
||||
blank=True,
|
||||
help_text="Short description (product label: Abstractor).",
|
||||
)
|
||||
visual = models.CharField(
|
||||
max_length=512,
|
||||
blank=True,
|
||||
help_text="Cover/visual path relative to space tree (e.g. assets/cover.jpg).",
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
"members.Member",
|
||||
on_delete=models.PROTECT,
|
||||
|
|
@ -80,6 +89,15 @@ class Space(models.Model):
|
|||
def has_content_binding(self) -> bool:
|
||||
return bool(self.forgejo_owner and self.forgejo_repo)
|
||||
|
||||
@property
|
||||
def abstractor(self) -> str:
|
||||
"""Product name for the short description field."""
|
||||
return self.description
|
||||
|
||||
@abstractor.setter
|
||||
def abstractor(self, value: str) -> None:
|
||||
self.description = value or ""
|
||||
|
||||
@classmethod
|
||||
def suggest_slug(cls, title: str) -> str:
|
||||
return slugify(title)[:80] or "space"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue