- 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.
8.3 KiB
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:
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
TYPE_CHOICES = [
("for-profit", "For-Profit"),
("non-profit", "Non-Profit"),
("government", "Government"),
("ngo", "NGO"),
("educational", "Educational"),
("healthcare", "Healthcare"),
("cooperative", "Cooperative"),
]
STATUS_CHOICES
STATUS_CHOICES = [
("active", "Active"),
("inactive", "Inactive"),
("pending", "Pending"),
("suspended", "Suspended"),
("dissolved", "Dissolved"),
("merged", "Merged"),
]
SIZE_CHOICES (recommended)
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)
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:
# 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:
# 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
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
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
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
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=Trueon CharField/TextField — useblank=True, default="" - ❌ Don't put all possible fields in a single model — extend per domain
- ❌ Don't use
Meta.orderingon 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
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).