TNCSC_Odoo/docs/community_classifieds_handover.md
metatroncubeswdev b0998f81a7
Some checks failed
CI / Brand-leak check (push) Has been cancelled
CI / flake8 / pylint-odoo / manifest completeness (push) Has been cancelled
CI / Install all modules with --test-enable (push) Has been cancelled
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.
2026-08-24 02:58:54 -04:00

7.6 KiB

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):

'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:

<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.imageclassified_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.