# 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).