Skip to Content

Digital Assets developer guide

This addon records infrastructure and its relationships in Odoo 18 Community. It does not provision hosts, execute database operations, or update DNS. Keep credentials outside the inventory. Examples and regression fixtures use synthetic data only.

Code map and loading order

  • models/pmis_asset_registration.py defines the shared abstract registration contract. Import it before the concrete models in models/__init__.py.

  • models/pmis_host.py, pmis_service.py, pmis_service_database.py, and pmis_domain.py own persistent inventory, validation, and navigation.

  • models/pmis_service_application.py owns the software catalog and registration presets; pmis_service_stage.py owns the shared configurable stages.

  • wizard/pmis_asset_registration_wizard.py collects inputs and creates the requested inventory graph. Importing the wizard package is required for registry discovery.

  • views/pmis_asset_registration_views.xml extends the original asset views with registration, lifecycle, archive, and contextual creation controls.

  • security/ir.model.access.csv grants model operations. The security XML defines the asset roles and retains the legacy role. No company record rules are defined.

  • tests/ contains pytest regressions. The runner installs the addon; tests do not install modules or create databases themselves.

The manifest loads the module category before groups and ACLs. Original views precede their inherited views. Wizard views and the registration action must load before asset buttons and menus that reference that action.

Relationships and ownership

The primary UI follows host, instance, database, and website domain order. views/pmis_inventory_workflow_views.xml applies last at priority 100 and replaces the everyday form layouts after the existing registration extensions. Stable base view IDs and optional historical fields remain available. Advanced tabs contain technical metadata and registration controls.

Domain service_id is the single stored instance relationship. It is computed from database_id.service_id for database-backed domains and can be selected directly for database-less applications. The ORM constraint rejects attempts to override a database-backed domain's instance, including draft imports. Domain and database host_id are stored related fields; reassignment uses normal ORM dependency propagation. New database and domain company defaults follow their selected parent unless ownership is explicitly supplied. Existing ownership is not rewritten. The Odoo catalog seed is noupdate and supplies new instance defaults without replacing existing applications.

A host has many services. A service represents one deployed application instance and references a software catalog entry. Its company is computed from the host; catalog application type supplies the service classification. A database belongs to a consuming application service, not a database engine host.

A domain can reference a parent domain, a database, its instance, and a serving web service. Parked domains need none of those links. A database is authoritative for its domains' instance. Web-server and application services can run on different hosts, but registration requires their company relationships to agree.

Service domain_ids uses the canonical service_id inverse. Host domain_ids additionally includes domains served by its web services, deduplicated with a recordset union. Archived domains retain their instance relationship and participate in dependency recomputation when their database moves.

The 18.0.1.2.0 pre-migration uses SQL before the new registry exists to preserve standalone routes, infer unique consumers for unassigned databases, and normalize all database-backed routes. Ambiguous consumers stop the transaction before writes. The post-migration drops retired columns after models and views are updated. Temporary-table regression fixtures isolate old schemas from the installed models and verify data preservation, ambiguity rejection, and retry safety.

Registration extension contract

pmis.asset.registration declares shared identity fields, registration state, lifecycle, archiving, and a computed checklist. Concrete models retain their own name field types, company defaults, tracking, and computed ownership definitions.

Extend these hooks when adding a registration requirement:

  • _registration_dependencies() returns dependency paths for the computed checklist. Extend super() and include fields read by the new checks.

  • _registration_issues() runs on one asset and returns translated messages. It must describe missing or inconsistent information without writing records.

  • _registration_link_issues(links) checks optional links for company consistency. It does not grant access and does not require optional links to exist.

  • _registration_dependents() returns recordsets that need revalidation after ownership or relationship edits. It does not recursively traverse itself.

The mixin validates names after normal create defaults have been applied. Drafts may retain incomplete information; registered records must satisfy their checks. write(vals) revalidates the edited records and, for the listed relationship fields, registered dependents. Extend that trigger list when introducing another relationship that can invalidate a dependent. Application classification edits also revalidate their affected services and directly linked domains.

Imports and API writes follow the same model checks as forms. action_register uses the ordinary write path. Reopening registration returns a record to draft before required information is removed. Validation errors rely on the surrounding Odoo transaction or savepoint to roll back changes; do not catch and ignore them after a partial write.

Wizard transaction boundaries

Details, Review, and Back only change the transient wizard. Context defaults preserve the parent selected by a contextual Add action. Company onchanges clear incompatible selections but must retain matching defaults during initial form load. Hidden fields can retain old values, so _existing_links selects only relations used by the current workflow.

_check_details validates the selected company, access to existing links, archive status, internal assignment, names, and compatible component types. UI domains and visibility are conveniences; direct callers receive the same checks. Duplicate-name warnings include archived records and remain advisory.

Register and Save Draft each wrap _create_assets in a savepoint. Records are created in host, service, database, then domain order. Register validates all newly created assets after their links exist. Any error rolls back the entire selection. Save Draft defers completeness checks, but still requires names and valid wizard inputs. Existing dependencies are never silently reassigned or registered.

result_reference reopens a completed result on a subsequent confirmation of the same wizard. It is not a concurrency lock or a general duplicate-prevention mechanism. The wizard creates at most one record of each supported type; further assets are added through their parent forms.

State, archiving, and permissions

Registration state records completeness. Lifecycle records operational status. Shared stages support configurable process grouping. active controls everyday visibility. None of these values automatically changes the others or provisions infrastructure.

An active-only write deliberately skips registration recertification so historical records can be archived and restored. No archive operation cascades to related records. Standard Odoo active_test behavior hides archived records in normal searches while existing many-to-one references remain available for history. The registration wizard explicitly rejects archived dependencies.

Digital Assets User can read, create, and update inventory and read configuration. Digital Assets Manager can also delete inventory and maintain configuration. ACLs are additive: existing Sales permissions remain in force, including their legacy deletion rights on hosts, services, and domains. Do not treat hidden menus or company fields as security isolation. There are no addon company record rules; same-company registration checks are data-consistency rules, not access rules.

Host copying retains the existing follower and service-copy policy. Supplying service_ids defaults bypasses the additional map_service step. Do not assume this helper clones a complete database/domain graph or provisions a new host.

Validation and maintenance

tests/conftest.py opens a fresh cursor and superuser environment per test, then rolls back and closes it. Role tests explicitly switch to restricted users. Form tests exercise initial defaults and onchanges; they do not replace browser testing.

Use the configured suite interpreter and repository tools. From the repository root, the check-only static gate is:

/srv/pmiverse/_pmiconfig/scripts/test/run_lint_addon.sh \
    --addon pmis_digital_assets --check

Run direct Ruff as well: the wrapper checks imports and formatting but does not exercise every configured Ruff rule. PyCharm inspections complement those checks, particularly for Odoo field references and method signatures.

For an authorized disposable-database test run, scope the coverage runner to this addon and use a task-specific disposable prefix:

/srv/pmiverse/_pmiconfig/scripts/test/run_pytest_repo.sh \
    --addons /srv/pmiverse/_pmisuite18/repos/pmiverse/stock-accounting/pmis_digital_assets \
    --db-prefix test_digital_assets_review_ --parallel 1 -- \
    -q --maxfail=1 --cov-fail-under=100

The runner creates and removes its disposable database. Never target an existing application database. Coverage reports live under the runner's timestamped log directory; a scoped run must not publish repository-wide badges.

Maintain README.rst and this guide directly. Translation catalogs remain the source of localized text; do not rewrite generated POT files to silence an IDE warning. Preserve existing license and copyright policy. Model, view, or ACL changes require the normal addon upgrade process before they appear in a running installation.