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 django.templatetags.static import static from rest_framework.authtoken.models import TokenProxy from unfold.admin import ModelAdmin, StackedInline from unfold.contrib.forms.widgets import WysiwygWidget from unfold.decorators import display from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.sections import TableSection from unfold.contrib.filters.admin import RangeDateTimeFilter # Import Models from apps.account.models import User, ClientUser, StudentUser, ProfessorUser, LocationHistory # Import Admin Sites from utils from utils.admin import project_admin_site, dovoodi_admin_site , is_dovoodi_panel from apps.account.admin.location import LocationHistoryInline from unfold.widgets import UnfoldAdminSelectWidget # ========================================================= # 1. Base User Admin (Logic Shared by all User types) # ========================================================= class UserAdminCreationForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = User fields = ("fullname", "email") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if 'fullname' in self.fields: self.fields['fullname'].required = True 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 class UserAdminChangeForm(UserChangeForm): class Meta(UserChangeForm.Meta): model = User def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if 'fullname' in self.fields: self.fields['fullname'].required = True if 'email' in self.fields: self.fields['email'].required = True class UserAdmin(ModelAdmin, BaseUserAdmin): form = UserAdminChangeForm add_form = UserAdminCreationForm 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"] def get_fieldsets(self, request, obj=None): # UserAdmin.get_fieldsets returns add_fieldsets when obj is None fieldsets = super().get_fieldsets(request, obj) if is_dovoodi_panel(request): new_fieldsets = [] for name, options in fieldsets: # Hide entire Permissions section if name == _('Permissions'): continue new_options = options.copy() # Hide skill field inside "Basic Information" (Edit) or the titleless section (Add) if name == _("Basic Information") or name is None: fields = list(new_options.get("fields", [])) new_fields = [] for f in fields: if isinstance(f, (list, tuple)): inner_f = [inner for inner in f if inner != 'skill'] if inner_f: new_fields.append(tuple(inner_f)) elif f != 'skill': new_fields.append(f) new_options["fields"] = tuple(new_fields) new_fieldsets.append((name, new_options)) return tuple(new_fieldsets) return fieldsets @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 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) # ========================================================= # 2. Specific User Type Admins # ========================================================= class GuestUserAdmin(UserAdmin): list_display = ('device_id', 'device_os', 'is_active', 'display_date_joined',) 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 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 ngettext("{count} permission", "{count} permissions", count).format(count=count) if count > 0 else "-" # ========================================================= # 3. Registrations (SAFE METHOD) # ========================================================= # A. DEFAULT DJANGO ADMIN (SAFE REGISTRATION) # This is required because plugins like 'django-filer' expect User to be registered here. try: admin.site.unregister(User) except admin.sites.NotRegistered: pass try: admin.site.register(User, UserAdmin) except admin.sites.AlreadyRegistered: pass # B. PROJECT ADMIN SITE (Imam Javad) project_admin_site.register(User, UserAdmin) project_admin_site.register(ClientUser, GuestUserAdmin) project_admin_site.register(Group, GroupAdmin) # C. DOVOODI ADMIN SITE dovoodi_admin_site.register(User, UserAdmin) dovoodi_admin_site.register(ClientUser, GuestUserAdmin) dovoodi_admin_site.register(Group, GroupAdmin) # D. Unregister TokenProxy safely (Cleaner UI) try: admin.site.unregister(TokenProxy) except admin.sites.NotRegistered: pass