Party System Architecture

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

Table of Contents

  1. Overview
  2. Component Map
  3. App Structure
  4. What the Framework Handles Automatically
  5. Signal Chain and Save Data Flow
  6. The Party Data Model
  7. Related Guides

Overview

The party system separates the entity a record represents (Party) from the business record (Customer, Vendor, Contact). A business model opts in by inheriting PartyModelMixin; from that moment the framework owns party creation, profile creation, and the detail-page inlines.

The design keeps a single source of truth for contact data. Many business records can point at one Party, and the party carries the reusable Address, ContactInformation, Person, and Business records.


Component Map

The party system is spread across the framework app, the common app, and your business app.

flowchart TD
    subgraph Framework["enezaframework"]
        MIXIN["PartyModelMixin<br/>models/ezaparty.py"]
        SIG["auto_create_party_for_party_model<br/>signals.py"]
        DETAIL["BaseDetailView party injection<br/>views/base.py"]
        INLINE["InlineMixin party flags<br/>views/mixins.py"]
        BRIDGE["BaseInlineOneToOneDetailView<br/>views/baseinline.py"]
    end

    subgraph Common["enezacommon"]
        PARTY["Party / Person / Business<br/>Address / ContactInformation<br/>models/Party.py"]
        PROFSIG["create_org_or_person_profile<br/>signals.py"]
        PARTYVIEWS["Party inline views<br/>views/party_views.py"]
    end

    subgraph Business["Your business app"]
        MODEL["Customer / Vendor model"]
        VIEWS["List / Detail / Create / Delete views"]
        APPS["apps.py menus"]
        URLS["urls.py"]
    end

    MIXIN --> MODEL
    SIG --> PARTY
    PARTY --> PROFSIG
    DETAIL --> VIEWS
    INLINE --> DETAIL
    BRIDGE --> VIEWS
    PARTYVIEWS --> PARTY
    MODEL --> VIEWS
    VIEWS --> URLS
    APPS --> URLS
Piece Location Role
PartyModelMixin enezaframework/models/ezaparty.py:5 Contributes the party FK and get_party_defaults().
auto_create_party_for_party_model enezaframework/signals.py:9 Creates the Party on first save of a party-based model.
create_org_or_person_profile enezacommon/signals.py:43 Creates Person or Business when a Party is created.
Party injection enezaframework/views/base.py:183, 200-294 Adds party_object and auto-injects party inlines.
Party handling in inline config enezaframework/views/mixins.py:353-361, 894-927 Hides party from forms and resolves party inline object IDs.
Party bridge enezaframework/views/baseinline.py:648-693 Resolves one-to-one profiles through parent.party.
Shared models enezacommon/models/Party.py Party, Person, Business, Address, ContactInformation.
Party inline views enezacommon/views/party_views.py The list/detail/create/delete inline views for party children.

App Structure

The party system does not change the standard app layout — it only adds model/view wiring. A party-based app keeps the normal structure described in Standardized App Structure.

text
accountpayable/
├── apps.py                      # navigation menus
├── urls.py                      # aggregates auto-registered URLs
├── models/
│   ├── __init__.py              # re-exports VendorTable, ...
│   └── vendortable.py           # class VendorTable(PartyModelMixin, EnezaMaster)
├── views/
│   ├── __init__.py              # re-exports the view module(s)
│   └── vendorviews.py           # BaseListView / BaseDetailView / ...
└── migrations/
    └── 0001_initial.py          # includes the auto-added party_id column

What the developer edits:

  1. models/vendortable.py — add the mixin, party_category, and optional get_party_defaults().
  2. models/__init__.py — re-export the model.
  3. views/vendorviews.py — declare the standard CRUD views; no party-specific overrides.
  4. apps.py / urls.py — navigation and URL aggregation as usual.

What the Framework Handles Automatically

Concern Handled by Mechanism
party ForeignKey field PartyModelMixin Abstract model contributes the party FK to the child.
Party auto-creation on save enezaframework.signals post_save receiver checks isinstance(instance, PartyModelMixin).
Person/Business profile auto-creation enezacommon.signals Party post_save receiver branches on party_type.
Party inlines on the detail page BaseDetailView._get_party_inline_objects() Auto-injects Address + ContactInformation + (Person or Business).
party_object in template context BaseDetailView.get_context_data() context['party_object'] = getattr(self.object, 'party', None).
is_party_inline flag BaseDetailView._get_party_inline_objects() Marks each injected inline dict.
Party PK in inline URLs inline_items_section.html Uses party_object.pk when inline_item.is_party_inline.
party hidden from forms UiMixin.get_form_class() Excludes party for models with an FK to Party.
One-to-one profile resolution BaseInlineOneToOneDetailView._is_party_bridge() Auto-detects the parent → parent.party → profile chain.

Signal Chain and Save Data Flow

Saving a new party-based record triggers a chain across the two signal modules.

sequenceDiagram
    participant Dev as YourModel.save()
    participant Fw as enezaframework.signals
    participant Common as enezacommon.signals
    participant DB as Database

    Dev->>Fw: post_save(created=True)
    Fw->>Fw: isinstance(instance, PartyModelMixin)?
    Fw->>Fw: instance.party is None? party_category set?
    Fw->>DB: Party.objects.create(party_type=..., display_name=..., is_active=...)
    Fw->>DB: instance.party = party and save(update_fields=['party'])
    DB-->>Common: Party post_save(created=True)
    Common->>DB: Person.objects.get_or_create(party=party)  OR
    Common->>DB: Business.objects.get_or_create(party=party)
flowchart TD
    SAVE[Save a PartyModelMixin instance] --> CREATED{created?}
    CREATED -->|No| STOP1[Do nothing]
    CREATED -->|Yes| HAS{instance.party set?}
    HAS -->|Yes| STOP2[Do nothing]
    HAS -->|No| CAT{party_category set?}
    CAT -->|No| WARN[Log warning, skip]
    CAT -->|Yes| CREATE[Create Party from get_party_defaults]
    CREATE --> LINK[Set instance.party and save]
    LINK --> PROFILE{party_type}
    PROFILE -->|PRS| PERSON[get_or_create Person]
    PROFILE -->|ORG| BUSINESS[get_or_create Business]

Key points:

  • The framework receiver runs on every post_save; it exits early unless the sender is a PartyModelMixin instance that was just created without a party.
  • A party_category of None is treated as "not a party-based model" and logged, not created.
  • get_party_defaults() supplies the Party fields and, for EnezaMaster-derived models, the entity.

The Party Data Model

Party is the hub. Profiles are one-to-one; addresses and contacts are one-to-many.

erDiagram
    ENTITY ||--o{ PARTY : scopes
    PARTY ||--o| PERSON : "person_profile"
    PARTY ||--o| BUSINESS : "business_profile"
    PARTY ||--o{ ADDRESS : "addresses"
    PARTY ||--o{ CONTACTINFORMATION : "contact_information"
    CUSTOMER }o--|| PARTY : "party"
    VENDOR }o--|| PARTY : "party"
  • party_type (PRS or ORG) decides which one-to-one profile exists and which conditional inline shows. See Party Types & Profiles.
  • Address and ContactInformation are always available for either party type. See Party Inline System.