Metadata-Version: 2.4
Name: stapel-classified
Version: 0.1.8
Summary: Composite: classified ads — the shop composite + geo + search + moderation
License: MIT
Project-URL: Homepage, https://github.com/usestapel/stapel-classified
Project-URL: Repository, https://github.com/usestapel/stapel-classified
Project-URL: Documentation, https://github.com/usestapel/stapel-classified#readme
Project-URL: Changelog, https://github.com/usestapel/stapel-classified/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/usestapel/stapel-classified/issues
Keywords: django,stapel,classified,composite,geo,search,moderation
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: stapel-core<1.0,>=0.32
Requires-Dist: stapel-shop<0.3,>=0.2.3
Requires-Dist: stapel-categories<0.6,>=0.5.6
Requires-Dist: stapel-listings<0.8,>=0.5
Requires-Dist: stapel-geo<0.4,>=0.2
Requires-Dist: stapel-search<0.3,>=0.2.2
Requires-Dist: stapel-moderation<0.2,>=0.1
Provides-Extra: all
Dynamic: license-file

<!-- Generated by stapel-readme from docs/readme.md + docs/*.json. Do not edit this file; edit docs/readme.md and re-run `make readme`. -->

# stapel-classified

[![CI](https://img.shields.io/github/actions/workflow/status/usestapel/stapel-classified/ci.yml?branch=main&logo=github&label=CI)](https://github.com/usestapel/stapel-classified/actions/workflows/ci.yml?query=branch%3Amain)
[![coverage](https://img.shields.io/codecov/c/github/usestapel/stapel-classified?branch=main&logo=codecov&label=coverage)](https://app.codecov.io/gh/usestapel/stapel-classified)
[![pypi](https://img.shields.io/pypi/v/stapel-classified?logo=pypi&logoColor=white&label=pypi)](https://pypi.org/project/stapel-classified/)
[![downloads](https://static.pepy.tech/badge/stapel-classified/month)](https://pepy.tech/project/stapel-classified)
[![python](https://img.shields.io/pypi/pyversions/stapel-classified?logo=python&logoColor=white)](https://pypi.org/project/stapel-classified/)
[![license](https://img.shields.io/github/license/usestapel/stapel-classified)](https://github.com/usestapel/stapel-classified/blob/main/LICENSE)
[![llms.txt](https://img.shields.io/badge/llms.txt-blue)](https://github.com/usestapel/stapel-classified/blob/main/docs/llms.txt)

> Composite preset for location-bound classified ads: the stapel-shop composite (categories + listings + reviews) plus stapel-geo, stapel-search and stapel-moderation. Carries the cross-domain declarations no member is allowed to write: the `listing` search source (listings.search_documents/search_export -> SearchDocumentInput, invalidated by listing.*) and the `listing`/`review` moderation target policies, plus the shop composite's ListingReviewSummaryProjection. Listing coordinates are the listing's own fields, not a projection. Mounts no urls of its own.

Part of the [Stapel framework](https://github.com/usestapel) — composable Django apps that deploy as a monolith or as microservices without changing module code.

## Install

```bash
pip install stapel-classified
```

## At a glance

| Fact | Value |
|---|---|
| Version | `0.1.8` |
| Python | `>=3.11` (3.11, 3.12, 3.13, 3.14) |
| Fleet dependencies | [`stapel-attributes`](https://github.com/usestapel/stapel-attributes) · [`stapel-categories`](https://github.com/usestapel/stapel-categories) · [`stapel-geo`](https://github.com/usestapel/stapel-geo) · [`stapel-listings`](https://github.com/usestapel/stapel-listings) · [`stapel-moderation`](https://github.com/usestapel/stapel-moderation) · [`stapel-notifications`](https://github.com/usestapel/stapel-notifications) (optional) · [`stapel-reviews`](https://github.com/usestapel/stapel-reviews) · [`stapel-search`](https://github.com/usestapel/stapel-search) |

## Documentation

[capabilities.json](https://github.com/usestapel/stapel-classified/blob/main/docs/capabilities.json) · [llms.txt (for agents)](https://github.com/usestapel/stapel-classified/blob/main/docs/llms.txt)

## Assemble (one line)

```bash
pip install stapel-tools
stapel-assemble myads --libs classified
cd myads && make test
```

That expands `classified` through the STAPEL_LIBS `requires`
closure and wires every member module into INSTALLED_APPS,
requirements.txt, urls.py and CONFIG.MD, then runs the verify gates.

## Manual wiring (no scaffold)

```python
# settings.py
from stapel_classified import preset

INSTALLED_APPS = [
    # ... django/stapel-core baseline (incl. stapel_core.django.projections)
    *preset.INSTALLED_APPS,
]
for _k, _v in preset.SETTINGS_DEFAULTS.items():
    globals().setdefault(_k, _v)

# urls.py
from django.urls import include, path

from stapel_classified import preset

urlpatterns = [
    path(prefix, include(module)) for prefix, module in preset.URL_INCLUDES
]
```

Mount from `preset.URL_INCLUDES` rather than by hand: `stapel-categories` and
`stapel-listings` contribute only the `v1/` segment and belong under
`<mod>/api/`, while `reviews`, `geo`, `search` and `moderation` bake `api/v1/`
in themselves. Both end at `/<mod>/api/v1/...`, and getting it wrong is a
`stapel_core.mounts.E004` refusal to boot, not a cosmetic difference.

## Config checklist (fill these, in the generated project's CONFIG.MD too)

| Key | Note |
|-----|------|
| `STAPEL_REVIEWS["TARGET_TYPES"]` | prefilled by the preset (targets `listing`) |
| `STAPEL_SEARCH["SOURCES"]` | prefilled by the preset (the `listing` source) |
| `STAPEL_MODERATION["TARGET_TYPES"]` | prefilled by the preset (`listing` pre-publication, `review` post) |
| `STAPEL_ACCESS["ROLES"]` | **yours** — the moderation console is staff-only; `preset.RECOMMENDED_ACCESS_ROLES` shows the shape |
| `STAPEL_GDPR["DATA_OWNERS"]` | **yours** — must list `"moderation"`, or erasure never closes over complaint data |
| `STAPEL_MODERATION["APPEAL_URL_TEMPLATE"]` | **yours** — an empty appeal link is what DSA Art. 17 notices |
| `STAPEL_SEARCH["BACKEND"]` | defaults to Postgres; name `naive` or `meili` if that is not your engine |
| `STAPEL_LISTINGS["BASE_CURRENCY"]` | default `USD` — set your currency |
| `STAPEL_GEO[...]` | geocoder provider/keys — see stapel-geo CONFIG.MD |
| listing coordinates | lat/lon are LISTING fields (no projection needed) — see stapel-listings |

## Glue

Two members ship deliberately empty registries, because neither may know what
a listing is. This package is the one place that knows both sides:

- **`STAPEL_SEARCH["SOURCES"]["listing"]`** →
  `stapel_classified.search_sources.listing_source`. Pulls documents through
  `listings.search_documents` / `listings.search_export`, invalidated by
  `listing.published` / `listing.updated` / `listing.removed`. Registering it
  is also what wires the subscribers — you write no signal handler. Facets are
  built from listings' `features_search` (stapel-search's declared lossy
  fallback: attribute *range* filters do not work until listings serves DAOs).
- **`STAPEL_MODERATION["TARGET_TYPES"]`** → `listing` (pre-publication:
  `listing.submitted` opens the case and nothing is public until the verdict)
  and `review` (post: live on arrival, a verdict is a takedown).

Coordinates need no glue at all: they are the listing's OWN fields (lat/lon on
the listing), not a foreign aggregate.

## License

MIT — see [LICENSE](https://github.com/usestapel/stapel-classified/blob/main/LICENSE).

---

<sub>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`.</sub>
