Party System Quick Start

Developer guide for the Enezaframework party system. Part of the Party System series.

Table of Contents

  1. Overview
  2. Step 1 — Define the Model
  3. Step 2 — Re-export the Model
  4. Step 3 — Define the CRUD Views
  5. Step 4 — Register URLs
  6. Step 5 — Add the Menu Entry
  7. Step 6 — Migrate
  8. What the Detail Page Shows
  9. Related Guides

Overview

A party-based entity needs the same pieces as any CRUD feature, plus the PartyModelMixin base class. The framework supplies the rest: party creation, profile creation, and the detail-page inlines.

You will build: a Customer model with a party type, standard list/detail/create/delete views, a URL namespace, and a menu entry — with party data handled automatically.

flowchart LR
    M[1. Model<br/>PartyModelMixin] --> R[2. Re-export<br/>models/__init__.py]
    R --> V[3. CRUD views<br/>detail_ui_view]
    V --> U[4. URLs<br/>auto_url_register]
    U --> N[5. Menu<br/>apps.py]
    N --> G[6. Migrate]
    G --> D[Working party-based pages]

Step 1 — Define the Model

python
# myapp/models/customer.py
from django.db import models
from enezaframework.models import EnezaMaster, PartyModelMixin
from enezacommon.models.choices import PartyCategory


class Customer(PartyModelMixin, EnezaMaster):
    customer_no = models.CharField(
        max_length=50,
        verbose_name="Customer No.",
    )
    customer_type = models.CharField(
        max_length=5,
        choices=PartyCategory.choices,
        verbose_name="Customer Type",
    )
    name = models.CharField(
        max_length=255,
        verbose_name="Name",
    )
    email = models.CharField(
        max_length=255,
        blank=True,
        null=True,
        verbose_name="Email",
    )

    sequence_field_name = 'customer_no'
    document_name = 'CUSTOMER'

    @property
    def party_category(self):
        return self.customer_type

    def get_party_defaults(self):
        defaults = super().get_party_defaults()
        defaults['display_name'] = self.name or self.customer_no or 'Customer'
        return defaults

    def __str__(self):
        if self.party and self.party.display_name:
            return self.party.display_name
        return self.customer_no or ''

    class Meta:
        db_table = 'customer'
        verbose_name = "Customer"
        verbose_name_plural = "Customers"

Details of the mixin contract are in PartyModelMixin & Model Integration.


Step 2 — Re-export the Model

python
# myapp/models/__init__.py
from .customer import Customer

Without this re-export, from myapp.models import Customer fails. See Standardized App Structure.


Step 3 — Define the CRUD Views

python
# myapp/views/customer_views.py
from enezaframework.views import (
    BaseListView, BaseDetailView, BaseCreateView, BaseDeleteView,
)
from enezaframework.utils import auto_url_register
from myapp.models import Customer


@auto_url_register
class CustomerListView(BaseListView):
    model = Customer

    def set_list_fields(self):
        return ['customer_no', 'party', 'customer_type', 'name']

    def set_search_fields(self):
        return ['customer_no', 'name']


@auto_url_register
class CustomerDetailView(BaseDetailView):
    model = Customer

    def detail_ui_view(self):
        return [
            {
                'name': 'Basic Information',
                'icon': 'Home',
                'Fieldcolumns': [
                    {'fields': ['customer_no', 'customer_type']},
                    {'fields': ['name', 'email']},
                ],
            },
        ]


@auto_url_register
class CustomerCreateView(BaseCreateView):
    model = Customer

    def detail_ui_view(self):
        return [
            {
                'name': 'Basic Information',
                'icon': 'Home',
                'Fieldcolumns': [
                    {'fields': ['customer_no', 'customer_type']},
                    {'fields': ['name', 'email']},
                ],
            },
        ]


@auto_url_register
class CustomerDeleteView(BaseDeleteView):
    model = Customer

Do not add 'party' to Fieldcolumns. It is auto-hidden from forms, and the party inlines are injected separately. See Party View Layer.


Step 4 — Register URLs

python
# myapp/urls.py
from django.urls import path

from . import views  # noqa: F401  (imports the view modules / re-exports)
from enezaframework.utils import get_registered_urls

app_name = "myapp"

dynamic_urls = get_registered_urls(app_name)

urlpatterns = [
    *dynamic_urls,
]

This yields the standard names (myapp:customer_list, myapp:customer_detail, myapp:customer_create, myapp:customer_delete). See URL Auto-Registration.


Step 5 — Add the Menu Entry

python
# myapp/apps.py
from django.apps import AppConfig
from enezaframework.utils import load_icon


class MyappConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "myapp"

    menus = [
        {
            "fasttab": "my_masterdata",
            "name": "Master Data",
            "icon": load_icon("users_icon"),
            "menuitems": [
                {
                    "url": f"{name}:customer_list",
                    "name": "Customers",
                    "icon": load_icon("users_icon"),
                },
            ],
        },
    ]

See Menu & Navigation Configuration.


Step 6 — Migrate

bash
python manage.py makemigrations myapp
python manage.py migrate

Django adds the party_id column automatically because the mixin contributes the field. For converting an existing model, follow Party System Migration instead.


What the Detail Page Shows

After saving the first record, the party and its profile exist, and the detail page shows the party inlines without any view override.

sequenceDiagram
    actor U as User
    participant D as CustomerDetailView
    participant B as BaseDetailView
    participant P as Party inlines

    U->>D: GET /customer/{pk}/
    D->>B: get_context_data()
    B->>B: party_object = object.party
    B->>B: inject Address + ContactInformation + Person/Business
    B-->>D: fieldtabs + inline_related_object + party_object
    D-->>U: detail page with party inline tabs
    U->>P: HTMX loads an inline (main_object_pk = party.pk)
    P-->>U: inline partial
Party type Inlines shown
PRS Address, Contact Information, Personal Details
ORG Address, Contact Information, Business Details