Base Models & Model Types

Developer guide for the Enezaframework. Part of the Developer documentation.

Table of Contents

  1. Overview
  2. Base Model Reference
  3. Choosing a Base Model
  4. The Entity Model
  5. Package Exports
  6. Change History
  7. Related Guides

Overview

Enezaframework provides abstract base models that carry the fields and behaviors every business record needs: primary key, timestamps, audit user tracking, history, and (for most types) an entity foreign key for multi-tenant separation. Application models inherit from one of these instead of models.Model directly.

All base models are abstract; inheriting from them does not create a table by itself. The framework uses simple_history for change tracking.

All models under EnezaModel also inherit the lifecycle-hook system, so @hook methods work without adding another base class or changing the model's inheritance. See Lifecycle Hooks (Events).

erDiagram
    EnezaModel ||--o{ EnezaMaster : "abstract parent"
    EnezaModel ||--o{ EnezaMasterDetails : "abstract parent"
    EnezaModel ||--o{ EnezaGeneral : "abstract parent"
    EnezaModel ||--o{ EnezaDocument : "abstract parent"
    EnezaModel ||--o{ EnezaDocumentLines : "abstract parent"
    EnezaModel ||--o{ EnezaTransactionalTable : "abstract parent"
    EnezaModel ||--o{ EnezaSetup : "abstract parent"
    EnezaModel ||--o{ EnezaParameter : "abstract parent"
    EnezaModel ||--o{ Entity : "concrete parent"

Base Model Reference

EnezaModel

The most fundamental base model. Defined in enezaframework/models/enezaModel.py.

Field Type Notes
id UUIDField Primary key, generated automatically.
created_at DateTimeField auto_now_add=True, nullable.
updated_at DateTimeField auto_now=True, nullable; refreshed on save, including update_fields.
created_by ForeignKey to enezacommon.User Set automatically from the current request user on first save.
modified_by ForeignKey to enezacommon.User Set automatically from the current request user on every save.
history HistoricalRecords simple_history tracking with the acting user and action.

Note: the audit field is modified_by, not updated_by. EnezaModel does not define entity or is_active. Each concrete base class adds its own entity foreign key. Do not assume is_active exists on a model unless that model defines it.

Audit fields and history

CurrentUserMiddleware stores request.user in thread-local storage. EnezaModel.save() uses that user to populate the audit fields:

  • On create, created_by and modified_by are set to the current user.
  • On update, created_by is preserved and modified_by is set to the current user.
  • On delete, the acting user is recorded as the history actor even though no update is written to the row.
  • When save(update_fields=[...]) is used, modified_by and updated_at are added to the update so the audit values are persisted.
  • simple_history reads the same acting user for history_user, so the audit trail shows the correct Changed by value. The Action column comes from history_type (+ created, ~ changed, - deleted).
  • Workflow approval, rejection, changes-requested, resubmission, status-change, and completion transactions are merged into the same audit trail and shown with their workflow action label.
  • If there is no current user, existing audit values are preserved. Background jobs can set the actor explicitly:
python
from enezaframework.current_user import clear_current_user, set_current_user

set_current_user(user)
try:
    record.save()
finally:
    clear_current_user()

The returned history entries are available through record.history and the audit-trail view.

Bulk operations such as bulk_create(), bulk_update(), queryset update(), and queryset delete() bypass model save()/delete(), so they do not populate audit fields or create history entries.

Convenience helpers on EnezaModel:

python
class MyModel(EnezaModel):
    ...

MyModel.search("acme", "name", "code")   # case-insensitive search across fields
instance.get_age()                        # timedelta since created_at
instance.was_updated_recently(days=1)     # bool

EnezaModel inherits EnezaEvents, so lifecycle hooks are available immediately:

python
from enezaframework import AFTER_UPDATE, hook
from enezaframework.models import EnezaGeneral


class Customer(EnezaGeneral):
    ...

    @hook(AFTER_UPDATE)
    def on_customer_update(self):
        ...

Use EnezaEvents only for models that do not inherit an Enezaframework base. Use EnezaEventsMixin when the model already inherits another Django base or mixin. See Lifecycle Hooks (Events).

EnezaMaster

class EnezaMaster(SequenceMixin, EnezaModel) — from enezaframework/models/enezamaster.py.

  • Adds entity (ForeignKey to Entity, on_delete=PROTECT, nullable).
  • Includes SequenceMixin, so it can generate sequential business numbers (for example CUST0001, VEND0002). See Document Sequences & Numbering.
  • Intended for master records referenced by transactions: Customer, Vendor, Item, BankAccount.
  • Entity specific: yes.
  • Custom manager: EnezaManager.

EnezaMasterDetails

class EnezaMasterDetails(EnezaModel) — from enezaframework/models/enezamaster.py.

  • Adds entity.
  • Intended for one-to-one detail tables linked to a master record, keeping the main table lean.
  • Entity specific: yes.

EnezaGeneral

class EnezaGeneral(EnezaModel) — from enezaframework/models/enezamaster.py.

  • Adds entity.
  • A general-purpose abstract base for records that are neither master data, documents, nor setup.

EnezaDocument

class EnezaDocument(SequenceMixin, EnezaModel) — from enezaframework/models/enezadocuments.py.

  • Adds entity and document numbering via SequenceMixin.
  • Intended for the header of transactional documents such as SalesInvoiceHeader, PurchaseOrderHeader, JournalBatch.
  • Entity specific: yes.

EnezaDocumentLines

class EnezaDocumentLines(EnezaModel) — from enezaframework/models/enezadocuments.py.

  • Adds entity.
  • Intended for line items belonging to a document header, typically one-to-many via a ForeignKey back to the header.
  • Entity specific: yes.

EnezaTransactionalTable

class EnezaTransactionalTable(EnezaModel) — from enezaframework/models/enezabtransactional.py.

  • Adds entity only. It does not include SequenceMixin.
  • Use it for a transactional header when you do not want framework-managed numbering; use EnezaDocument when you do.

EnezaSetup

class EnezaSetup(EnezaModel) — from enezaframework/models/enezacsetups.py.

  • Adds entity.
  • Intended for configuration/setup records that control system behavior: LedgerSetup, PaymentTerms, PostingGroup, Currency.
  • Entity specific: often yes, some setups may be global.

EnezaParameter

class EnezaParameter(EnezaModel) — from enezaframework/models/enezacsetups.py.

  • Adds entity.
  • Intended for single-instance parameter models (typically one record per entity), often edited through a parameter view. See CRUD Base Views.

EnezaMasterDetail (singular)

class EnezaMasterDetail(EnezaModel) exists at enezaframework/models/enezamasterdetail.py but is not re-exported from enezaframework/models/__init__.py, so from enezaframework.models import EnezaMasterDetail fails on the package root. Prefer EnezaMasterDetails (re-exported) unless you import the module directly.

The architecture reference describes a framework signal that auto-creates the detail row for EnezaMasterDetail children. Verify this signal in the current source before depending on it.


Choosing a Base Model

flowchart TD
    START[What kind of record?] --> SETUP{Configuration<br/>or settings?}
    SETUP -->|Single instance per entity| PARAM[EnezaParameter]
    SETUP -->|Many setup rows| ESETUP[EnezaSetup]
    SETUP -->|No| DOC{Part of a document?}
    DOC -->|Header, needs numbering| EDOC[EnezaDocument]
    DOC -->|Header, no numbering| ETRAN[EnezaTransactionalTable]
    DOC -->|Line items| ELINES[EnezaDocumentLines]
    DOC -->|No| MASTER{Master data with<br/>a business number?}
    MASTER -->|Yes| EMASTER[EnezaMaster]
    MASTER -->|One-to-one detail| EDETAILS[EnezaMasterDetails]
    MASTER -->|No| EGEN[EnezaGeneral]

The Entity Model

Entity is the central model representing a legal entity (company, organization, or individual) for which records are kept. Most records link to an Entity, which is how the ERP separates data per company/branch.

  • Defined in enezaframework/models/Entity.py (framework, not the common app).
  • It is a concrete model: class Entity(PartyModelMixin, EnezaModel).
  • Database table: entity.
  • Key fields include name, company_code, full_legal_name, slug, logo, document_logo, tax_registration_number, default_currency, and tax-related fields.

Because Entity inherits from EnezaModel, it uses the same UUID primary key and audit/history fields as everything else.


Package Exports

Only names re-exported from enezaframework/models/__init__.py are importable from the package root:

python
from .enezaModel import EnezaModel
from .enezabtransactional import *
from .enezacsetups import EnezaSetup
from .enezamaster import EnezaMaster, EnezaMasterDetails, EnezaGeneral
from .choices import *
from .enezadocuments import *
from .Entity import *
from .configurable_image_field import ConfigurableImageField, ConfigurableMultipleImageField
from .ezaparty import PartyModelMixin

The event base is also re-exported from enezaframework.models and the package root:

python
from enezaframework.models import EnezaEvents
from enezaframework import EnezaEvents, EnezaEventsMixin, hook

If a model cannot be imported from enezaframework.models, check whether it is re-exported here before importing it from its module path.


Change History

Dated changes that affect this guide, taken from docs/Release note/CHANGELOG.md and the release notes. For the complete history, see the changelog.

No dated changes to the base model set (EnezaModel, EnezaMaster, EnezaMasterDetails, EnezaGeneral, EnezaDocument, EnezaDocumentLines, EnezaTransactionalTable, EnezaSetup, EnezaParameter, Entity) appear in the changelog for the 0.2.7–0.2.19 window. Sequence-related model changes are tracked in Document Sequences & Numbering.