Base Models & Model Types
Developer guide for the Enezaframework. Part of the architecture series.
Table of Contents
- Overview
- Base Model Reference
- Choosing a Base Model
- The
EntityModel - How to Add a Model
- Model Field Conventions
- 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.
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. |
updated_at |
DateTimeField |
auto_now=True. |
created_by |
ForeignKey to enezacommon.User |
Set on first save. |
modified_by |
ForeignKey to enezacommon.User |
Updated on every save. |
history |
HistoricalRecords |
simple_history tracking. |
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.
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
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). - 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
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; do not rely on an external row being created automatically without confirming the receiver exists.
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.
How to Add a Model
- Create a file in the app's
models/package, for exampleaccountpayable/models/vendortable.py. - Import the base model from
enezaframework.modelsand inherit from it. - Declare fields following the project field conventions below.
- Re-export the model in
models/__init__.py. - Generate and apply migrations.
# accountpayable/models/vendortable.py
from django.db import models
from enezaframework.models import EnezaMaster
from enezacommon.models import VendorType
class VendorTable(EnezaMaster):
vendorno = models.CharField(
max_length=50,
unique=True,
verbose_name="Vendor No.",
)
searchname = models.CharField(
max_length=255,
verbose_name="Search Name",
)
vendortype = models.CharField(
max_length=50,
choices=VendorType.choices,
verbose_name="Vendor Type",
)
vendorgroup = models.ForeignKey(
"VendorGroup",
on_delete=models.PROTECT,
verbose_name="Vendor Group",
)
blocked = models.BooleanField(
default=False,
verbose_name="Blocked",
)
class Meta:
verbose_name = "Vendor"
verbose_name_plural = "Vendors"
def __str__(self):
return self.searchname
# accountpayable/models/__init__.py
from .vendortable import VendorTable
from .vendorgroup import VendorGroup
from .vendortabledetails import VendorTableDetails
python manage.py makemigrations accountpayable
python manage.py migrate
Next, add the views in CRUD Base Views and the navigation entries in Menu & Navigation Configuration.
Model Field Conventions
These conventions are mandatory for this project:
- One field per line. Never declare multiple fields on the same line.
- Always set
verbose_name. Every field must include averbose_namekeyword argument. - Multi-line fields close on their own line. When a field declaration spans multiple lines (for example a
ForeignKeywithrelated_name,on_delete,help_text), the closing parenthesis goes on its own line, aligned with the start of the field declaration.
vendorgroup = models.ForeignKey(
"VendorGroup",
on_delete=models.PROTECT,
related_name="vendors",
verbose_name="Vendor Group",
)