Files
mnemosyne/docs/Pattern_Organization_V1-00.md
Robert Helewka 99bdb4ac92 Add Themis application with custom widgets, views, and utilities
- Implemented custom form widgets for date, time, and datetime fields with DaisyUI styling.
- Created utility functions for formatting dates, times, and numbers according to user preferences.
- Developed views for profile settings, API key management, and notifications, including health check endpoints.
- Added URL configurations for Themis tests and main application routes.
- Established test cases for custom widgets to ensure proper functionality and integration.
- Defined project metadata and dependencies in pyproject.toml for package management.
2026-03-21 02:00:18 +00:00

276 lines
8.3 KiB
Markdown

# Organization Model Pattern v1.0.0
Standard pattern for Organization models across Django applications. Each app implements its own Organization model following this pattern to ensure interoperability and consistent field names.
## 🐾 Red Panda Approval™
This pattern follows Red Panda Approval standards.
---
## Why a Pattern, Not a Shared Model
Organization requirements vary by domain. A financial app needs stock symbols and ISIN codes. A healthcare app needs provider IDs. An education app needs accreditation fields. Shipping a monolithic Organization model with 40+ fields forces every app to carry fields it does not need.
Instead, this pattern defines:
- **Required fields** every Organization model must have
- **Recommended fields** most apps should include
- **Extension guidelines** for domain-specific needs
- **Standard choice values** for interoperability
---
## Required Fields
Every Organization model must include these fields:
```python
import uuid
from django.conf import settings
from django.db import models
class Organization(models.Model):
# Primary key
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# Core identity
name = models.CharField(max_length=255, db_index=True,
help_text="Organization display name")
slug = models.SlugField(max_length=255, unique=True,
help_text="URL-friendly identifier")
# Classification
type = models.CharField(max_length=20, choices=TYPE_CHOICES,
help_text="Organization type")
status = models.CharField(max_length=20, choices=STATUS_CHOICES,
default="active", help_text="Current status")
# Location
country = models.CharField(max_length=2, help_text="ISO 3166-1 alpha-2 country code")
# Audit
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
null=True, blank=True, related_name="created_organizations")
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
null=True, blank=True, related_name="updated_organizations")
class Meta:
verbose_name = "Organization"
verbose_name_plural = "Organizations"
def __str__(self):
return self.name
def get_absolute_url(self):
from django.urls import reverse
return reverse("organization-detail", kwargs={"slug": self.slug})
```
---
## Standard Choice Values
Use these exact values for interoperability between apps:
### TYPE_CHOICES
```python
TYPE_CHOICES = [
("for-profit", "For-Profit"),
("non-profit", "Non-Profit"),
("government", "Government"),
("ngo", "NGO"),
("educational", "Educational"),
("healthcare", "Healthcare"),
("cooperative", "Cooperative"),
]
```
### STATUS_CHOICES
```python
STATUS_CHOICES = [
("active", "Active"),
("inactive", "Inactive"),
("pending", "Pending"),
("suspended", "Suspended"),
("dissolved", "Dissolved"),
("merged", "Merged"),
]
```
### SIZE_CHOICES (recommended)
```python
SIZE_CHOICES = [
("micro", "Micro (1-9)"),
("small", "Small (10-49)"),
("medium", "Medium (50-249)"),
("large", "Large (250-999)"),
("enterprise", "Enterprise (1000+)"),
]
```
### PARENT_RELATIONSHIP_CHOICES (if using hierarchy)
```python
PARENT_RELATIONSHIP_CHOICES = [
("subsidiary", "Subsidiary"),
("division", "Division"),
("branch", "Branch"),
("franchise", "Franchise"),
("joint-venture", "Joint Venture"),
("department", "Department"),
]
```
---
## Recommended Fields
Most apps should include these fields:
```python
# Extended identity
legal_name = models.CharField(max_length=255, blank=True, default="",
help_text="Full legal entity name")
abbreviated_name = models.CharField(max_length=50, blank=True, default="",
db_index=True, help_text="Short name/acronym")
# Classification
size = models.CharField(max_length=20, choices=SIZE_CHOICES, blank=True, default="",
help_text="Organization size")
# Contact
primary_email = models.EmailField(blank=True, default="", help_text="Primary contact email")
primary_phone = models.CharField(max_length=20, blank=True, default="", help_text="Primary phone")
website = models.URLField(blank=True, default="", help_text="Organization website")
# Address
address_line1 = models.CharField(max_length=255, blank=True, default="")
address_line2 = models.CharField(max_length=255, blank=True, default="")
city = models.CharField(max_length=100, blank=True, default="")
state_province = models.CharField(max_length=100, blank=True, default="")
postal_code = models.CharField(max_length=20, blank=True, default="")
# Content
overview = models.TextField(blank=True, default="", help_text="Organization description")
# Metadata
is_active = models.BooleanField(default=True, help_text="Soft delete flag")
tags = models.JSONField(default=list, blank=True, help_text="Flexible tags")
```
---
## Hierarchy Pattern
For apps that need parent-child organization relationships:
```python
# Hierarchical relationships
parent_organization = models.ForeignKey(
"self",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="subsidiaries",
help_text="Parent organization",
)
parent_relationship_type = models.CharField(
max_length=20,
choices=PARENT_RELATIONSHIP_CHOICES,
blank=True,
default="",
help_text="Type of relationship with parent",
)
```
### Hierarchy Utility Functions
```python
def get_ancestors(org):
"""Walk up the parent chain. Returns list of Organization instances."""
ancestors = []
current = org.parent_organization
while current:
ancestors.append(current)
current = current.parent_organization
return ancestors
def get_descendants(org):
"""Recursively collect all child organizations."""
descendants = []
for child in org.subsidiaries.all():
descendants.append(child)
descendants.extend(get_descendants(child))
return descendants
```
⚠️ **Warning:** Recursive queries can be expensive. For deep hierarchies, consider using `django-mptt` or `django-treebeard`, or store a materialized path.
---
## Domain Extension Examples
### Financial App
```python
class Organization(BaseOrganization):
revenue = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True)
revenue_year = models.PositiveIntegerField(null=True, blank=True)
employee_count = models.PositiveIntegerField(null=True, blank=True)
stock_symbol = models.CharField(max_length=10, blank=True, default="", db_index=True)
fiscal_year_end_month = models.PositiveSmallIntegerField(null=True, blank=True)
```
### Healthcare App
```python
class Organization(BaseOrganization):
npi_number = models.CharField(max_length=10, blank=True, default="")
facility_type = models.CharField(max_length=30, choices=FACILITY_CHOICES)
bed_count = models.PositiveIntegerField(null=True, blank=True)
accreditation = models.JSONField(default=list, blank=True)
```
### Education App
```python
class Organization(BaseOrganization):
institution_type = models.CharField(max_length=30, choices=INSTITUTION_CHOICES)
student_count = models.PositiveIntegerField(null=True, blank=True)
accreditation_body = models.CharField(max_length=100, blank=True, default="")
```
---
## Anti-Patterns
- ❌ Don't use `null=True` on CharField/TextField — use `blank=True, default=""`
- ❌ Don't put all possible fields in a single model — extend per domain
- ❌ Don't use `Meta.ordering` on Organization — specify in queries
- ❌ Don't override `save()` for hierarchy calculation — use signals or service functions
- ❌ Don't expose sequential IDs in URLs — use slug or short UUID
---
## Indexing Recommendations
```python
class Meta:
indexes = [
models.Index(fields=["name"], name="org_name_idx"),
models.Index(fields=["status"], name="org_status_idx"),
models.Index(fields=["type"], name="org_type_idx"),
models.Index(fields=["country"], name="org_country_idx"),
]
```
Add domain-specific indexes as needed (e.g., `stock_symbol` for financial apps).