227 lines
6.6 KiB
Python
227 lines
6.6 KiB
Python
from django.contrib import admin
|
|
from django.forms import ModelForm, CharField, TextInput
|
|
from django.utils.html import format_html
|
|
from django.utils.translation import gettext, gettext_lazy as _, ungettext
|
|
from django.contrib.auth.admin import UserAdmin as OldUserAdmin
|
|
from django.core.exceptions import ValidationError
|
|
from django.contrib.auth.forms import UserChangeForm
|
|
|
|
from django_ace import AceWidget
|
|
|
|
from judge.models import Profile, ProfileInfo
|
|
from judge.widgets import AdminPagedownWidget, AdminSelect2Widget
|
|
|
|
from reversion.admin import VersionAdmin
|
|
|
|
import re
|
|
|
|
|
|
class ProfileForm(ModelForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super(ProfileForm, self).__init__(*args, **kwargs)
|
|
if "current_contest" in self.base_fields:
|
|
# form.fields['current_contest'] does not exist when the user has only view permission on the model.
|
|
self.fields[
|
|
"current_contest"
|
|
].queryset = self.instance.contest_history.select_related("contest").only(
|
|
"contest__name", "user_id", "virtual"
|
|
)
|
|
self.fields["current_contest"].label_from_instance = (
|
|
lambda obj: "%s v%d" % (obj.contest.name, obj.virtual)
|
|
if obj.virtual
|
|
else obj.contest.name
|
|
)
|
|
|
|
class Meta:
|
|
widgets = {
|
|
"timezone": AdminSelect2Widget,
|
|
"language": AdminSelect2Widget,
|
|
"ace_theme": AdminSelect2Widget,
|
|
"current_contest": AdminSelect2Widget,
|
|
}
|
|
if AdminPagedownWidget is not None:
|
|
widgets["about"] = AdminPagedownWidget
|
|
|
|
|
|
class TimezoneFilter(admin.SimpleListFilter):
|
|
title = _("timezone")
|
|
parameter_name = "timezone"
|
|
|
|
def lookups(self, request, model_admin):
|
|
return (
|
|
Profile.objects.values_list("timezone", "timezone")
|
|
.distinct()
|
|
.order_by("timezone")
|
|
)
|
|
|
|
def queryset(self, request, queryset):
|
|
if self.value() is None:
|
|
return queryset
|
|
return queryset.filter(timezone=self.value())
|
|
|
|
|
|
class ProfileInfoInline(admin.StackedInline):
|
|
model = ProfileInfo
|
|
can_delete = False
|
|
verbose_name_plural = "profile info"
|
|
fk_name = "profile"
|
|
|
|
|
|
class ProfileAdmin(VersionAdmin):
|
|
fields = (
|
|
"user",
|
|
"display_rank",
|
|
"about",
|
|
"organizations",
|
|
"timezone",
|
|
"language",
|
|
"ace_theme",
|
|
"last_access",
|
|
"ip",
|
|
"mute",
|
|
"is_unlisted",
|
|
"notes",
|
|
"is_totp_enabled",
|
|
"current_contest",
|
|
)
|
|
readonly_fields = ("user",)
|
|
list_display = (
|
|
"admin_user_admin",
|
|
"email",
|
|
"is_totp_enabled",
|
|
"timezone_full",
|
|
"date_joined",
|
|
"last_access",
|
|
"ip",
|
|
"show_public",
|
|
)
|
|
ordering = ("user__username",)
|
|
search_fields = ("user__username", "ip", "user__email")
|
|
list_filter = ("language", TimezoneFilter)
|
|
actions = ("recalculate_points",)
|
|
actions_on_top = True
|
|
actions_on_bottom = True
|
|
form = ProfileForm
|
|
inlines = (ProfileInfoInline,)
|
|
|
|
def get_queryset(self, request):
|
|
return super(ProfileAdmin, self).get_queryset(request).select_related("user")
|
|
|
|
def get_fields(self, request, obj=None):
|
|
if request.user.has_perm("judge.totp"):
|
|
fields = list(self.fields)
|
|
fields.insert(fields.index("is_totp_enabled") + 1, "totp_key")
|
|
return tuple(fields)
|
|
else:
|
|
return self.fields
|
|
|
|
def get_readonly_fields(self, request, obj=None):
|
|
fields = self.readonly_fields
|
|
if not request.user.has_perm("judge.totp"):
|
|
fields += ("is_totp_enabled",)
|
|
return fields
|
|
|
|
def show_public(self, obj):
|
|
return format_html(
|
|
'<a href="{0}" style="white-space:nowrap;">{1}</a>',
|
|
obj.get_absolute_url(),
|
|
gettext("View on site"),
|
|
)
|
|
|
|
show_public.short_description = ""
|
|
|
|
def admin_user_admin(self, obj):
|
|
return obj.username
|
|
|
|
admin_user_admin.admin_order_field = "user__username"
|
|
admin_user_admin.short_description = _("User")
|
|
|
|
def email(self, obj):
|
|
return obj.email
|
|
|
|
email.admin_order_field = "user__email"
|
|
email.short_description = _("Email")
|
|
|
|
def timezone_full(self, obj):
|
|
return obj.timezone
|
|
|
|
timezone_full.admin_order_field = "timezone"
|
|
timezone_full.short_description = _("Timezone")
|
|
|
|
def date_joined(self, obj):
|
|
return obj.user.date_joined
|
|
|
|
date_joined.admin_order_field = "user__date_joined"
|
|
date_joined.short_description = _("date joined")
|
|
|
|
def recalculate_points(self, request, queryset):
|
|
count = 0
|
|
for profile in queryset:
|
|
profile.calculate_points()
|
|
count += 1
|
|
self.message_user(
|
|
request,
|
|
ungettext(
|
|
"%d user have scores recalculated.",
|
|
"%d users have scores recalculated.",
|
|
count,
|
|
)
|
|
% count,
|
|
)
|
|
|
|
recalculate_points.short_description = _("Recalculate scores")
|
|
|
|
|
|
class UserForm(UserChangeForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields["username"].help_text = _(
|
|
"Username can only contain letters, digits, and underscores."
|
|
)
|
|
|
|
def clean_username(self):
|
|
username = self.cleaned_data.get("username")
|
|
if not re.match(r"^\w+$", username):
|
|
raise ValidationError(
|
|
_("Username can only contain letters, digits, and underscores.")
|
|
)
|
|
return username
|
|
|
|
|
|
class UserAdmin(OldUserAdmin):
|
|
# Customize the fieldsets for adding and editing users
|
|
form = UserForm
|
|
fieldsets = (
|
|
(None, {"fields": ("username", "password")}),
|
|
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
|
(
|
|
"Permissions",
|
|
{
|
|
"fields": (
|
|
"is_active",
|
|
"is_staff",
|
|
"is_superuser",
|
|
"groups",
|
|
"user_permissions",
|
|
)
|
|
},
|
|
),
|
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
|
)
|
|
|
|
readonly_fields = ("last_login", "date_joined")
|
|
|
|
def get_readonly_fields(self, request, obj=None):
|
|
fields = self.readonly_fields
|
|
if not request.user.is_superuser:
|
|
fields += (
|
|
"is_staff",
|
|
"is_active",
|
|
"is_superuser",
|
|
"groups",
|
|
"user_permissions",
|
|
)
|
|
return fields
|
|
|
|
def has_add_permission(self, request):
|
|
return False
|