feat(directory): listing media upload against per-tenant storage - #152
Merged
Conversation
Adds MediaAsset and a per-tenant storage resolver, so an operator can upload logo, cover and gallery images to a listing and the public detail page renders them. - MediaAsset: canonical for asset facts (bytes, dimensions, status, storage key, provenance). Listing.media is a projection rebuilt from the ready rows on every attach/detach. - directory.storage.get_tenant_storage resolves the backend per request from tenant.settings["storage"]. Local disk only; s3/azure/gcp raise DeferredFeatureError (#150). - directory.media.attach_media / detach_media: validate, orient, strip EXIF and re-encode with Pillow in-request (#151); no worker exists, so derivatives and abuse scanning are out of scope and no seam is left. MediaAsset.status carries an unused "quarantined" value. - Both operations emit listing.updated only; media.* stays deferred (spec 3.4). Gallery changes are a whole-array replace at /media/gallery. - media is a rejected key on listing.upsert (spec 7.1 v0.7): 422 naming the field when present, even null. - Tenant-scoped /media/<asset_id> view streams local assets; "media" is a reserved slug. - One migration (0005_mediaasset). pillow==11.3.0 pinned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C4DdoL1tjw53aVyHpwo4L3
Review follow-ups on the media PR. - media_asset view now filters listing__visibility=PUBLISHED, so a draft or hidden listing's images are not publicly retrievable by asset id (decisions.md §4.1, ruling 13). Behavioural 404 test added. - PublishedGuardTests widened: it walked only `Listing.objects.*` and so never saw the media view querying MediaAsset. It now AST-walks every `<Model>.objects` in public_views.py and requires a published()/ visibility constraint on any model carrying visibility or a listing FK. Verified it fails on the pre-fix view. - Media ref `url` is now absolute `https://<primary_domain>/media/<id>` once the tenant's domain is verified, relative until then. This value is written into the durable listing.updated outbox payload, where a relative path is unresolvable off-box. Scheme is always https and does not branch on DEBUG / OSDS_SECURE_COOKIES. decisions.md §4.1 row added. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C4DdoL1tjw53aVyHpwo4L3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MediaAsset, per-tenant storage resolver (local only), attach/detach services emitting listing.updated, tenant-scoped media serving view, admin upload UI, public rendering.
Rejects media on listing.upsert per spec 7.1 (v0.7).
Cloud backends deferred, #150. Pillow added, closes #151.