from django import forms from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin from django.contrib.auth.models import Group from django.db import models from django.utils.html import format_html from django.utils.translation import gettext_lazy as _, ngettext from rest_framework.authtoken.models import TokenProxy from unfold.admin import ModelAdmin, StackedInline from unfold.decorators import display from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.contrib.filters.admin import RangeDateTimeFilter from apps.account.models import User, ClientUser, LocationHistory from utils.admin import project_admin_site from apps.account.admin.location import LocationHistoryInline from apps.account.admin.verification import VerificationDocumentInline # ========================================================= # 1. Base User Admin Form & Admin # ========================================================= class UserAdminCreationForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = User fields = ("first_name", "last_name", "fullname", "email") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if 'email' in self.fields: self.fields['email'].required = True def clean_email(self): email = self.cleaned_data.get('email') if User.objects.filter(email=email).exists(): raise forms.ValidationError(_("A user with this email already exists.")) return email import json from apps.account.admin.widgets import ( GroupPillSelectMultipleWidget, RegionPillSelectMultipleWidget, RolePermissionsMatrixWidget, UserAccessManagementWidget, ) from apps.profiles.models import Region class UserAdminChangeForm(UserChangeForm): managed_regions = forms.ModelMultipleChoiceField( queryset=Region.objects.filter(is_active=True), widget=RegionPillSelectMultipleWidget, required=False, label=_("Managed Regions"), help_text=_("Geographic regions assigned to this administrator. Administrative jurisdiction applies only to institutions and projects within these regions.") ) groups = forms.ModelMultipleChoiceField( queryset=Group.objects.all(), widget=GroupPillSelectMultipleWidget, required=False, label=_("Assigned Groups") ) custom_permissions = forms.CharField( widget=UserAccessManagementWidget, required=False, label=_("Role & Permissions Overview") ) class Meta(UserChangeForm.Meta): model = User def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if 'email' in self.fields: self.fields['email'].required = True if 'custom_permissions' in self.fields: self.fields['custom_permissions'].widget.instance = self.instance def clean_custom_permissions(self): val = self.cleaned_data.get('custom_permissions') if isinstance(val, str): try: return json.loads(val) if val.strip() else {} except Exception: return {} elif isinstance(val, dict): return val return {} class UserAdmin(ModelAdmin, BaseUserAdmin): form = UserAdminChangeForm add_form = UserAdminCreationForm change_password_form = AdminPasswordChangeForm compressed_fields = False list_display = ('first_name', 'last_name', 'fullname', 'email', 'role', 'user_type', 'managed_regions_display', 'is_staff', 'is_active', 'display_date_joined',) list_display_links = ('first_name', 'last_name', 'fullname', 'email') ordering = ("-id",) search_fields = ('email', 'first_name', 'last_name', 'fullname', 'username',) filter_horizontal = ('user_permissions',) list_filter = [ "role", "user_type", "managed_regions", "is_active", "is_staff", ("last_login", RangeDateTimeFilter), ("date_joined", RangeDateTimeFilter), ] inlines = [LocationHistoryInline, VerificationDocumentInline] add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ( ('first_name', 'last_name'), ('fullname', 'email'), 'role', 'phone_number', ('title', 'rank'), ('institution_name', 'website'), 'address', 'birthdate', 'gender', 'avatar', 'skill', 'info' ), }), ) fieldsets = ( (None, { "fields": ( ("first_name", "last_name"), ("fullname", "email"), ("password",), ), }), (_("Profile Details"), { "classes": ["tab"], "fields": ( ("avatar",), ("phone_number", "gender"), ("title", "rank"), ("institution_name", "website"), ("address",), ("birthdate", "language"), ("skill",), ("info",), ), }), (_("Access & Role"), { "classes": ["tab"], "fields": ( ("is_active", "is_staff", "is_superuser"), "custom_permissions", ("role", "user_type"), "groups", "managed_regions", ), }), (_("Raw Database Permissions"), { "classes": ["tab", "collapse"], "description": _("Low-level Django database permissions for internal operations. Application features are managed in the Permissions Matrix above."), "fields": ( "user_permissions", ), }), (_("Timestamps & Metadata"), { "classes": ["tab"], "fields": ( ("date_joined", "last_login"), ("deleted_at",), ), }), ) readonly_fields = ('date_joined', 'last_login') @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=_("Managed Regions")) def managed_regions_display(self, instance: User): regions = instance.managed_regions.all() if not regions.exists(): if instance.user_type == 'regional_admin': return format_html('{}', _("None (Unassigned)")) return "-" names = [r.name for r in regions[:3]] text = ", ".join(names) if regions.count() > 3: text += f" (+{regions.count() - 3})" return text class GroupAdmin(BaseGroupAdmin, ModelAdmin): list_display = ('name', 'permissions_count') list_display_links = ('name',) 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 ngettext("{count} permission", "{count} permissions", count).format(count=count) if count > 0 else "-" def has_add_permission(self, request): return bool(request.user and request.user.is_authenticated and (request.user.is_staff or request.user.is_superuser or request.user.can_access_admin_panel())) def has_change_permission(self, request, obj=None): return bool(request.user and request.user.is_authenticated and (request.user.is_staff or request.user.is_superuser or request.user.can_access_admin_panel())) def has_delete_permission(self, request, obj=None): return bool(request.user and request.user.is_authenticated and (request.user.is_staff or request.user.is_superuser or request.user.can_access_admin_panel())) # ========================================================= # 2. Registrations # ========================================================= try: admin.site.unregister(User) except admin.sites.NotRegistered: pass try: admin.site.register(User, UserAdmin) except admin.sites.AlreadyRegistered: pass project_admin_site.register(User, UserAdmin) project_admin_site.register(Group, GroupAdmin) try: admin.site.unregister(TokenProxy) except admin.sites.NotRegistered: pass