How to Add a Model

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

Table of Contents

  1. Overview
  2. Step 1: Pick a Base Model
  3. Step 2: Create the Model File
  4. Step 3: Declare Fields
  5. Step 4: Re-export in models/__init__.py
  6. Step 5: Migrate
  7. Common Pitfalls
  8. Change History
  9. Related Guides

Overview

Adding a model is the first step of every feature. This guide lists the atomic steps in order, so each one can be done and reviewed on its own. The complete chain after the model is covered in Developer Workflow.

flowchart TD
    PICK[Pick a base model] --> FILE[Create models/<name>.py]
    FILE --> FIELDS[Declare fields per conventions]
    FIELDS --> EXPORT[Re-export in models/__init__.py]
    EXPORT --> MIGRATE[makemigrations + migrate]
    MIGRATE --> NEXT[Add views, menus, URLs]

Step 1: Pick a Base Model

Choose the base that matches the record's role. The decision tree is in Base Models & Model Types.

Record Base
Master data with a business number EnezaMaster
One-to-one detail of a master EnezaMasterDetails
Document header with numbering EnezaDocument
Document header without numbering EnezaTransactionalTable
Document line item EnezaDocumentLines
Setup/configuration row EnezaSetup
Single-instance parameter EnezaParameter
Anything else EnezaGeneral

Every model that inherits one of these bases also supports lifecycle hooks. Add @hook methods directly to the model; no additional base or mixin is needed. See Lifecycle Hooks (Events).


Step 2: Create the Model File

Create one file per model under the app's models/ package.

text
accountpayable/models/vendorgroup.py
python
# accountpayable/models/vendorgroup.py
from django.db import models

from enezaframework.models import EnezaSetup


class VendorGroup(EnezaSetup):
    ...

Use one file per model and name the file after the model in lowercase (vendorgroup.py for VendorGroup). See Standardized App Structure.


Step 3: Declare Fields

Declare each field on its own line and follow the mandatory conventions in Model Field Conventions: one field per line, a verbose_name on every field, and a multi-line field call closed on its own aligned line.

python
# accountpayable/models/vendorgroup.py
from django.db import models

from enezaframework.models import EnezaSetup


class VendorGroup(EnezaSetup):
    code = models.CharField(
        max_length=50,
        unique=True,
        verbose_name="Code",
    )
    description = models.CharField(
        max_length=255,
        verbose_name="Description",
    )
    vendorpostinggroup = models.ForeignKey(
        "VendorPostingGroup",
        on_delete=models.PROTECT,
        verbose_name="Vendor Posting Group",
    )

    class Meta:
        verbose_name = "Vendor Group"
        verbose_name_plural = "Vendor Groups"

    def __str__(self):
        return self.description

entity is added by the base class; do not declare it yourself.


Step 4: Re-export in models/__init__.py

The framework and other apps import from the models package root, so every model must be re-exported.

python
# accountpayable/models/__init__.py
from .vendorgroup import VendorGroup
from .vendorpostinggroup import VendorPostingGroup

Without this line, from accountpayable.models import VendorGroup fails even though the class exists. This is the same rule the framework uses for its own package.


Step 5: Migrate

bash
python manage.py makemigrations accountpayable
python manage.py migrate

Inspect the generated migration before applying it. For models that inherit a framework base, the entity, audit, UUID primary key, and history table come from the base.


Common Pitfalls

  • Forgetting the re-export. The class is not importable from the package root until it is in models/__init__.py.
  • Assuming EnezaModel has entity or is_active. EnezaModel has neither; each concrete base adds entity, and is_active is opt-in per model.
  • Using the singular EnezaMasterDetail. It is not re-exported; use EnezaMasterDetails.
  • Expecting EnezaTransactionalTable to auto-number. It has no SequenceMixin; use EnezaDocument when you need numbering.
  • Skipping verbose_name. Every field must set it.

The user is solely responsible for testing and validating the model (for example by running the migrations in their environment); the developer/agent does not run the application.


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 add-model steps or the models/__init__.py re-export rule appear in the changelog for the 0.2.7–0.2.19 window.