Problem
Release pages are currently flat YAML blobs rendered by ReleaseContentHandler.php. There's no schema enforcement beyond valid YAML, no typed fields per release type, no object model, and no query methods beyond the custom releaselist API action.
Every new field requires touching the PHP renderer. Batch operations (enrichment, cleanup, migration) are reimplemented as standalone Python scripts in blue-railroad-import that parse YAML, mutate strings, and push edits through the MediaWiki API.
There are now three separate draft/staging systems that don't talk to each other:
- delivery-kid content drafts (
/draft-content) — temporary file staging, 24h TTL
- PickiPedia ReleaseDrafts — wiki namespace pages with metadata
- Blue Railroad submissions — on-chain submissions with their own lifecycle
Each was invented independently with its own state management.
Vision
Releases should behave more like Django models:
- Typed release kinds (album, blue-railroad, talk, video, etc.) with per-type required/optional fields
- Proper validation on save
- Query interface ("all releases where type=talk", "releases missing torrent metadata")
- Methods (
release.add_version(), release.enrich())
- Unified draft/staging lifecycle
Decision: Django app (hybrid model)
The wiki page remains the human-editable interface. A Django app owns the data model, validation, and API. Edits sync bidirectionally.
Why Django over extending delivery-kid (FastAPI)
- delivery-kid is about file staging and transcoding — different concern, different deploy cadence
- Django's ORM, migrations, and admin are better suited for a data-modeling problem than FastAPI + hand-rolled SQLAlchemy
- Django admin gives us a debugging/batch-ops interface for free
- blue-railroad-import already does Release operations in Python — the model classes there (DraftType, RecordDraft, VideoDraft, etc.) are proto-Django models waiting to graduate
Why hybrid over pure-DB or pure-wiki
- Wiki pages are the human interface people know. Editors shouldn't need to learn a new tool.
- But wiki pages are a terrible database. No schema, no queries, no validation, no relations.
- Hybrid: wiki is the view/editor layer, Django is the data layer. Best of both.
Architecture sketch
┌─────────────────────┐
│ Wiki page edit │ (human or bot)
│ (ReleaseDraft or │
│ Release namespace)│
└────────┬────────────┘
│ ContentSaveComplete hook
▼
┌─────────────────────┐
│ PHP hook → HTTP │ POST to Django API with parsed YAML
│ (PickiPediaReleases │
│ extension) │
└────────┬────────────┘
▼
┌─────────────────────┐
│ Django app │ Validates, stores, indexes
│ - Release model │ Returns errors if validation fails
│ - ReleaseDraft model│ (hook can reject the edit)
│ - REST API │
│ - Admin interface │
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ PostgreSQL │ magenta_memory DB or separate DB
│ (on hunter) │
└─────────────────────┘
Reverse sync (Django → wiki)
When Django modifies a Release (e.g., bot enrichment, batch update):
- Django calls the MediaWiki API to update the wiki page
- Same pattern blue-railroad-import uses today, but from Django instead of standalone scripts
What moves out of PHP
| Currently in PHP |
Moves to Django |
| YAML parsing + validation |
Model with typed fields |
| Draft type dispatch (record/video/other) |
Model subclasses or type field with per-type validation |
releaselist API action |
REST API with filtering/search |
| Status derivation (is finalized? has Release page?) |
Model state field with transitions |
| Blockheight estimation |
Utility function (already in Python) |
What stays in PHP
| Stays in PHP |
Why |
| Wiki page rendering (ContentHandler) |
MW integration — but renders from Django API, not raw YAML |
| Special pages (DeliverRecord, etc.) |
MW integration — but POST to Django instead of building YAML |
| HMAC token generation |
Auth layer, MW session-aware |
| Hook wiring |
MW event system |
What this replaces in blue-railroad-import
The release_draft.py module — DraftType, RecordDraft, VideoDraft, OtherDraft, BlueRailroadDraft, build_release_from_draft(), process_release_drafts() — all become Django model methods. The bot loop calls Django's API instead of parsing wiki YAML directly.
Implementation notes for later
Django models (rough sketch)
class Release(models.Model):
cid = models.CharField(max_length=128, unique=True, db_index=True)
release_type = models.CharField(choices=RELEASE_TYPES) # record, video, other, blue-railroad
title = models.CharField(max_length=512)
description = models.TextField(blank=True)
blockheight = models.PositiveBigIntegerField(null=True) # content time
upload_blockheight = models.PositiveBigIntegerField(null=True) # upload time
commit = models.CharField(max_length=64, blank=True) # maybelle-config build
pinned_on = ArrayField(models.CharField(max_length=64))
created_at = models.DateTimeField(auto_now_add=True)
# Type-specific fields (nullable, relevant per release_type)
artist = models.CharField(max_length=256, blank=True)
venue = models.CharField(max_length=256, blank=True)
performers = ArrayField(models.CharField(max_length=128), default=list)
file_type = models.CharField(max_length=64, blank=True)
torrent_url = models.URLField(blank=True)
webseed_url = models.URLField(blank=True)
submission_id = models.PositiveIntegerField(null=True) # Blue Railroad
class ReleaseDraft(models.Model):
draft_id = models.UUIDField(unique=True)
draft_type = models.CharField(choices=DRAFT_TYPES)
source = models.CharField(max_length=64) # special-deliver-video, blue-railroad-bot, etc.
uploader = models.CharField(max_length=128)
commit = models.CharField(max_length=64, blank=True)
blockheight = models.PositiveBigIntegerField(null=True)
upload_blockheight = models.PositiveBigIntegerField(null=True)
state = models.CharField(choices=DRAFT_STATES) # draft, transcoding, finalized, error
release = models.ForeignKey(Release, null=True, on_delete=models.SET_NULL)
metadata = models.JSONField(default=dict) # type-specific content blob
created_at = models.DateTimeField(auto_now_add=True)
Migration path
- Build the Django app with models + REST API + admin
- Write a management command that imports all existing Release and ReleaseDraft wiki pages into the DB (one-time migration)
- Add the
ContentSaveComplete hook in PHP that syncs wiki edits → Django
- Switch the PHP ContentHandler to render from Django API responses
- Switch the Deliver page JS to POST structured data to Django instead of building YAML
- blue-railroad-import bot calls Django API instead of wiki API for Release operations
- delivery-kid finalize callback notifies Django instead of (or in addition to) creating wiki edits directly
Where it lives
Options:
- New repo (
cryptograss/pickipedia-api or cryptograss/release-service) — cleanest separation
- Inside maybelle-config alongside delivery-kid — colocated with the other Python services
- Inside pickipedia repo as a
django/ directory — keeps wiki + API together
Leaning toward a new repo. It's a distinct service with its own deploy, DB, and dependencies.
Deploy
- Runs on hunter alongside delivery-kid and the wiki
- Nginx reverse-proxies
/api/releases/ to Django (gunicorn/uvicorn)
- Shares the PostgreSQL instance (separate database or schema)
- Managed by maybelle-config deploy scripts
Context
This came up while cleaning up Release pages (CIDv0/v1 duplicates, unlabeled test uploads, missing metadata) and while porting the Coconut.co transcoding integration. The lack of structure makes problems hard to prevent and tedious to fix. The three parallel draft systems are a symptom of not having a unified Release lifecycle.
As of block ~22520000 (March 2026), the PHP extension handles: 3 Special pages (DeliverRecord, DeliverOtherContent, DeliverVideo), ReleaseDraft content type with 5 draft type handlers, Release content type, HMAC auth for upload/finalize, and hand-built YAML in 4 JS modules via quoteYamlValue(). All of this strains under the lack of a real data model.
Problem
Release pages are currently flat YAML blobs rendered by
ReleaseContentHandler.php. There's no schema enforcement beyond valid YAML, no typed fields per release type, no object model, and no query methods beyond the customreleaselistAPI action.Every new field requires touching the PHP renderer. Batch operations (enrichment, cleanup, migration) are reimplemented as standalone Python scripts in blue-railroad-import that parse YAML, mutate strings, and push edits through the MediaWiki API.
There are now three separate draft/staging systems that don't talk to each other:
/draft-content) — temporary file staging, 24h TTLEach was invented independently with its own state management.
Vision
Releases should behave more like Django models:
release.add_version(),release.enrich())Decision: Django app (hybrid model)
The wiki page remains the human-editable interface. A Django app owns the data model, validation, and API. Edits sync bidirectionally.
Why Django over extending delivery-kid (FastAPI)
Why hybrid over pure-DB or pure-wiki
Architecture sketch
Reverse sync (Django → wiki)
When Django modifies a Release (e.g., bot enrichment, batch update):
What moves out of PHP
releaselistAPI actionWhat stays in PHP
What this replaces in blue-railroad-import
The
release_draft.pymodule —DraftType,RecordDraft,VideoDraft,OtherDraft,BlueRailroadDraft,build_release_from_draft(),process_release_drafts()— all become Django model methods. The bot loop calls Django's API instead of parsing wiki YAML directly.Implementation notes for later
Django models (rough sketch)
Migration path
ContentSaveCompletehook in PHP that syncs wiki edits → DjangoWhere it lives
Options:
cryptograss/pickipedia-apiorcryptograss/release-service) — cleanest separationdjango/directory — keeps wiki + API togetherLeaning toward a new repo. It's a distinct service with its own deploy, DB, and dependencies.
Deploy
/api/releases/to Django (gunicorn/uvicorn)Context
This came up while cleaning up Release pages (CIDv0/v1 duplicates, unlabeled test uploads, missing metadata) and while porting the Coconut.co transcoding integration. The lack of structure makes problems hard to prevent and tedious to fix. The three parallel draft systems are a symptom of not having a unified Release lifecycle.
As of block ~22520000 (March 2026), the PHP extension handles: 3 Special pages (DeliverRecord, DeliverOtherContent, DeliverVideo), ReleaseDraft content type with 5 draft type handlers, Release content type, HMAC auth for upload/finalize, and hand-built YAML in 4 JS modules via
quoteYamlValue(). All of this strains under the lack of a real data model.