Base Models & Model Types
Developer guide for the Enezaframework. Part of the Developer documentation.
Table of Contents
- Overview
- Base Model Reference
- Choosing a Base Model
- The
EntityModel - Package Exports
- Change History
- 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, notupdated_by.EnezaModeldoes not defineentityoris_active. Each concrete base class adds its ownentityforeign key. Do not assumeis_activeexists 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_byandmodified_byare set to the current user. - On update,
created_byis preserved andmodified_byis 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_byandupdated_atare added to the update so the audit values are persisted. simple_historyreads the same acting user forhistory_user, so the audit trail shows the correct Changed by value. The Action column comes fromhistory_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:
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:
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:
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 toEntity,on_delete=PROTECT, nullable). - Includes
SequenceMixin, so it can generate sequential business numbers (for exampleCUST0001,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
entityand document numbering viaSequenceMixin. - 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
entityonly. It does not includeSequenceMixin. - Use it for a transactional header when you do not want framework-managed numbering; use
EnezaDocumentwhen 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
EnezaMasterDetailchildren. 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:
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:
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.