You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

398 lines
12 KiB

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
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,
)
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),
]
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"],
}
),
(
_('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", 'user_type', ]
@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 "-"
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',
)
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',),
}),
)
@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(
'<span title="{}">{}</span>',
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(
"""
<div class="flex flex-row gap-2 items-center">
<span class="truncate">{}</span>
<a href="/admin/course/course/{}/change/" class="leading-none ml-auto">
<span class="material-symbols-outlined leading-none text-base-500">visibility</span>
</a>
</div>
""",
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(
'<a href="/admin/course/course/{}/change/" class="leading-none">'
'<span class="material-symbols-outlined leading-none text-base-500">visibility</span>'
'</a>',
instance.id
)
edit_link.short_description = _("Edit")
class ProfessorUserAdmin(UserAdmin):
list_display = (
'display_header', 'email', 'courses_count'
)
list_sections = [CourseTableSection]
save_as = True
@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(
"""
<div class="flex flex-row gap-2 items-center">
<span class="truncate">{}</span>
<a href="/admin/course/course/{}/change/" class="leading-none ml-auto">
<span class="material-symbols-outlined leading-none text-base-500">visibility</span>
</a>
</div>
""",
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)