docs: community_classifieds standalone-reuse handover
A developer-facing guide for taking community_classifieds out of this monorepo into a different project: exact dependency surface (hard: website/portal; the one soft-check on community_membership and nowhere else, verified by grepping the whole module), how to move/rename it, the recent admin-access fix and how it works, config reference, data model, public routes, cron/email jobs, and known gaps (missing static/description/banner.png, empty demo/, placeholder pricing) to close before shipping it publicly.
This commit is contained in:
parent
90f3237dfe
commit
b0998f81a7
167
docs/community_classifieds_handover.md
Normal file
167
docs/community_classifieds_handover.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# `community_classifieds` — Module Handover
|
||||||
|
|
||||||
|
A technical guide for taking this module out of the CommunityOS monorepo
|
||||||
|
and installing it in a different Odoo project. For how the module *works*
|
||||||
|
day-to-day, see the CommunityOS build plan or `README.rst` inside the
|
||||||
|
module itself — this doc is specifically about the move.
|
||||||
|
|
||||||
|
## What it is
|
||||||
|
|
||||||
|
A member-gated classifieds board: public listing board, a posting form
|
||||||
|
for logged-in users, an admin moderation queue (publish/reject), and
|
||||||
|
automatic expiry with warning emails. Odoo 19 Community Edition, licensed
|
||||||
|
`LGPL-3`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
**Hard** (declared in `__manifest__.py`, both stock Odoo Community — no
|
||||||
|
Enterprise, nothing else from this suite):
|
||||||
|
```python
|
||||||
|
'depends': ['website', 'portal'],
|
||||||
|
```
|
||||||
|
|
||||||
|
**Soft — one runtime check, nowhere else**: `controllers/main.py` calls
|
||||||
|
`_is_module_installed(env, 'community_membership')` before gating who can
|
||||||
|
post. If `community_membership` is present *and* installed, posting
|
||||||
|
requires an active membership; if it's absent, that check is simply
|
||||||
|
skipped and posting only requires being logged in. This isn't in
|
||||||
|
`depends`, so Odoo has no idea the two modules are related — verified by
|
||||||
|
grepping the whole module for any other cross-reference to
|
||||||
|
`community_membership`, `community_theme_base`, `community_portal`, or
|
||||||
|
any other `community_*`/`event_qr_ticketing` module: there are none.
|
||||||
|
Every template it calls into (`website.layout`, `portal.portal_layout`)
|
||||||
|
comes from its own declared dependencies.
|
||||||
|
|
||||||
|
**Net effect**: copy the folder, install it in a project with just
|
||||||
|
`website` and `portal`, and it works exactly like it does here, minus
|
||||||
|
the optional membership-gating.
|
||||||
|
|
||||||
|
## Moving it
|
||||||
|
|
||||||
|
1. Copy `addons/community_classifieds/` into the new project's addons
|
||||||
|
path. That's the whole module — no other file in this repo is
|
||||||
|
required by it.
|
||||||
|
2. **No client data to strip.** This is a product-layer module in the
|
||||||
|
original repo's own architecture — it's already been through that
|
||||||
|
repo's brand-leak CI check (`scripts/check_brand_leak.py`), which
|
||||||
|
greps every `community_*` module for client names/emails/colours and
|
||||||
|
fails the build if it finds any. There's nothing TNCSC-specific
|
||||||
|
anywhere in it.
|
||||||
|
3. **Consider renaming the technical prefix** before distributing it
|
||||||
|
publicly or installing it alongside another copy of the same module —
|
||||||
|
`community_classifieds` could collide with another vendor's module of
|
||||||
|
the same name. If you rename the folder, note that the Python model
|
||||||
|
name (`community.classified`) is hardcoded in `models/community_classified.py`
|
||||||
|
and referenced by XML id throughout `views/`, `security/`, and
|
||||||
|
`data/` — a folder rename alone is enough (Odoo resolves everything
|
||||||
|
by folder/technical name automatically), but a *model* rename would
|
||||||
|
need a project-wide find-replace across every file in the module.
|
||||||
|
4. Install normally: `-i community_classifieds` (or via Apps once the
|
||||||
|
addons path is registered).
|
||||||
|
|
||||||
|
## Post-install: access & permissions
|
||||||
|
|
||||||
|
As of `19.0.1.0.1`, **every Odoo Administrator automatically gets
|
||||||
|
moderator access** — nothing to configure. This wasn't always true: a
|
||||||
|
standalone install used to leave the installing admin unable to see the
|
||||||
|
Classifieds app or Moderation queue at all, because the
|
||||||
|
`Classifieds Moderator` group wasn't implied by anything an admin already
|
||||||
|
had. Fixed in `security/classifieds_security.xml` by making
|
||||||
|
`base.group_system` (Odoo's built-in Administrator group) imply
|
||||||
|
`group_classifieds_moderator`:
|
||||||
|
```xml
|
||||||
|
<record id="base.group_system" model="res.groups">
|
||||||
|
<field name="implied_ids" eval="[(4, ref('group_classifieds_moderator'))]"/>
|
||||||
|
</record>
|
||||||
|
```
|
||||||
|
Verified on a from-scratch database with *only* `community_classifieds`
|
||||||
|
installed (`community_membership` and any deployment layer both
|
||||||
|
`uninstalled`): `admin.has_group('community_classifieds.group_classifieds_moderator')`
|
||||||
|
returns `True` and "Classifieds" appears in the app switcher immediately.
|
||||||
|
|
||||||
|
**To give a non-admin staff member moderator access**: Settings → Users
|
||||||
|
& Companies → Users → open their record → check **Classifieds Moderator**
|
||||||
|
under the Classifieds privilege group.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Settings → General Settings → **Classifieds** tab:
|
||||||
|
|
||||||
|
| Setting | Field / param | Default |
|
||||||
|
|---|---|---|
|
||||||
|
| Listing Duration (days) | `community_classifieds.expiry_days` | 30 |
|
||||||
|
|
||||||
|
One thing *not* exposed as a setting: the expiry-warning email fires
|
||||||
|
exactly **7 days** before expiry — `DEFAULT_WARNING_DAYS_BEFORE_EXPIRY`
|
||||||
|
in `models/community_classified.py`, a hardcoded constant. Change the
|
||||||
|
constant if a different lead time is needed; it's not wired to a
|
||||||
|
`res.config.settings` field.
|
||||||
|
|
||||||
|
## Data model
|
||||||
|
|
||||||
|
**`community.classified`** — the listing itself:
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `title` | Char | required |
|
||||||
|
| `category` | Selection | for_sale / housing / services / jobs / other |
|
||||||
|
| `description` | Html | |
|
||||||
|
| `image_ids` | One2many → `community.classified.image` | max 3, enforced by a `@api.constrains` |
|
||||||
|
| `contact_method` / `contact_email` / `contact_phone` | | |
|
||||||
|
| `poster_partner_id` | Many2one res.partner | readonly, set by the controller on create |
|
||||||
|
| `post_date` | Datetime | readonly, defaults to now |
|
||||||
|
| `expiry_date` | Date | readonly, computed on create/publish/renew |
|
||||||
|
| `state` | Selection | pending_review / published / expired / rejected |
|
||||||
|
| `admin_notes` | Text | |
|
||||||
|
| `view_count` | Integer | incremented via `sudo()` on each public detail-page view |
|
||||||
|
|
||||||
|
**`community.classified.image`** — `classified_id` (required, cascade
|
||||||
|
delete), `sequence`, `image` (Binary, `attachment=True`).
|
||||||
|
|
||||||
|
## Public routes
|
||||||
|
|
||||||
|
| Route | Auth | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET /classifieds` | public | list, `?category=` filter, only `state=published` |
|
||||||
|
| `GET /classifieds/<id>` | public | detail page, 404s unless published |
|
||||||
|
| `GET`/`POST /classifieds/new` | portal login required | membership check if applicable (see Dependencies) |
|
||||||
|
| `GET /classifieds/my` | portal login required | poster's own listings, any state |
|
||||||
|
| `GET /classifieds/<id>/renew` | portal login required | only works if the requesting partner is the poster |
|
||||||
|
|
||||||
|
Login-required routes use `auth='public'` plus a manual redirect check
|
||||||
|
(`_redirect_to_login_if_public`), not `auth='user'` — a documented
|
||||||
|
workaround for an Odoo 19 bug where `auth='user'` + `website=True` throws
|
||||||
|
a raw 500 instead of redirecting to login in some builds. Worth knowing
|
||||||
|
before "simplifying" that pattern back to `auth='user'`.
|
||||||
|
|
||||||
|
## Background jobs
|
||||||
|
|
||||||
|
Two daily `ir.cron` entries (`data/ir_cron.xml`):
|
||||||
|
- **Expire Listings** — flips `published` past `expiry_date` to `expired`.
|
||||||
|
- **Send Expiry Warnings** — emails the poster when `expiry_date` is
|
||||||
|
exactly 7 days out.
|
||||||
|
|
||||||
|
## Email templates
|
||||||
|
|
||||||
|
`data/mail_templates.xml`: one to moderators on every new submission
|
||||||
|
(`mail_template_new_submission`), one to the poster on approaching expiry
|
||||||
|
(`mail_template_expiry_warning`). Both are looked up via
|
||||||
|
`env.ref(..., raise_if_not_found=False)` — if you strip the data file,
|
||||||
|
the module still runs, it just silently skips sending.
|
||||||
|
|
||||||
|
## Known gaps to fix before shipping this elsewhere
|
||||||
|
|
||||||
|
- **`static/description/banner.png` is referenced in the manifest's
|
||||||
|
`images` key but the file doesn't exist.** The App Store / Apps-list
|
||||||
|
listing will show a broken image until a real banner asset is added.
|
||||||
|
- **`demo/` is empty** (just a `.gitkeep`) — no demo data ships with the
|
||||||
|
module.
|
||||||
|
- Manifest `price`/`currency` are placeholder zero values (`0.00 USD`) —
|
||||||
|
set real pricing before listing it for sale.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`tests/test_classifieds.py` covers: new listing starts pending review,
|
||||||
|
publish makes it visible, reject, the expiry cron, renew resets expiry,
|
||||||
|
the 3-image max constraint, and the configurable expiry-days setting.
|
||||||
|
Run with `--test-enable --test-tags /community_classifieds` on install.
|
||||||
Loading…
x
Reference in New Issue
Block a user