Initial commit: SmoothSchedule multi-tenant scheduling platform
This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
143
users/models.py
Normal file
143
users/models.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user