Party System Vendor Example

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

Table of Contents

  1. Overview
  2. The Model
  3. The Views
  4. URLs and Menu
  5. The Full Chain
  6. Related Guides

Overview

This is a complete, party-based VendorTable in an accountpayable app. It demonstrates the whole pattern in one place: a master model with PartyModelMixin, a normalized vendortype party category, forward-compatible defaults, and standard CRUD views with no party-specific overrides.


The Model

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


class VendorTable(PartyModelMixin, EnezaMaster):
    vendorno = models.CharField(
        max_length=50,
        verbose_name="Vendor No.",
    )
    searchname = models.CharField(
        max_length=255,
        verbose_name="Search Name",
    )
    vendortype = models.CharField(
        max_length=5,
        choices=PartyCategory.choices,
        verbose_name="Vendor Type",
    )
    blocked = models.BooleanField(
        default=False,
        verbose_name="Blocked",
    )

    sequence_field_name = 'vendorno'
    document_name = 'VENDOR'

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

    def get_party_defaults(self):
        defaults = super().get_party_defaults()
        defaults['display_name'] = self.searchname or self.vendorno or 'Vendor'
        defaults['is_active'] = not self.blocked
        return defaults

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

    class Meta:
        db_table = 'vendor'
        verbose_name = "Vendor"
        verbose_name_plural = "Vendors"
python
# accountpayable/models/__init__.py
from .vendortable import VendorTable

Note how get_party_defaults() mirrors the business blocked flag into the party's is_active, so disabling a vendor also deactivates its party.


The Views

python
# accountpayable/views/vendor_views.py
from enezaframework.views import (
    BaseListView, BaseDetailView, BaseCreateView, BaseDeleteView,
)
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorTable


@auto_url_register
class VendorTableListView(BaseListView):
    model = VendorTable

    def set_list_fields(self):
        return ['vendorno', 'party', 'vendortype', 'searchname', 'blocked']

    def set_search_fields(self):
        return ['vendorno', 'searchname']


@auto_url_register
class VendorTableDetailView(BaseDetailView):
    model = VendorTable

    def detail_ui_view(self):
        return [
            {
                'name': 'Vendor Information',
                'icon': 'Home',
                'Fieldcolumns': [
                    {'fields': ['vendorno', 'vendortype']},
                    {'fields': ['searchname', 'blocked']},
                ],
            },
        ]


@auto_url_register
class VendorTableCreateView(BaseCreateView):
    model = VendorTable

    def detail_ui_view(self):
        return [
            {
                'name': 'Vendor Information',
                'icon': 'Home',
                'Fieldcolumns': [
                    {'fields': ['vendorno', 'vendortype']},
                    {'fields': ['searchname', 'blocked']},
                ],
            },
        ]


@auto_url_register
class VendorTableDeleteView(BaseDeleteView):
    model = VendorTable
python
# accountpayable/views/__init__.py
from .vendor_views import *

The detail view has no get_inline_related_objects() and no get_related_object_id() — party inlines and profile resolution are handled by the framework. Do not add 'party' to Fieldcolumns.


URLs and Menu

python
# accountpayable/urls.py
from enezaframework.utils import get_registered_urls

app_name = "accountpayable"

dynamic_urls = get_registered_urls(app_name)

urlpatterns = [
    *dynamic_urls,
]
python
# accountpayable/apps.py
from django.apps import AppConfig
from enezaframework.utils import load_icon


class AccountpayableConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "accountpayable"

    menus = [
        {
            "fasttab": "ap_masterdata",
            "name": "AP Master Data",
            "icon": load_icon("users_icon"),
            "menuitems": [
                {
                    "url": f"{name}:vendortable_list",
                    "name": "Vendors",
                    "icon": load_icon("vendor_icon"),
                },
            ],
        },
    ]

The Full Chain

flowchart LR
    M["VendorTable<br/>PartyModelMixin"] --> V["VendorTableDetailView<br/>BaseDetailView"]
    V --> L["detail_ui_view<br/>Fieldcolumns"]
    V --> S["@auto_url_register"]
    S --> U["urls.py<br/>get_registered_urls"]
    V --> N["apps.py menus"]
    U --> PAGE["CRUD pages"]
    N --> PAGE
    M --> SIG["post_save signal"]
    SIG --> PARTY["Party created"]
    PARTY --> PROF["Person/Business profile"]
    V --> INLINE["Party inlines on detail page"]
    PARTY --> INLINE
erDiagram
    VendorTable }o--|| Party : "party"
    Party ||--o| Person : "person_profile"
    Party ||--o| Business : "business_profile"
    Party ||--o{ Address : "addresses"
    Party ||--o{ ContactInformation : "contact_information"

At runtime:

  1. Saving a vendor creates the vendor row, then the framework creates the Party from get_party_defaults().
  2. The party type (PRS/ORG) triggers Person or Business creation.
  3. The vendor detail page injects Address, Contact Information, and the matching profile inline.
  4. The menu links to accountpayable:vendortable_list; the URLs are generated from the registered views.