Files
smoothschedule/core/admin.py
poduck 2e111364a2 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>
2025-11-27 01:43:20 -05:00

238 lines
5.8 KiB
Python

"""
Smooth Schedule Core App Admin Configuration
"""
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from django_tenants.admin import TenantAdminMixin
from .models import Tenant, Domain, PermissionGrant
@admin.register(Tenant)
class TenantAdmin(TenantAdminMixin, admin.ModelAdmin):
"""
Admin interface for Tenant management.
"""
list_display = [
'name',
'schema_name',
'subscription_tier',
'is_active',
'created_on',
'user_count',
'domain_list',
]
list_filter = [
'is_active',
'subscription_tier',
'created_on',
]
search_fields = [
'name',
'schema_name',
'contact_email',
]
readonly_fields = [
'schema_name',
'created_on',
]
fieldsets = (
('Basic Information', {
'fields': ('name', 'schema_name', 'created_on')
}),
('Subscription', {
'fields': ('subscription_tier', 'is_active', 'max_users', 'max_resources')
}),
('Contact', {
'fields': ('contact_email', 'phone')
}),
)
def user_count(self, obj):
"""Display count of users in this tenant"""
count = obj.users.count()
return format_html(
'<span style="color: {};">{}</span>',
'green' if count < obj.max_users else 'red',
count
)
user_count.short_description = 'Users'
def domain_list(self, obj):
"""Display list of domains for this tenant"""
domains = obj.domain_set.all()
if not domains:
return '-'
domain_links = []
for domain in domains:
url = reverse('admin:core_domain_change', args=[domain.pk])
domain_links.append(f'<a href="{url}">{domain.domain}</a>')
return format_html(' | '.join(domain_links))
domain_list.short_description = 'Domains'
@admin.register(Domain)
class DomainAdmin(admin.ModelAdmin):
"""
Admin interface for Domain management.
"""
list_display = [
'domain',
'tenant',
'is_primary',
'is_custom_domain',
'verified_status',
]
list_filter = [
'is_primary',
'is_custom_domain',
]
search_fields = [
'domain',
'tenant__name',
]
readonly_fields = [
'verified_at',
]
fieldsets = (
('Domain Information', {
'fields': ('domain', 'tenant', 'is_primary')
}),
('Custom Domain Settings', {
'fields': (
'is_custom_domain',
'route53_zone_id',
'route53_record_set_id',
'ssl_certificate_arn',
'verified_at',
),
'classes': ('collapse',),
}),
)
def verified_status(self, obj):
"""Display verification status with color coding"""
if obj.is_verified():
return format_html(
'<span style="color: green;">✓ Verified</span>'
)
else:
return format_html(
'<span style="color: orange;">⚠ Pending</span>'
)
verified_status.short_description = 'Status'
@admin.register(PermissionGrant)
class PermissionGrantAdmin(admin.ModelAdmin):
"""
Admin interface for Permission Grant management.
"""
list_display = [
'id',
'grantor',
'grantee',
'action',
'granted_at',
'expires_at',
'status',
'time_left',
]
list_filter = [
'action',
'granted_at',
'expires_at',
]
search_fields = [
'grantor__email',
'grantee__email',
'action',
'reason',
]
readonly_fields = [
'granted_at',
'grantor',
'grantee',
'ip_address',
'user_agent',
]
fieldsets = (
('Grant Information', {
'fields': ('grantor', 'grantee', 'action', 'reason')
}),
('Timing', {
'fields': ('granted_at', 'expires_at', 'revoked_at')
}),
('Audit Trail', {
'fields': ('ip_address', 'user_agent'),
'classes': ('collapse',),
}),
)
def status(self, obj):
"""Display status with color coding"""
if obj.revoked_at:
return format_html(
'<span style="color: red;">✗ Revoked</span>'
)
elif obj.is_active():
return format_html(
'<span style="color: green;">✓ Active</span>'
)
else:
return format_html(
'<span style="color: gray;">⊘ Expired</span>'
)
status.short_description = 'Status'
def time_left(self, obj):
"""Display remaining time"""
remaining = obj.time_remaining()
if remaining is None:
return '-'
minutes = int(remaining.total_seconds() / 60)
if minutes < 5:
color = 'red'
elif minutes < 15:
color = 'orange'
else:
color = 'green'
return format_html(
'<span style="color: {};">{} min</span>',
color,
minutes
)
time_left.short_description = 'Time Left'
actions = ['revoke_grants']
def revoke_grants(self, request, queryset):
"""Admin action to revoke permission grants"""
count = 0
for grant in queryset:
if grant.is_active():
grant.revoke()
count += 1
self.message_user(
request,
f'Successfully revoked {count} permission grant(s).'
)
revoke_grants.short_description = 'Revoke selected grants'