""" Smooth Schedule Custom User Model Implements strict role hierarchy and multi-tenant user management """ from django.contrib.auth.models import AbstractUser from django.db import models from django.utils.translation import gettext_lazy as _ class User(AbstractUser): """ Custom User model with strict role-based access control. Replaces Django's default User model. """ # Role Choices - Strict Hierarchy class Role(models.TextChoices): # Platform-level roles (access across all tenants) SUPERUSER = 'SUPERUSER', _('Platform Superuser') PLATFORM_MANAGER = 'PLATFORM_MANAGER', _('Platform Manager') PLATFORM_SALES = 'PLATFORM_SALES', _('Platform Sales') PLATFORM_SUPPORT = 'PLATFORM_SUPPORT', _('Platform Support') # Tenant-level roles (access within single tenant) TENANT_OWNER = 'TENANT_OWNER', _('Tenant Owner') TENANT_MANAGER = 'TENANT_MANAGER', _('Tenant Manager') TENANT_STAFF = 'TENANT_STAFF', _('Tenant Staff') # Customer role (end users of the tenant) CUSTOMER = 'CUSTOMER', _('Customer') # Core fields role = models.CharField( max_length=20, choices=Role.choices, default=Role.CUSTOMER, help_text="User's role in the system hierarchy" ) # For multi-tenancy: link users to their tenant # Note: This is only for tenant-level users. Platform users can access all tenants. tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, null=True, blank=True, related_name='users', help_text="Tenant this user belongs to (null for platform-level users)" ) # Special flags is_temporary = models.BooleanField( default=False, help_text="True for sales demo accounts - can be masqueraded by Platform Sales" ) # Additional profile fields phone = models.CharField(max_length=20, blank=True) job_title = models.CharField(max_length=100, blank=True) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) last_login_ip = models.GenericIPAddressField(null=True, blank=True) class Meta: ordering = ['email'] indexes = [ models.Index(fields=['role', 'tenant']), models.Index(fields=['email', 'is_active']), ] def __str__(self): return f"{self.email} ({self.get_role_display()})" def is_platform_user(self): """Check if user has platform-level access""" return self.role in [ self.Role.SUPERUSER, self.Role.PLATFORM_MANAGER, self.Role.PLATFORM_SALES, self.Role.PLATFORM_SUPPORT, ] def is_tenant_user(self): """Check if user is tenant-scoped""" return self.role in [ self.Role.TENANT_OWNER, self.Role.TENANT_MANAGER, self.Role.TENANT_STAFF, self.Role.CUSTOMER, ] def can_manage_users(self): """Check if user can manage other users""" return self.role in [ self.Role.SUPERUSER, self.Role.PLATFORM_MANAGER, self.Role.TENANT_OWNER, self.Role.TENANT_MANAGER, ] def can_access_billing(self): """Check if user can access billing information""" return self.role in [ self.Role.SUPERUSER, self.Role.PLATFORM_MANAGER, self.Role.TENANT_OWNER, ] def get_accessible_tenants(self): """ Get list of tenants this user can access. Platform users can access all tenants. Tenant users can only access their own tenant. """ from core.models import Tenant if self.is_platform_user(): return Tenant.objects.all() elif self.tenant: return Tenant.objects.filter(pk=self.tenant.pk) else: return Tenant.objects.none() def save(self, *args, **kwargs): """ Override save to enforce business rules """ # Superusers must be staff and have is_superuser flag if self.role == self.Role.SUPERUSER: self.is_staff = True self.is_superuser = True # Platform users should not be tied to a tenant if self.is_platform_user(): self.tenant = None # Tenant users must have a tenant if self.is_tenant_user() and not self.tenant: raise ValueError(f"Users with role {self.role} must be assigned to a tenant") super().save(*args, **kwargs)