stapel-listings
Listings and catalog vertical: a Listing core (owner, opaque category, typed attribute values, price + price_base, inventory) with a draft/publish lifecycle including the moderation takedown state 'blocked', an independent moderation status, a value-validation pipeline delegated to stapel-attributes against a category schema fetched over comm, a publish service, first-class favorites, and the two pull seams its consumers read it through — listings.search_documents / listings.search_export for an indexer and listings.moderation_content for a moderation queue.
Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.
Install
pip install stapel-listings
At a glance
| Fact | Value |
|---|---|
| Version | 0.23.2 |
| Python | >=3.11 (3.11, 3.12, 3.13, 3.14) |
| Django | djangorestframework>=3.14 |
| HTTP operations | 20 |
| Config axes | 3 |
| Usage surface | 10 |
| Extension points | 6 |
| Error codes | 72 |
| Fleet dependencies | stapel-attributes · stapel-categories (optional) · stapel-core |
Documentation
OpenAPI · capabilities.json · llms.txt (for agents)
What this is
Listing is the marketplace core: an owner, an opaque category, typed
attribute values, a two-machine lifecycle + moderation status, a publish
pipeline, and first-class favorites. It consumes stapel-categories (feature
schema, over comm) and stapel-attributes (value validation), and stays
decoupled from search, moderation and currencies (see the boundaries below).
Quick start
INSTALLED_APPS = [
# ...
"stapel_listings",
]
# urls.py — this module's own urls.py bakes in only `v1/`; the host
# contributes `api/`, giving the canonical `/listings/api/v1/...` prefix.
path("listings/api/", include("stapel_listings.urls"))
Requires a categories.features comm Function provider (stapel-categories) for
value validation against a category's schema.
Settings
All configuration lives in the STAPEL_LISTINGS namespace (dict setting, flat
setting, or env var — resolved lazily). Full table with seam semantics in
MODULE.md.
| Key | Default | Meaning |
|---|---|---|
CATEGORY_FEATURES_FUNCTION |
"categories.features" |
comm Function resolving a category's feature schema. |
PRICE_BASE_CONVERTER |
identity | Dotted-path (amount, currency, base) -> Decimal. |
AUTO_APPROVE_ON_PUBLISH |
False |
Publish immediately when no moderation module is installed. |
REQUIRE_IMAGE_ON_PUBLISH |
True |
Require ≥1 image to publish. |
MODERATION_TARGET_TYPE |
"listing" |
target_type this module answers to in moderation.completed. |
LISTING_URL_TEMPLATE |
"" |
Public URL template ({listing_id}) for the moderator's card. |
DEFAULT_LISTING_TTL_DAYS |
30 |
Days until a published listing expires. |
comm surface
Emits (Actions): listing.submitted (moderation boundary),
listing.published / listing.updated / listing.removed (search boundary).
Consumes: category.changed, moderation.completed, user.deleted.
Provides Functions: listings.status, listings.search_documents,
listings.search_export, listings.moderation_content,
listings.rename_feature_keys (the catalogue seam — a feature-slug rename
moves the stored draft keys here).
Calls: categories.features, categories.children.
Boundaries: search/filtering is a separate stapel-search module; this
module builds features_search, signals with the listing.* events and hands
over the document through listings.search_documents (keyed batch) and
listings.search_export (cursor snapshot), but exposes no search endpoints —
the events carry identity, so no listing content rides the durable bus and no
indexer reads this database. Moderation is a separate stapel-moderation
module: this module emits listing.submitted, serves the content over
listings.moderation_content and applies the target-generic
moderation.completed verdict (including the published → blocked takedown),
but runs no moderation pipeline. Re-moderating an edit of a live listing is
post-moderation: the lifecycle stays published, moderation_status goes to
pending, the edit is visible immediately, and a rejecting verdict removes it
through the takedown edge.
Contract
docs/{schema,flows,errors}.json are emitted from a single-module
{listings + core} Django instance mounted at the canonical
/listings/api/v1 prefix (make contract / make contract-check; see
_codegen.py) — the same mechanism stapel-search, stapel-chat and
stapel-forms already use. docs/flows.json is []: no flow is declared via
@flow yet, same state as every other contract-complete module today.
Delta note — one field stays untyped on purpose. features_search
(ListingDetailSerializer) is a flattened per-category search index: one
dynamic key per feature slug, shaped by whatever category schema a given
listing happens to carry. There is no fixed property set to declare, so the
schema types it as a bare object rather than fake a closed shape that would
go stale the moment any category adds a feature. Every other field that used
to fall back to an untyped blob this way — images / images_draft, both
lists of opaque <type>/<hash> CDN refs (models.py "Opaque list of CDN image
references") — is now typed as array[string], and the ten polymorphic
attribute-value shapes (FeatureDto/FeatureDao) are a proper
discriminated oneOf keyed by type, contributed by stapel-attributes.
Extension points
See MODULE.md — the agent-facing map of every fork-free seam (settings, serializer seams, comm surface, GDPR provider).
Development
pip install -e . && pip install pytest pytest-django ruff
./setup-hooks.sh
pytest tests/
License
MIT — see LICENSE.
This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.
Metadata
Release files for stapel-listings 0.23.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stapel_listings-0.23.2.tar.gz | 262.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stapel_listings-0.23.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 449.0 kB
Release files / stapel_listings-0.23.2.tar.gz
| Download URL | stapel_listings-0.23.2.tar.gz |
|---|---|
| Size | 262.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a28983b5e022e9ae77bb9434ed560089b1cbd838e28643bcac64187e3142cf67
|
|
BLAKE2b-256 checksum How to use checksums |
f2db786e75d6d0953c1cf54597fd685284af04b937ca3a70ba7aa183f453f1cf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / stapel_listings-0.23.2-py3-none-any.whl
| Download URL | stapel_listings-0.23.2-py3-none-any.whl |
|---|---|
| Size | 186.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7818676cf3c5faccfcd8348030ff3f525bade456100dae71d417b9d308c16cbb
|
|
BLAKE2b-256 checksum How to use checksums |
c700f596833d0fac3b78a48dd0c6df27dbec24f77c0a343ce9b2da500af249b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log