angee.platform.models
Source models for the platform addon.
The platform console reflects the runtime the composer already built. Addon is that reflection made persistent: one row per composed/available addon, converged from the app registry after migrate — the same reconcile Django runs for django_content_type / auth_permission (see signals.py). It is therefore authoritatively derived, never authored: settings.yaml (enabled) and uv.lock (available) remain the source of truth; this table is the queryable mirror that backs the console.
Scope is deliberately local: which addons are available (installed bundles + local addon.toml) and which lifecycle state each is in — enabled (composed), disabled (available but not composed), or removed (gone from the env, the row kept as history; the reconcile marks state, it never deletes). The remote marketplace — addons known from VCS provenance but not materialised — is not here; the platform_integrate_vcs addon extends Addon with that tier.
PlatformExplorer stays a table-less REBAC type anchor for the schema explorer.
AddonManager
class AddonManager(AngeeManager)Manager owning the reflection table's reconcile and the install/uninstall flow.
install
def install(name: str) -> InstallResultInstall an addon: validate it, edit settings.yaml, then reflect pending.
Refuses a name no installed bundle or local addon provides — a marketplace (REMOTE) row is known but not materialised, so adding it to INSTALLED_APPS would brick the next boot — and otherwise delegates the settings.yaml edit to the :class:~angee.platform.installer.AddonInstaller and re-runs the reconcile so the board shows the new pending state at once (the addon itself composes on the next angee dev boot).
uninstall
def uninstall(name: str) -> InstallResultUninstall an addon, refusing a forced (depended-on) one (Odoo "not uninstallable").
The forced policy lives on the reflected row (:meth:Addon.uninstall_block_reason); this only resolves the row, relays its refusal, and otherwise delegates the settings.yaml edit and re-runs the reconcile so the board reflects the queued uninstall immediately.
reconcile_from_registry
def reconcile_from_registry(using: str) -> NoneConverge the table to the composed app graph + available addons.
A state reconcile, never a delete: an addon that leaves the project is marked REMOVED — its row, and so its history, is kept — rather than pruned. Scoped to the tier this reconcile owns (installed/local); rows of other tiers (the VCS marketplace platform_integrate_vcs contributes) are left untouched. Each present addon's row is a full overwrite so a state flip (enabled ↔ disabled) resets every reflected field. Runs under the caller's system_context (see signals.py); routed through using and wrapped in one transaction like the sibling source reconciles, so a mid-loop failure never leaves the table half-converged.
Addon
class Addon(AngeeModel)The composed-runtime addon registry — local reflection, system-synced.
Identity is name (e.g. angee.iam) — the stable key the console cross-links on — not a sqid.
Kind
class Kind(models.TextChoices)Whether the project chose this addon (root) or it came in as a dependency.
Source
class Source(models.TextChoices)Where the available addon resolved from.
INSTALLED
an installed bundle's entry point (uv.lock)
LOCAL
an addon.toml under ANGEE_ADDON_DIRS
REMOTE
known from a VCS source, not materialised (platform_integrate_vcs)
State
class State(models.TextChoices)The addon's lifecycle in this project — reconciled, never deleted.
ENABLED
composed into the app graph
DISABLED
available/known but not composed
REMOVED
was present, now gone from the env (kept as history)
Meta
class Meta()Django model options.
__str__
def __str__() -> strReturn the addon name for Django displays.
uninstall_block_reason
@property
def uninstall_block_reason() -> strReturn why this addon cannot be uninstalled, or "" when it may be.
A forced (depended-on) addon — framework core, or anything another installed addon needs — cannot be uninstalled (Odoo's "not uninstallable"). The policy and its wording live on the row that carries forced (derived from the composer's dependency closure); the uninstall flow only relays this.
PlatformExplorer
class PlatformExplorer(AngeeModel)Table-less REBAC type anchor for the platform introspection surface.