Skip to content

feat: listing type and category admin with the schema builder - #124

Merged
OriginDevIT merged 3 commits into
mainfrom
feat/block2-listing-type-admin
Sep 6, 2026
Merged

feat: listing type and category admin with the schema builder#124
OriginDevIT merged 3 commits into
mainfrom
feat/block2-listing-type-admin

Conversation

@OriginDevIT

Copy link
Copy Markdown
Owner

ListingType and Category configuration through the service layer, emitting tenant.settings_changed with patch pointers at /listing_types/{key} and /categories/{type_key}/{slug}. field_schema.py validates against the closed type set with keys frozen after creation. HTMX schema builder so field definitions are edited as form rows rather than JSON. path_segment and category slug changes write PathRedirect rows. htmx is vendored, not CDN-loaded.

OriginDevIT and others added 3 commits September 6, 2026 09:22
Block 2, PR 1 of 4. Tenant-admin configuration for listing types and
their categories, at /admin on the tenant's own domain. Custom views,
not Django ModelAdmin -- writes go through directory/services.py so
they emit tenant.settings_changed and hit the command log
(invariant 6).

- directory/field_schema.py: validate_type_schema() -- the closed type
  set, options rules, searchable only on text/long_text/select,
  reserved-name and slug checks, and the frozen-field-type rule
  (ruling 6: a field's type cannot change once it exists; add a new
  key). normalize_type_schema() canonicalises a validated schema.

- directory/services.py: create/update/delete for ListingType and
  Category. key is frozen after creation (update_listing_type refuses
  it). path_segment is editable; changing it while multi-type, and
  gaining a second type, each write a PathRedirect row (spec §4.5).
  Patch pointers are a local convention (ruling 1):
  /listing_types/{key} and /categories/{type_key}/{slug}. Deleting a
  type with listings is blocked (PROTECT -> friendly error).

- HTMX schema builder: field rows are added/removed by fetching a
  server-rendered partial; the set posts as index-aligned parallel
  arrays (selects, not checkboxes, so alignment survives). Invalid
  schemas re-render with errors and never persist.

- directory/access.py: tenant_admin_required -- an active
  StaffMembership at >= the given rank (default admin). No membership
  -> 404; too low a rank -> 403. AUTHORIZATION SURFACE, flagged for
  review; the operator login form is a separate PR and tests use
  force_login.

- New model PathRedirect (directory/0003) + templatetag join_lines.

public default for a custom field is True (spec is silent). htmx is
CDN-loaded with a TODO to vendor.

49 new tests (schema validation incl. every rejection case and the
frozen-type rule end to end; the config services; the admin views and
their access control). Full suite 123, green on Postgres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5qqrLcxpVZRis5jbN5pKD
Signed-off-by: Matthew Wren <info@origindev.com>
The schema builder runs on an authenticated tenant admin session. A
CDN <script> would be a third-party request from that session on every
page and would leak the tenant domain (Referer, SNI) to the CDN.

htmx 2.0.4 is now committed at
directory/static/directory/vendor/htmx-2.0.4.min.js with a provenance
header (source URL, retrieval date, licence). base.html loads it via
{% static %}. A test asserts the file is on disk with the header and
that no admin template references a CDN host.

Note: production static serving (whitenoise middleware + STATIC_ROOT +
collectstatic) is still unwired -- that is a release/deploy PR. In
DEBUG, runserver's staticfiles handler serves it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5qqrLcxpVZRis5jbN5pKD
Signed-off-by: Matthew Wren <info@origindev.com>
Category browse pages and the listing URLs under them are indexed, so
a slug rename needs a 301 just as a path_segment change does. The
PathRedirect model is already used in this PR.

update_category now records tenant + old_prefix -> new_prefix on a slug
change, using /<slug> while single-type and /<segment>/<slug> once the
tenant is multi-type (ruling 7 -- one category level either way). A
non-slug edit writes nothing. Chained/overlapping redirects are the
public router's concern (a later PR).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5qqrLcxpVZRis5jbN5pKD
Signed-off-by: Matthew Wren <info@origindev.com>
@OriginDevIT
OriginDevIT merged commit c69b806 into main Sep 6, 2026
1 check passed
@OriginDevIT
OriginDevIT deleted the feat/block2-listing-type-admin branch September 6, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant