Refactor common functions for creating admin user list, peformance tweaks

This commit is contained in:
ErikKalkoken 2020-02-08 00:51:13 +01:00
parent c1388bf23f
commit 47babf2ed7
5 changed files with 179 additions and 340 deletions

View File

@ -100,63 +100,153 @@ class UserProfileInline(admin.StackedInline):
return False return False
class UserAdmin(BaseUserAdmin): def user_profile_pic(obj):
"""profile pic column data for user objects
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists (requires CSS)
""" """
Extending Django's UserAdmin model user_obj = obj.user if hasattr(obj, 'user') else obj
if user_obj.profile.main_character:
return format_html(
'<img src="{}" class="img-circle">',
user_obj.profile.main_character.portrait_url(size=32)
)
else:
return ''
user_profile_pic.short_description = ''
def user_username(obj):
"""user column data for user objects
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
link = reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
args=(obj.pk,)
)
user_obj = obj.user if hasattr(obj, 'user') else obj
return format_html(
'<strong><a href="{}">{}</a></strong><br>{}',
link,
user_obj.username,
user_obj.profile.main_character.character_name \
if user_obj.profile.main_character else ''
)
user_username.short_description = 'user / main'
user_username.admin_order_field = 'username'
def user_main_organization(obj):
"""main organization column data for user objects
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
user_obj = obj.user if hasattr(obj, 'user') else obj
if user_obj.profile.main_character:
corporation = user_obj.profile.main_character.corporation_name
else:
corporation = ''
if (user_obj.profile.main_character
and user_obj.profile.main_character.alliance_id
):
alliance = user_obj.profile.main_character.alliance_name
else:
alliance = ''
return format_html('{}<br>{}',
corporation,
alliance
)
user_main_organization.short_description = 'Corporation / Alliance (Main)'
user_main_organization.admin_order_field = \
'profile__main_character__corporation_name'
class MainCorporationsFilter(admin.SimpleListFilter):
"""Custom filter to filter on corporations from mains only
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
title = 'corporation'
parameter_name = 'main_corporations'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(userprofile=None)\
.values('corporation_id', 'corporation_name')\
.distinct()\
.order_by(Lower('corporation_name'))
return tuple(
[(x['corporation_id'], x['corporation_name']) for x in qs]
)
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs\
.filter(profile__main_character__corporation_id=\
self.value())
else:
return qs\
.filter(user__profile__main_character__corporation_id=\
self.value())
class MainAllianceFilter(admin.SimpleListFilter):
"""Custom filter to filter on alliances from mains only
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
title = 'alliance'
parameter_name = 'main_alliances'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(alliance_id=None)\
.exclude(userprofile=None)\
.values('alliance_id', 'alliance_name')\
.distinct()\
.order_by(Lower('alliance_name'))
return tuple(
[(x['alliance_id'], x['alliance_name']) for x in qs]
)
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs\
.filter(profile__main_character__alliance_id=self.value())
else:
return qs\
.filter(user__profile__main_character__alliance_id=\
self.value())
class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model
Behavior of groups and characters columns can be configured via settings
""" """
class Media: class Media:
css = { css = {
"all": ("authentication/css/admin.css",) "all": ("authentication/css/admin.css",)
} }
class MainCorporationsFilter(admin.SimpleListFilter):
"""Custom filter to filter on corporations from mains only"""
title = 'corporation'
parameter_name = 'main_corporations'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(userprofile=None)\
.values('corporation_id', 'corporation_name')\
.distinct()\
.order_by(Lower('corporation_name'))
return tuple(
[(x['corporation_id'], x['corporation_name']) for x in qs]
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset\
.filter(profile__main_character__corporation_id=self.value())
class MainAllianceFilter(admin.SimpleListFilter):
"""Custom filter to filter on alliances from mains only"""
title = 'alliance'
parameter_name = 'main_alliances'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(alliance_id=None)\
.exclude(userprofile=None)\
.values('alliance_id', 'alliance_name')\
.distinct()\
.order_by(Lower('alliance_name'))
return tuple(
[(x['alliance_id'], x['alliance_name']) for x in qs]
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset\
.filter(profile__main_character__alliance_id=self.value())
class RealGroupsFilter(admin.SimpleListFilter): class RealGroupsFilter(admin.SimpleListFilter):
"""Custom filter to get groups w/o Autogroups""" """Custom filter to get groups w/o Autogroups"""
@ -167,8 +257,8 @@ class UserAdmin(BaseUserAdmin):
qs = Group.objects.all().order_by(Lower('name')) qs = Group.objects.all().order_by(Lower('name'))
if _has_auto_groups: if _has_auto_groups:
qs = qs\ qs = qs\
.filter(managedalliancegroup__exact=None)\ .filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__exact=None) .filter(managedcorpgroup__isnull=True)
return tuple([(x.pk, x.name) for x in qs]) return tuple([(x.pk, x.name) for x in qs])
def queryset(self, request, queryset): def queryset(self, request, queryset):
@ -208,20 +298,24 @@ class UserAdmin(BaseUserAdmin):
# Check update_groups is redefined/overloaded # Check update_groups is redefined/overloaded
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__: if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
action = make_service_hooks_update_groups_action(svc) action = make_service_hooks_update_groups_action(svc)
actions[action.__name__] = (action, actions[action.__name__] = (
action.__name__, action,
action.short_description) action.__name__,
action.short_description
)
# Create sync nickname action if service implements it # Create sync nickname action if service implements it
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__: if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
action = make_service_hooks_sync_nickname_action(svc) action = make_service_hooks_sync_nickname_action(svc)
actions[action.__name__] = (action, actions[action.__name__] = (
action.__name__, action, action.__name__,
action.short_description) action.short_description
)
return actions return actions
def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str: def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str:
"""converts list of strings into HTML with cutoff and tooltip when > max""" """converts list of strings into HTML with cutoff and tooltip"""
items_truncated_str = ', '.join(my_items[:max_items]) items_truncated_str = ', '.join(my_items[:max_items])
if len(my_items) <= max_items: if len(my_items) <= max_items:
return items_truncated_str return items_truncated_str
@ -242,11 +336,11 @@ class UserAdmin(BaseUserAdmin):
show_full_result_count = True show_full_result_count = True
list_display = ( list_display = (
'_profile_pic', user_profile_pic,
'_user', user_username,
'_state', '_state',
'_groups', '_groups',
'_main_organization', user_main_organization,
'_characters', '_characters',
'is_active', 'is_active',
'date_joined', 'date_joined',
@ -269,67 +363,13 @@ class UserAdmin(BaseUserAdmin):
'character_ownerships__character__character_name' 'character_ownerships__character__character_name'
) )
def _profile_pic(self, obj):
"""profile pic column data for user objects"""
if obj.profile.main_character:
return format_html(
'<img src="{}" class="img-circle">',
obj.profile.main_character.portrait_url(size=32)
)
else:
return ''
_profile_pic.short_description = ''
def _user(self, obj):
"""user column data for user objects"""
link = reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
args=(obj.pk,)
)
return format_html(
'<strong><a href="{}">{}</a></strong><br>{}',
link,
obj.username,
obj.profile.main_character.character_name \
if obj.profile.main_character else ''
)
_user.short_description = 'user / main'
_user.admin_order_field = 'username'
def _main_organization(self, obj):
"""main organization column data for user objects"""
if obj.profile.main_character:
corporation = obj.profile.main_character.corporation_name
else:
corporation = ''
if (obj.profile.main_character
and obj.profile.main_character.alliance_id
):
alliance = obj.profile.main_character.alliance_name
else:
alliance = ''
return format_html('{}<br>{}',
corporation,
alliance
)
_main_organization.short_description = 'Corporation / Alliance (Main)'
_main_organization.admin_order_field = \
'profile__main_character__corporation_name'
def _characters(self, obj): def _characters(self, obj):
my_characters = [ my_characters = [
x.character.character_name x.character.character_name
for x in CharacterOwnership.objects\ for x in CharacterOwnership.objects\
.filter(user=obj)\ .filter(user=obj)\
.order_by('character__character_name') .order_by('character__character_name')\
.select_related()
] ]
return self._list_2_html_w_tooltips( return self._list_2_html_w_tooltips(
my_characters, my_characters,
@ -352,8 +392,8 @@ class UserAdmin(BaseUserAdmin):
else: else:
my_groups = [ my_groups = [
x.name for x in obj.groups\ x.name for x in obj.groups\
.filter(managedalliancegroup=None)\ .filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup=None)\ .filter(managedcorpgroup__isnull=True)\
.order_by('name') .order_by('name')
] ]
@ -425,61 +465,12 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
css = { css = {
"all": ("authentication/css/admin.css",) "all": ("authentication/css/admin.css",)
} }
class MainCorporationsFilter(admin.SimpleListFilter):
"""Custom filter to filter on corporations from mains only"""
title = 'corporation'
parameter_name = 'main_corporations'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(userprofile=None)\
.values('corporation_id', 'corporation_name')\
.distinct()\
.order_by(Lower('corporation_name'))
return tuple(
[(x['corporation_id'], x['corporation_name']) for x in qs]
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset.filter(
user__profile__main_character__corporation_id=self.value()
)
class MainAllianceFilter(admin.SimpleListFilter):
"""Custom filter to filter on alliances from mains only"""
title = 'alliance'
parameter_name = 'main_alliances'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(alliance_id=None)\
.exclude(userprofile=None)\
.values('alliance_id', 'alliance_name')\
.distinct()\
.order_by(Lower('alliance_name'))
return tuple(
[(x['alliance_id'], x['alliance_name']) for x in qs]
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset.filter(
user__profile__main_character__alliance_id=self.value()
)
list_select_related = True list_select_related = True
list_display = ( list_display = (
'_profile_pic', user_profile_pic,
'_user', user_username,
'_main_organization', user_main_organization,
'character', 'character',
) )
search_fields = ( search_fields = (
@ -498,61 +489,6 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
return 'owner_hash', 'character' return 'owner_hash', 'character'
return tuple() return tuple()
def _profile_pic(self, obj):
"""profile pic column data for user objects"""
if obj.user.profile.main_character:
return format_html(
'<img src="{}" class="img-circle">',
obj.user.profile.main_character.portrait_url(size=32)
)
else:
return ''
_profile_pic.short_description = ''
def _user(self, obj):
"""user column data for user objects"""
link = reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
args=(obj.pk,)
)
return format_html(
'<strong><a href="{}">{}</a></strong><br>{}',
link,
obj.user.username,
obj.user.profile.main_character.character_name \
if obj.user.profile.main_character else ''
)
_user.short_description = 'user / main'
_user.admin_order_field = 'user__username'
def _main_organization(self, obj):
"""main organization column data for user objects"""
if obj.user.profile.main_character:
corporation = obj.user.profile.main_character.corporation_name
else:
corporation = ''
if (obj.user.profile.main_character
and obj.user.profile.main_character.alliance_id
):
alliance = obj.user.profile.main_character.alliance_name
else:
alliance = ''
return format_html('{}<br>{}',
corporation,
alliance
)
_main_organization.short_description = 'Corporation / Alliance (Main)'
_main_organization.admin_order_field = \
'user__profile__main_character__corporation_name'
@admin.register(OwnershipRecord) @admin.register(OwnershipRecord)
class OwnershipRecordAdmin(BaseOwnershipAdmin): class OwnershipRecordAdmin(BaseOwnershipAdmin):

View File

@ -4,7 +4,7 @@ CSS for allianceauth admin site
/* styling for profile pic */ /* styling for profile pic */
.img-circle { border-radius: 50%; } .img-circle { border-radius: 50%; }
.column-_profile_pic { width: 50px; } .column-user_profile_pic { width: 50px; }
/* tooltip */ /* tooltip */
.tooltip { .tooltip {

View File

@ -50,11 +50,14 @@ if _has_auto_groups:
value = self.value() value = self.value()
if value == 'Yes': if value == 'Yes':
return queryset.exclude( return queryset.exclude(
managedalliancegroup__exact=None, managedalliancegroup__isnull=True,
managedcorpgroup__exact=None managedcorpgroup__isnull=True
) )
elif value == 'No': elif value == 'No':
return queryset.filter(managedalliancegroup__exact=None).filter(managedcorpgroup__exact=None) return queryset.filter(
managedalliancegroup__isnull=True,
managedcorpgroup__isnull=True
)
else: else:
return queryset return queryset

View File

@ -6,61 +6,13 @@ from django.utils.html import format_html
from allianceauth import hooks from allianceauth import hooks
from allianceauth.eveonline.models import EveCharacter from allianceauth.eveonline.models import EveCharacter
from allianceauth.authentication.admin import user_profile_pic, \
user_username, user_main_organization, MainCorporationsFilter,\
MainAllianceFilter
from .models import NameFormatConfig from .models import NameFormatConfig
class MainCorporationsFilter(admin.SimpleListFilter):
"""Custom filter to show corporations from service users only
To be used together with ServicesUserAdmin class
"""
title = 'corporation'
parameter_name = 'main_corporations'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(userprofile=None)\
.values('corporation_id', 'corporation_name')\
.distinct()\
.order_by(Lower('corporation_name'))
return tuple(
[(x['corporation_id'], x['corporation_name']) for x in qs]
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset\
.filter(user__profile__main_character__corporation_id=self.value())
class MainAllianceFilter(admin.SimpleListFilter):
"""Custom filter to show alliances from service users only
To be used together with ServicesUserAdmin class
"""
title = 'alliance'
parameter_name = 'main_alliances'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(alliance_id=None)\
.exclude(userprofile=None)\
.values('alliance_id', 'alliance_name')\
.distinct()\
.order_by(Lower('alliance_name'))
return tuple(
[(x['alliance_id'], x['alliance_name']) for x in qs]
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset\
.filter(user__profile__main_character__alliance_id=self.value())
class ServicesUserAdmin(admin.ModelAdmin): class ServicesUserAdmin(admin.ModelAdmin):
"""Parent class for UserAdmin classes for all services""" """Parent class for UserAdmin classes for all services"""
class Media: class Media:
@ -75,9 +27,9 @@ class ServicesUserAdmin(admin.ModelAdmin):
ordering = ('user__username', ) ordering = ('user__username', )
list_select_related = True list_select_related = True
list_display = ( list_display = (
'_profile_pic', user_profile_pic,
'_user', user_username,
'_main_organization', user_main_organization,
'_date_joined' '_date_joined'
) )
list_filter = ( list_filter = (
@ -86,58 +38,6 @@ class ServicesUserAdmin(admin.ModelAdmin):
'user__date_joined' 'user__date_joined'
) )
def _profile_pic(self, obj):
if obj.user.profile.main_character:
return format_html(
'<img src="{}" class="img-circle">',
obj.user.profile.main_character.portrait_url(size=32)
)
else:
return ''
_profile_pic.short_description = ''
def _user(self, obj):
link = reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
args=(obj.pk,)
)
return format_html(
'<strong><a href="{}">{}</a></strong><br>{}',
link,
obj.user.username,
obj.user.profile.main_character.character_name \
if obj.user.profile.main_character else ''
)
_user.short_description = 'user / main'
_user.admin_order_field = 'user__username'
def _main_organization(self, obj):
if obj.user.profile.main_character:
corporation = obj.user.profile.main_character.corporation_name
else:
corporation = ''
if (obj.user.profile.main_character
and obj.user.profile.main_character.alliance_id
):
alliance = obj.user.profile.main_character.alliance_name
else:
alliance = ''
return format_html('{}<br>{}',
corporation,
alliance
)
_main_organization.short_description = 'Corporation / Alliance (Main)'
_main_organization.admin_order_field = \
'profile__main_character__corporation_name'
def _date_joined(self, obj): def _date_joined(self, obj):
return obj.user.date_joined return obj.user.date_joined

View File

@ -3,4 +3,4 @@ CSS for allianceauth admin site
*/ */
.img-circle { border-radius: 50%; } .img-circle { border-radius: 50%; }
.column-_profile_pic { width: 50px; } .column-user_profile_pic { width: 50px; }