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:
152
users/admin.py
Normal file
152
users/admin.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Smooth Schedule Users App Admin Configuration
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.utils.html import format_html
|
||||
from hijack.contrib.admin import HijackUserAdminMixin
|
||||
from .models import User
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(HijackUserAdminMixin, BaseUserAdmin):
|
||||
"""
|
||||
Custom User admin with role-based filtering and masquerade button.
|
||||
"""
|
||||
list_display = [
|
||||
'email',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'role_badge',
|
||||
'tenant',
|
||||
'is_active',
|
||||
'is_temporary',
|
||||
'created_at',
|
||||
'hijack_field', # Adds the masquerade button
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'role',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'is_temporary',
|
||||
'created_at',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'email',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'tenant__name',
|
||||
]
|
||||
|
||||
ordering = ['-created_at']
|
||||
|
||||
fieldsets = (
|
||||
('Authentication', {
|
||||
'fields': ('email', 'password')
|
||||
}),
|
||||
('Personal Information', {
|
||||
'fields': ('first_name', 'last_name', 'phone', 'job_title')
|
||||
}),
|
||||
('Permissions & Role', {
|
||||
'fields': ('role', 'tenant', 'is_temporary', 'is_active', 'is_staff', 'is_superuser')
|
||||
}),
|
||||
('Groups & Permissions', {
|
||||
'fields': ('groups', 'user_permissions'),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
('Important Dates', {
|
||||
'fields': ('last_login', 'date_joined', 'created_at', 'updated_at'),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
('Security', {
|
||||
'fields': ('last_login_ip',),
|
||||
'classes': ('collapse',),
|
||||
}),
|
||||
)
|
||||
|
||||
readonly_fields = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'date_joined',
|
||||
'last_login',
|
||||
'last_login_ip',
|
||||
]
|
||||
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': (
|
||||
'email',
|
||||
'password1',
|
||||
'password2',
|
||||
'role',
|
||||
'tenant',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'is_temporary',
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
def role_badge(self, obj):
|
||||
"""Display role with color-coded badge"""
|
||||
role_colors = {
|
||||
'SUPERUSER': '#d32f2f', # Dark red
|
||||
'PLATFORM_MANAGER': '#f57c00', # Orange
|
||||
'PLATFORM_SALES': '#fbc02d', # Yellow
|
||||
'PLATFORM_SUPPORT': '#7cb342', # Light green
|
||||
'TENANT_OWNER': '#1976d2', # Blue
|
||||
'TENANT_MANAGER': '#0288d1', # Light blue
|
||||
'TENANT_STAFF': '#0097a7', # Cyan
|
||||
'CUSTOMER': '#5e35b1', # Purple
|
||||
}
|
||||
|
||||
color = role_colors.get(obj.role, '#777')
|
||||
|
||||
badge_html = f'''
|
||||
<span style="
|
||||
background-color: {color};
|
||||
color: white;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
">{obj.get_role_display()}</span>
|
||||
'''
|
||||
|
||||
if obj.is_temporary:
|
||||
badge_html += ' <span style="color: orange; font-size: 11px;">🎭 DEMO</span>'
|
||||
|
||||
return format_html(badge_html)
|
||||
role_badge.short_description = 'Role'
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""
|
||||
Filter users based on who is viewing.
|
||||
Platform users see all, tenant users only see their tenant.
|
||||
"""
|
||||
qs = super().get_queryset(request)
|
||||
|
||||
if request.user.is_superuser:
|
||||
return qs
|
||||
|
||||
# If user is a tenant-level admin, only show users from their tenant
|
||||
if request.user.tenant:
|
||||
return qs.filter(tenant=request.user.tenant)
|
||||
|
||||
return qs
|
||||
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
"""
|
||||
Limit tenant choices based on who is editing.
|
||||
"""
|
||||
if db_field.name == "tenant":
|
||||
# If editing user is tenant-scoped, they can only assign to their own tenant
|
||||
if request.user.tenant and not request.user.is_superuser:
|
||||
kwargs["queryset"] = request.user.get_accessible_tenants()
|
||||
|
||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
Reference in New Issue
Block a user