Initial commit - SoundWave v1.0
- Full PWA support with offline capabilities - Comprehensive search across songs, playlists, and channels - Offline playlist manager with download tracking - Pre-built frontend for zero-build deployment - Docker-based deployment with docker compose - Material-UI dark theme interface - YouTube audio download and management - Multi-user authentication support
This commit is contained in:
commit
51679d1943
254 changed files with 37281 additions and 0 deletions
243
backend/user/admin_users.py
Normal file
243
backend/user/admin_users.py
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
"""Admin interface for user management"""
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from user.models import Account, UserYouTubeAccount
|
||||
|
||||
|
||||
@admin.register(Account)
|
||||
class AccountAdmin(BaseUserAdmin):
|
||||
"""Enhanced admin for Account model with user management"""
|
||||
|
||||
list_display = [
|
||||
'username',
|
||||
'email',
|
||||
'is_admin',
|
||||
'is_active',
|
||||
'storage_usage',
|
||||
'channel_count',
|
||||
'playlist_count',
|
||||
'date_joined',
|
||||
'last_login',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'is_admin',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'two_factor_enabled',
|
||||
'date_joined',
|
||||
]
|
||||
|
||||
search_fields = ['username', 'email']
|
||||
|
||||
fieldsets = (
|
||||
('Account Info', {
|
||||
'fields': ('username', 'email', 'password')
|
||||
}),
|
||||
('Permissions', {
|
||||
'fields': (
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_admin',
|
||||
'is_superuser',
|
||||
'groups',
|
||||
'user_permissions',
|
||||
)
|
||||
}),
|
||||
('Resource Limits', {
|
||||
'fields': (
|
||||
'storage_quota_gb',
|
||||
'storage_used_gb',
|
||||
'max_channels',
|
||||
'max_playlists',
|
||||
)
|
||||
}),
|
||||
('Security', {
|
||||
'fields': (
|
||||
'two_factor_enabled',
|
||||
'two_factor_secret',
|
||||
)
|
||||
}),
|
||||
('Metadata', {
|
||||
'fields': (
|
||||
'user_notes',
|
||||
'created_by',
|
||||
'date_joined',
|
||||
'last_login',
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
add_fieldsets = (
|
||||
('Create New User', {
|
||||
'classes': ('wide',),
|
||||
'fields': (
|
||||
'username',
|
||||
'email',
|
||||
'password1',
|
||||
'password2',
|
||||
'is_admin',
|
||||
'is_active',
|
||||
'storage_quota_gb',
|
||||
'max_channels',
|
||||
'max_playlists',
|
||||
'user_notes',
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
readonly_fields = ['date_joined', 'last_login', 'storage_used_gb']
|
||||
|
||||
ordering = ['-date_joined']
|
||||
|
||||
def storage_usage(self, obj):
|
||||
"""Display storage usage with progress bar"""
|
||||
percent = obj.storage_percent_used
|
||||
if percent > 90:
|
||||
color = 'red'
|
||||
elif percent > 75:
|
||||
color = 'orange'
|
||||
else:
|
||||
color = 'green'
|
||||
|
||||
return format_html(
|
||||
'<div style="width:100px; background-color:#f0f0f0; border:1px solid #ccc;">'
|
||||
'<div style="width:{}%; background-color:{}; height:20px; text-align:center; color:white;">'
|
||||
'{:.1f}%'
|
||||
'</div></div>',
|
||||
min(percent, 100),
|
||||
color,
|
||||
percent
|
||||
)
|
||||
storage_usage.short_description = 'Storage'
|
||||
|
||||
def channel_count(self, obj):
|
||||
"""Display channel count with limit"""
|
||||
from channel.models import Channel
|
||||
count = Channel.objects.filter(owner=obj).count()
|
||||
return format_html(
|
||||
'<span style="color: {};">{} / {}</span>',
|
||||
'red' if count >= obj.max_channels else 'green',
|
||||
count,
|
||||
obj.max_channels
|
||||
)
|
||||
channel_count.short_description = 'Channels'
|
||||
|
||||
def playlist_count(self, obj):
|
||||
"""Display playlist count with limit"""
|
||||
from playlist.models import Playlist
|
||||
count = Playlist.objects.filter(owner=obj).count()
|
||||
return format_html(
|
||||
'<span style="color: {};">{} / {}</span>',
|
||||
'red' if count >= obj.max_playlists else 'green',
|
||||
count,
|
||||
obj.max_playlists
|
||||
)
|
||||
playlist_count.short_description = 'Playlists'
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
"""Set created_by for new users"""
|
||||
if not change and request.user.is_authenticated:
|
||||
obj.created_by = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
actions = [
|
||||
'reset_storage_quota',
|
||||
'disable_users',
|
||||
'enable_users',
|
||||
'reset_2fa',
|
||||
]
|
||||
|
||||
def reset_storage_quota(self, request, queryset):
|
||||
"""Reset storage usage to 0"""
|
||||
count = queryset.update(storage_used_gb=0.0)
|
||||
self.message_user(request, f'Reset storage for {count} users')
|
||||
reset_storage_quota.short_description = 'Reset storage usage'
|
||||
|
||||
def disable_users(self, request, queryset):
|
||||
"""Disable selected users"""
|
||||
count = queryset.update(is_active=False)
|
||||
self.message_user(request, f'Disabled {count} users')
|
||||
disable_users.short_description = 'Disable selected users'
|
||||
|
||||
def enable_users(self, request, queryset):
|
||||
"""Enable selected users"""
|
||||
count = queryset.update(is_active=True)
|
||||
self.message_user(request, f'Enabled {count} users')
|
||||
enable_users.short_description = 'Enable selected users'
|
||||
|
||||
def reset_2fa(self, request, queryset):
|
||||
"""Reset 2FA for selected users"""
|
||||
count = queryset.update(
|
||||
two_factor_enabled=False,
|
||||
two_factor_secret='',
|
||||
backup_codes=[]
|
||||
)
|
||||
self.message_user(request, f'Reset 2FA for {count} users')
|
||||
reset_2fa.short_description = 'Reset 2FA'
|
||||
|
||||
|
||||
@admin.register(UserYouTubeAccount)
|
||||
class UserYouTubeAccountAdmin(admin.ModelAdmin):
|
||||
"""Admin for YouTube accounts"""
|
||||
|
||||
list_display = [
|
||||
'user',
|
||||
'account_name',
|
||||
'youtube_channel_name',
|
||||
'is_active',
|
||||
'auto_download',
|
||||
'download_quality',
|
||||
'created_date',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'is_active',
|
||||
'auto_download',
|
||||
'download_quality',
|
||||
'created_date',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'user__username',
|
||||
'account_name',
|
||||
'youtube_channel_name',
|
||||
'youtube_channel_id',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Account Info', {
|
||||
'fields': (
|
||||
'user',
|
||||
'account_name',
|
||||
'youtube_channel_id',
|
||||
'youtube_channel_name',
|
||||
)
|
||||
}),
|
||||
('Authentication', {
|
||||
'fields': (
|
||||
'cookies_file',
|
||||
'is_active',
|
||||
'last_verified',
|
||||
)
|
||||
}),
|
||||
('Download Settings', {
|
||||
'fields': (
|
||||
'auto_download',
|
||||
'download_quality',
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
readonly_fields = ['created_date', 'last_verified']
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""Filter by user if not admin"""
|
||||
qs = super().get_queryset(request)
|
||||
if request.user.is_superuser or request.user.is_admin:
|
||||
return qs
|
||||
return qs.filter(user=request.user)
|
||||
Loading…
Add table
Add a link
Reference in a new issue