Party System Architecture
Developer guide for the Enezaframework party system. Part of the Party System series.
Table of Contents
- Overview
- Component Map
- App Structure
- What the Framework Handles Automatically
- Signal Chain and Save Data Flow
- The Party Data Model
- 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.
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:
models/vendortable.py— add the mixin,party_category, and optionalget_party_defaults().models/__init__.py— re-export the model.views/vendorviews.py— declare the standard CRUD views; no party-specific overrides.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 aPartyModelMixininstance that was just created without a party. - A
party_categoryofNoneis treated as "not a party-based model" and logged, not created. get_party_defaults()supplies thePartyfields and, forEnezaMaster-derived models, theentity.
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(PRSorORG) decides which one-to-one profile exists and which conditional inline shows. See Party Types & Profiles.AddressandContactInformationare always available for either party type. See Party Inline System.