from django.contrib import admin from django.contrib.auth.forms import UserChangeForm, UsernameField from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin from django.utils.translation import gettext_lazy as _ from rest_framework.authtoken.models import TokenProxy from ajaxdatatable.admin import AjaxDatatable from apps.account.models import User, Notification, LocationHistory from django import forms from django.contrib import admin from django.urls import path, reverse from django.shortcuts import render, redirect from django.contrib import messages from apps.account.models import ClientUser, AdminUser, StudentUser, ProfessorUser from phonenumber_field.formfields import PhoneNumberField from utils.admin import project_admin_site from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.admin import ModelAdmin, StackedInline, TabularInline from django.contrib.auth.models import Group from django.db import models from unfold.contrib.forms.widgets import WysiwygWidget from unfold.decorators import action, display from unfold.sections import TableSection from unfold.contrib.filters.admin import ( AutocompleteSelectMultipleFilter, ChoicesDropdownFilter, MultipleRelatedDropdownFilter, RangeDateFilter, RangeDateTimeFilter, RangeNumericFilter, SingleNumericFilter, TextFilter, ) from apps.account.admin.location import LocationHistoryInline class UserAdmin(BaseUserAdmin, ModelAdmin): form = UserChangeForm add_form = UserCreationForm change_password_form = AdminPasswordChangeForm compressed_fields = False list_before_template = "account/user_list_section.html" list_display = ( 'fullname', 'email', 'is_active', 'display_date_joined', ) ordering = ("-id",) search_fields = ( 'email', 'fullname', 'username', ) list_filter = [ "is_active", "is_staff", ("last_login", RangeDateTimeFilter), ("date_joined", RangeDateTimeFilter), ] inlines = [LocationHistoryInline] add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ( ('fullname', 'email'), 'phone_number', 'birthdate', 'gender','avatar', 'skill', 'info'), }), (_('Location'), { 'fields': ('city', 'country'), 'classes': ('collapse',), }), (_('Password'), { 'fields': ('password1', 'password2'), 'classes': ('collapse',), }), (_('Permissions'), { 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups'), 'classes': ('collapse',), }), ) fieldsets = ( (None, {"fields": ("email", "fullname")}), ( _("Basic Information"), { "fields": ("gender", "avatar", "phone_number", "birthdate", 'info', 'skill', "password"), "classes": ["tab"], }, ), ( _('Country & City'), { 'fields': ('city', 'country'), "classes": ["tab"], } ), ( _('Device Information'), { 'fields': ('device_id', 'device_os', 'fcm', 'language', ), "classes": ["tab"], } ), ( _('Authentication'), { 'fields': ('display_auth_token',), "classes": ["tab"], } ), ( _('Permissions'), { 'fields': ('user_type', 'is_active', 'is_staff', 'groups'), "classes": ["tab"], } ), ( _('Important dates'), { 'fields': ('last_login', 'date_joined', 'deleted_at'), "classes": ["tab"], } ), ) formfield_overrides = { models.TextField: { "widget": WysiwygWidget, } } radio_fields = { "gender": admin.HORIZONTAL, } readonly_fields = ["last_login", "date_joined", "display_auth_token"] @display(description=_("Date Joined")) def display_date_joined(self, instance: User): return instance.date_joined.strftime("%Y-%m-%d %H:%M") if instance.date_joined else "-" @display(description=_("Last Login")) def display_last_login(self, instance: User): return instance.last_login.strftime("%Y-%m-%d %H:%M") if instance.last_login else "-" @display(description=_("Authentication Token")) def display_auth_token(self, instance: User): from rest_framework.authtoken.models import Token from django.utils.html import format_html try: token, created = Token.objects.get_or_create(user=instance) return format_html('{}', token.key) except Exception as e: return format_html('{}', str(e)) def get_queryset(self, request): qs = super().get_queryset(request) return qs.filter(email__isnull=False) class GuestUserAdmin(UserAdmin): list_display = ( 'device_id', 'device_os', 'is_active', 'display_date_joined', ) # Inherits fieldsets from UserAdmin, which now include the auth token def has_add_permission(self, request): if '_popup' in request.GET and request.GET['_popup'] == '1': return True return False def get_queryset(self, request): qs = super().get_queryset(request) return qs.filter(email__isnull=True) @display(description=_("Date Joined")) def display_date_joined(self, instance: User): return instance.date_joined.strftime("%Y-%m-%d %H:%M") if instance.date_joined else "-" class StudentUserAdmin(UserAdmin): list_display = ( 'display_header', 'email', 'gender', 'display_age', 'courses_count' ) add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': (('fullname', 'email'), 'phone_number', 'avatar', 'birthdate', 'gender'), }), (_('Location'), { 'fields': (('city', 'country'),), 'classes': ('collapse',), }), (_('password'), { 'fields': ('password1', 'password2',), 'classes': ('collapse',), }), ) # Ensure the fieldsets from UserAdmin are used, which now include the auth token @display(description=_("Student"), header=True) def display_header(self, instance: StudentUser): from django.templatetags.static import static # Get avatar image path - use user's avatar if available, otherwise use default avatar_path = instance.avatar.url if instance.avatar else static("images/reading(1).png") return [ instance.fullname, None, None, { "path": avatar_path, "height": 30, "width": 36, "borderless": True, # "squared": True, }, ] @display(description=_("Age")) def display_age(self, instance: StudentUser): from django.utils.html import format_html from datetime import date if not instance.birthdate: return "-" today = date.today() birthdate = instance.birthdate age = today.year - birthdate.year - ((today.month, today.day) < (birthdate.month, birthdate.day)) formatted_date = birthdate.strftime("%Y-%m-%d") return format_html( '{}', f"Born on {formatted_date}", age ) @display(description=_("Courses"), dropdown=True) def courses_count(self, instance: StudentUser): from django.utils.html import format_html total = instance.participated_courses.count() items = [] for participant in instance.participated_courses.all(): course = participant.course title = format_html( """
{} visibility
""", course.title, course.id ) items.append( { "title": title, } ) # Display custom string if no records found if total == 0: return "-" return { "title": f"{total} {_('courses')}", "items": items, "striped": True, } def get_queryset(self, request): """ Optimize queries by prefetching related courses """ return ( super().get_queryset(request) .prefetch_related( "participated_courses", "participated_courses__course", ) ) # Register with default admin site for filer app compatibility admin.site.register(User, UserAdmin) # Register with custom project admin site project_admin_site.register(User, UserAdmin) project_admin_site.register(ClientUser, GuestUserAdmin) project_admin_site.register(StudentUser, StudentUserAdmin) @admin.register(Group, site=project_admin_site) class GroupAdmin(BaseGroupAdmin, ModelAdmin): list_display = ('name', 'permissions_count') search_fields = ('name',) ordering = ('name',) filter_horizontal = ('permissions',) fieldsets = ( (None, {'fields': ('name',)}), (_('Permissions'), {'fields': ('permissions',), 'classes': ['tab']}), ) @display(description=_("Permissions")) def permissions_count(self, obj): count = obj.permissions.count() return f"{count} {_('permissions')}" if count > 0 else "-" class CourseTableSection(TableSection): verbose_name = _("Course Categories") related_name = "courses" height = 380 fields = [ "title", "status", "edit_link" ] def edit_link(self, instance): from django.utils.html import format_html return format_html( '' 'visibility' '', instance.id ) edit_link.short_description = _("Edit") class ProfessorUserAdmin(UserAdmin): list_display = ( 'display_header', 'email', 'courses_count' ) list_sections = [CourseTableSection] save_as = True # Inherits fieldsets from UserAdmin, which now include the auth token @display(description=_("Professor"), header=True) def display_header(self, instance: StudentUser): from django.templatetags.static import static # Get avatar image path - use user's avatar if available, otherwise use default avatar_path = instance.avatar.url if instance.avatar else static("images/reading(1).png") return [ instance.fullname, None, None, { "path": avatar_path, "height": 30, "width": 50, "borderless": True, "squared": True, }, ] @display(description=_("Courses"), dropdown=True) def courses_count(self, instance: ProfessorUser): from django.utils.html import format_html total = instance.courses.count() items = [] for course in instance.courses.all(): title = format_html( """
{} visibility
""", course.title, course.id ) items.append( { "title": title, } ) # Display custom string if no records found if total == 0: return "-" return { "title": f"{total} {_('courses')}", "items": items, "striped": True, } def get_queryset(self, request): """ Optimize queries by prefetching related courses """ return ( super().get_queryset(request) .prefetch_related("courses") ) # Register the ProfessorUserAdmin with the project admin site project_admin_site.register(ProfessorUser, ProfessorUserAdmin) admin.site.unregister(TokenProxy)