From 2b8bfbe54464a16ec25cdb6e657b78e0be22df57 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Wed, 5 Feb 2020 17:48:06 +0100 Subject: [PATCH 01/16] Improve user and group admin --- allianceauth/authentication/admin.py | 35 +++++++++- allianceauth/groupmanagement/admin.py | 96 ++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 894ab2f5..ff21f902 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -106,14 +106,45 @@ class UserAdmin(BaseUserAdmin): action.short_description) return actions - list_filter = BaseUserAdmin.list_filter + ('profile__state',) inlines = BaseUserAdmin.inlines + [UserProfileInline] - list_display = ('username', 'email', 'get_main_character', 'get_state', 'is_active') + + list_select_related = True + + list_filter = BaseUserAdmin.list_filter + ( + 'profile__main_character__corporation_name', + 'profile__main_character__alliance_name', + 'profile__state', + 'date_joined' + ) + + list_display = ( + 'username', + 'get_main_character', + 'get_main_corporation', + 'get_main_alliance', + 'get_state', + 'date_joined', + 'is_active' + ) def get_main_character(self, obj): return obj.profile.main_character get_main_character.short_description = "Main Character" + def get_main_corporation(self, obj): + if obj.profile.main_character: + return obj.profile.main_character.corporation_name + else: + return None + get_main_corporation.short_description = "Main Corporation" + + def get_main_alliance(self, obj): + if obj.profile.main_character: + return obj.profile.main_character.alliance_name + else: + return None + get_main_alliance.short_description = "Main Alliance" + def get_state(self, obj): return obj.profile.state get_state.short_description = "State" diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 2fc69ca7..6ddb8414 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -1,11 +1,23 @@ +from django.conf import settings + from django.contrib import admin from django.contrib.auth.models import Group as BaseGroup -from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed +from django.db.models import Count +from django.db.models.signals import pre_save, post_save, pre_delete, \ + post_delete, m2m_changed from django.dispatch import receiver + from .models import AuthGroup from .models import GroupRequest from . import signals +if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: + _has_auto_groups = True + from allianceauth.eveonline.autogroups.models import * +else: + _has_auto_groups = False + + class AuthGroupInlineAdmin(admin.StackedInline): model = AuthGroup filter_horizontal = ('group_leaders', 'group_leader_groups', 'states',) @@ -23,10 +35,92 @@ class AuthGroupInlineAdmin(admin.StackedInline): return request.user.has_perm('auth.change_group') +if _has_auto_groups: + class IsAutoGroupFilter(admin.SimpleListFilter): + title = 'auto group' + parameter_name = 'auto_group' + + def lookups(self, request, model_admin): + return ( + ('Yes', 'Yes'), + ('No', 'No'), + ) + + def queryset(self, request, queryset): + value = self.value() + if value == 'Yes': + return queryset.exclude( + managedalliancegroup__exact=None, + managedcorpgroup__exact=None + ) + elif value == 'No': + return queryset.filter(managedalliancegroup__exact=None).filter(managedcorpgroup__exact=None) + else: + return queryset + + class GroupAdmin(admin.ModelAdmin): + list_select_related = True + list_display = ( + 'name', + 'description', + 'member_count', + 'has_leader', + '_attributes' + ) + + list_filter = ( + 'authgroup__internal', + 'authgroup__hidden', + 'authgroup__open', + 'authgroup__public', + IsAutoGroupFilter + ) + filter_horizontal = ('permissions',) inlines = (AuthGroupInlineAdmin,) + def get_queryset(self, request): + queryset = super().get_queryset(request) + queryset = queryset.annotate( + _member_count=Count('user', distinct=True), + ) + return queryset + + def description(self, obj): + return obj.authgroup.description + + def member_count(self, obj): + return obj._member_count + + member_count.admin_order_field = '_member_count' + + def has_leader(self, obj): + return obj.authgroup.group_leaders.exists() + + has_leader.boolean = True + + def _attributes(self, obj): + attributes = list() + if _has_auto_groups and (obj.managedalliancegroup_set.exists() + or obj.managedcorpgroup_set.exists() + ): + attributes.append('Auto Group') + elif obj.authgroup.internal: + attributes.append('Internal') + else: + if obj.authgroup.hidden: + attributes.append('Hidden') + if obj.authgroup.open: + attributes.append('Open') + if obj.authgroup.public: + attributes.append('Public') + if not attributes: + attributes.append('Default') + + return ', '.join(attributes) + + _attributes.short_description = "Attributes" class Group(BaseGroup): class Meta: From f902f59b313ac49e878086867baed8759404b3dd Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Wed, 5 Feb 2020 23:20:44 +0100 Subject: [PATCH 02/16] Improve user and group admin lists --- allianceauth/authentication/admin.py | 189 +++++++++++++++++++++----- allianceauth/groupmanagement/admin.py | 7 +- 2 files changed, 161 insertions(+), 35 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index ff21f902..34129db9 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -1,15 +1,28 @@ +from django.conf import settings + from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin -from django.contrib.auth.models import User as BaseUser, Permission as BasePermission -from django.utils.text import slugify +from django.contrib.auth.models import User as BaseUser, \ + Permission as BasePermission from django.db.models import Q from allianceauth.services.hooks import ServicesHook -from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed +from django.db.models.signals import pre_save, post_save, pre_delete, \ + post_delete, m2m_changed from django.dispatch import receiver -from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile, OwnershipRecord +from django.forms import ModelForm +from django.utils.safestring import mark_safe +from django.utils.text import slugify + +from allianceauth.authentication.models import State, get_guest_state,\ + CharacterOwnership, UserProfile, OwnershipRecord from allianceauth.hooks import get_hooks from allianceauth.eveonline.models import EveCharacter -from django.forms import ModelForm + +if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: + _has_auto_groups = True + from allianceauth.eveonline.autogroups.models import * +else: + _has_auto_groups = False def make_service_hooks_update_groups_action(service): @@ -83,6 +96,25 @@ class UserProfileInline(admin.StackedInline): return False +class MyGroupFilter(admin.SimpleListFilter): + title = 'group' + parameter_name = 'my_groups' + + def lookups(self, request, model_admin): + qs = Group.objects.all().order_by('name') + if _has_auto_groups: + qs = qs\ + .filter(managedalliancegroup__exact=None)\ + .filter(managedcorpgroup__exact=None) + return tuple([(x.pk, x.name) for x in qs]) + + def queryset(self, request, queryset): + if self.value() is None: + return queryset.all() + else: + return queryset.filter(groups__pk=self.value()) + + class UserAdmin(BaseUserAdmin): """ Extending Django's UserAdmin model @@ -106,48 +138,139 @@ class UserAdmin(BaseUserAdmin): action.short_description) return actions + inlines = BaseUserAdmin.inlines + [UserProfileInline] - list_select_related = True - - list_filter = BaseUserAdmin.list_filter + ( - 'profile__main_character__corporation_name', - 'profile__main_character__alliance_name', - 'profile__state', - 'date_joined' - ) + list_select_related = True + show_full_result_count = True list_display = ( - 'username', - 'get_main_character', - 'get_main_corporation', - 'get_main_alliance', - 'get_state', + '_profile_pic', + '_username', + '_state', + '_groups', + '_main_organization', + '_characters', + 'is_active', 'date_joined', - 'is_active' + '_role' + ) + list_display_links = None + + list_filter = ( + 'profile__state', + MyGroupFilter, + 'profile__main_character__corporation_name', + 'profile__main_character__alliance_name', + 'is_active', + 'date_joined', + 'is_staff', + 'is_superuser' + ) + search_fields = ( + 'username', + 'character_ownerships__character__character_name', + 'groups__name' ) - def get_main_character(self, obj): - return obj.profile.main_character - get_main_character.short_description = "Main Character" - - def get_main_corporation(self, obj): + def _profile_pic(self, obj): if obj.profile.main_character: - return obj.profile.main_character.corporation_name + return mark_safe( + ''.format( + obj.profile.main_character.portrait_url(size=32) + )) else: - return None - get_main_corporation.short_description = "Main Corporation" + return '' + _profile_pic.short_description = '' - def get_main_alliance(self, obj): + + def _username(self, obj): + #/admin////change/ + link = '/admin/authentication/{}/{}/change/'.format( + type(obj).__name__.lower(), + obj.pk + ) + return mark_safe('{}
{}'.format( + link, + obj.username, + obj.email + )) + + _username.short_description = 'user' + _username.admin_order_field = 'username' + + def _main_organization(self, obj): if obj.profile.main_character: - return obj.profile.main_character.alliance_name + corporation = obj.profile.main_character.corporation_name else: - return None - get_main_alliance.short_description = "Main Alliance" + corporation = '' + if (obj.profile.main_character + and obj.profile.main_character.alliance_id + ): + alliance = obj.profile.main_character.alliance_name + else: + alliance = '' + return mark_safe('{}
{}'.format(corporation, alliance)) - def get_state(self, obj): + _main_organization.short_description = 'Corporation / Alliance (Main)' + _main_organization.admin_order_field = \ + 'profile__main_character__corporation_name' + + + def _characters(self, obj): + alts = [ + x.character.character_name + for x in CharacterOwnership.objects\ + .filter(user=obj)\ + .order_by('character__character_name') + .exclude(character=obj.profile.main_character) + ] + if obj.profile.main_character: + result = [ + '{}'.format(obj.profile.main_character.character_name) + ] + else: + result = [] + return mark_safe(', '.join(result + alts)) + + _characters.short_description = 'characters' + + + def _state(self, obj): return obj.profile.state - get_state.short_description = "State" + + _state.short_description = 'state' + _state.admin_order_field = 'profile__state' + + + def _groups(self, obj): + if not _has_auto_groups: + my_groups = [x.name for x in obj.groups.order_by('name')] + else: + my_groups = [ + x.name for x in obj.groups\ + .filter(managedalliancegroup=None)\ + .filter(managedcorpgroup=None)\ + .order_by('name') + ] + + return ', '.join(my_groups) + + _groups.short_description = 'groups' + + + def _role(self, obj): + if obj.is_superuser: + role = 'Superuser' + elif obj.is_staff: + role = 'Staff' + else: + role = 'User' + + return role + + _role.short_description = 'role' + def has_change_permission(self, request, obj=None): return request.user.has_perm('auth.change_user') diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 6ddb8414..48969d08 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -102,9 +102,12 @@ class GroupAdmin(admin.ModelAdmin): def _attributes(self, obj): attributes = list() - if _has_auto_groups and (obj.managedalliancegroup_set.exists() + if (_has_auto_groups + and ( + obj.managedalliancegroup_set.exists() or obj.managedcorpgroup_set.exists() - ): + ) + ): attributes.append('Auto Group') elif obj.authgroup.internal: attributes.append('Internal') From b0448a45652916efb5a986bc794f2e24ad746369 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 6 Feb 2020 01:19:53 +0100 Subject: [PATCH 03/16] Further improvements --- allianceauth/authentication/admin.py | 92 ++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 34129db9..47772d18 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -4,19 +4,19 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User as BaseUser, \ Permission as BasePermission -from django.db.models import Q +from django.db.models import Q, F from allianceauth.services.hooks import ServicesHook from django.db.models.signals import pre_save, post_save, pre_delete, \ post_delete, m2m_changed from django.dispatch import receiver from django.forms import ModelForm -from django.utils.safestring import mark_safe +from django.utils.html import format_html from django.utils.text import slugify from allianceauth.authentication.models import State, get_guest_state,\ CharacterOwnership, UserProfile, OwnershipRecord from allianceauth.hooks import get_hooks -from allianceauth.eveonline.models import EveCharacter +from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: _has_auto_groups = True @@ -96,9 +96,10 @@ class UserProfileInline(admin.StackedInline): return False -class MyGroupFilter(admin.SimpleListFilter): +class RealGroupsFilter(admin.SimpleListFilter): + """Custom filter to get groups w/o Autogroups""" title = 'group' - parameter_name = 'my_groups' + parameter_name = 'real_groups' def lookups(self, request, model_admin): qs = Group.objects.all().order_by('name') @@ -113,7 +114,54 @@ class MyGroupFilter(admin.SimpleListFilter): return queryset.all() else: return queryset.filter(groups__pk=self.value()) - + + +class MainCorporationsFilter(admin.SimpleListFilter): + """Custom filter to show corporations from mains only""" + title = 'corporation' + parameter_name = 'main_corporations' + + def lookups(self, request, model_admin): + qs = UserProfile.objects\ + .exclude(main_character=None)\ + .values(corporation_id=F('main_character__corporation_id'))\ + .annotate(corporation_name=F('main_character__corporation_name'))\ + .distinct() + 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 show alliances from mains only""" + title = 'alliance' + parameter_name = 'main_alliances' + + def lookups(self, request, model_admin): + qs = UserProfile.objects\ + .exclude(main_character=None)\ + .exclude(main_character__alliance_id=None)\ + .values(alliance_id=F('main_character__alliance_id'))\ + .annotate(alliance_name=F('main_character__alliance_name'))\ + .distinct() + 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 UserAdmin(BaseUserAdmin): """ @@ -159,9 +207,11 @@ class UserAdmin(BaseUserAdmin): list_filter = ( 'profile__state', - MyGroupFilter, - 'profile__main_character__corporation_name', - 'profile__main_character__alliance_name', + RealGroupsFilter, + #'profile__main_character__corporation_name', + MainCorporationsFilter, + #'profile__main_character__alliance_name', + MainAllianceFilter, 'is_active', 'date_joined', 'is_staff', @@ -175,10 +225,10 @@ class UserAdmin(BaseUserAdmin): def _profile_pic(self, obj): if obj.profile.main_character: - return mark_safe( - ''.format( - obj.profile.main_character.portrait_url(size=32) - )) + return format_html( + '', + obj.profile.main_character.portrait_url(size=32) + ) else: return '' _profile_pic.short_description = '' @@ -190,11 +240,12 @@ class UserAdmin(BaseUserAdmin): type(obj).__name__.lower(), obj.pk ) - return mark_safe('{}
{}'.format( + return format_html( + '{}
{}', link, obj.username, obj.email - )) + ) _username.short_description = 'user' _username.admin_order_field = 'username' @@ -210,7 +261,10 @@ class UserAdmin(BaseUserAdmin): alliance = obj.profile.main_character.alliance_name else: alliance = '' - return mark_safe('{}
{}'.format(corporation, alliance)) + return format_html('{}
{}', + corporation, + alliance + ) _main_organization.short_description = 'Corporation / Alliance (Main)' _main_organization.admin_order_field = \ @@ -227,11 +281,13 @@ class UserAdmin(BaseUserAdmin): ] if obj.profile.main_character: result = [ - '{}'.format(obj.profile.main_character.character_name) + '{}'.format( + obj.profile.main_character.character_name + ) ] else: result = [] - return mark_safe(', '.join(result + alts)) + return format_html(', '.join(result + alts)) _characters.short_description = 'characters' From 458685026b1dcfb52564c784398c617502140589 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 6 Feb 2020 03:04:30 +0100 Subject: [PATCH 04/16] Add fields to discord and ts3 admin --- allianceauth/authentication/admin.py | 28 +++--- allianceauth/groupmanagement/admin.py | 90 +++++++------------ .../services/modules/discord/admin.py | 52 ++++++++++- .../services/modules/teamspeak3/admin.py | 55 +++++++++++- 4 files changed, 143 insertions(+), 82 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 47772d18..03263aef 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -189,12 +189,13 @@ class UserAdmin(BaseUserAdmin): inlines = BaseUserAdmin.inlines + [UserProfileInline] + ordering = ('username', ) list_select_related = True show_full_result_count = True list_display = ( '_profile_pic', - '_username', + '_user', '_state', '_groups', '_main_organization', @@ -234,7 +235,7 @@ class UserAdmin(BaseUserAdmin): _profile_pic.short_description = '' - def _username(self, obj): + def _user(self, obj): #/admin////change/ link = '/admin/authentication/{}/{}/change/'.format( type(obj).__name__.lower(), @@ -244,11 +245,12 @@ class UserAdmin(BaseUserAdmin): '{}
{}', link, obj.username, - obj.email + obj.profile.main_character.character_name \ + if obj.profile.main_character else '' ) - _username.short_description = 'user' - _username.admin_order_field = 'username' + _user.short_description = 'user / main' + _user.admin_order_field = 'username' def _main_organization(self, obj): if obj.profile.main_character: @@ -272,22 +274,12 @@ class UserAdmin(BaseUserAdmin): def _characters(self, obj): - alts = [ + return [ x.character.character_name for x in CharacterOwnership.objects\ .filter(user=obj)\ .order_by('character__character_name') - .exclude(character=obj.profile.main_character) - ] - if obj.profile.main_character: - result = [ - '{}'.format( - obj.profile.main_character.character_name - ) - ] - else: - result = [] - return format_html(', '.join(result + alts)) + ] _characters.short_description = 'characters' @@ -372,7 +364,7 @@ class StateAdmin(admin.ModelAdmin): class BaseOwnershipAdmin(admin.ModelAdmin): list_display = ('user', 'character') - search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name') + search_fields = ('user__user', 'character__character_name', 'character__corporation_name', 'character__alliance_name') def get_readonly_fields(self, request, obj=None): if obj and obj.pk: diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 48969d08..59c05716 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -35,95 +35,67 @@ class AuthGroupInlineAdmin(admin.StackedInline): return request.user.has_perm('auth.change_group') -if _has_auto_groups: - class IsAutoGroupFilter(admin.SimpleListFilter): - title = 'auto group' - parameter_name = 'auto_group' - - def lookups(self, request, model_admin): - return ( - ('Yes', 'Yes'), - ('No', 'No'), - ) - - def queryset(self, request, queryset): - value = self.value() - if value == 'Yes': - return queryset.exclude( - managedalliancegroup__exact=None, - managedcorpgroup__exact=None - ) - elif value == 'No': - return queryset.filter(managedalliancegroup__exact=None).filter(managedcorpgroup__exact=None) - else: - return queryset - - -class GroupAdmin(admin.ModelAdmin): +class GroupAdmin(admin.ModelAdmin): list_select_related = True + ordering = ('name', ) list_display = ( 'name', 'description', - 'member_count', - 'has_leader', - '_attributes' + '_properties', + '_member_count', + 'has_leader' ) list_filter = ( 'authgroup__internal', 'authgroup__hidden', 'authgroup__open', - 'authgroup__public', - IsAutoGroupFilter + 'authgroup__public' ) filter_horizontal = ('permissions',) inlines = (AuthGroupInlineAdmin,) def get_queryset(self, request): - queryset = super().get_queryset(request) - queryset = queryset.annotate( - _member_count=Count('user', distinct=True), + qs = super().get_queryset(request) + if _has_auto_groups: + qs = qs\ + .filter(managedalliancegroup__exact=None)\ + .filter(managedcorpgroup__exact=None) + qs = qs.annotate( + member_count=Count('user', distinct=True), ) - return queryset + return qs def description(self, obj): return obj.authgroup.description - def member_count(self, obj): - return obj._member_count + def _member_count(self, obj): + return obj.member_count - member_count.admin_order_field = '_member_count' + _member_count.short_description = 'Members' + _member_count.admin_order_field = 'member_count' def has_leader(self, obj): return obj.authgroup.group_leaders.exists() has_leader.boolean = True - def _attributes(self, obj): - attributes = list() - if (_has_auto_groups - and ( - obj.managedalliancegroup_set.exists() - or obj.managedcorpgroup_set.exists() - ) - ): - attributes.append('Auto Group') - elif obj.authgroup.internal: - attributes.append('Internal') - else: - if obj.authgroup.hidden: - attributes.append('Hidden') - if obj.authgroup.open: - attributes.append('Open') - if obj.authgroup.public: - attributes.append('Public') - if not attributes: - attributes.append('Default') + def _properties(self, obj): + properties = list() + if obj.authgroup.hidden: + properties.append('Hidden') + if obj.authgroup.open: + properties.append('Open') + if obj.authgroup.public: + properties.append('Public') + if not properties: + properties.append('Default') - return ', '.join(attributes) + return ', '.join(properties) + + _properties.short_description = "properties" - _attributes.short_description = "Attributes" class Group(BaseGroup): class Meta: diff --git a/allianceauth/services/modules/discord/admin.py b/allianceauth/services/modules/discord/admin.py index 1f47945d..bdcdb62a 100644 --- a/allianceauth/services/modules/discord/admin.py +++ b/allianceauth/services/modules/discord/admin.py @@ -3,7 +3,55 @@ from .models import DiscordUser class DiscordUserAdmin(admin.ModelAdmin): - list_display = ('user', 'uid') - search_fields = ('user__username', 'uid') + ordering = ('user__username', ) + list_select_related = True + + list_display = ( + 'user', + 'uid', + '_corporation', + '_alliance', + '_date_joined' + ) + search_fields = ( + 'user__username', + 'uid' + ) + list_filter = ( + 'user__profile__main_character__corporation_name', + 'user__profile__main_character__alliance_name', + 'user__date_joined', + ) + + def _corporation(self, obj): + if obj.user.profile.main_character: + return obj.user.profile.main_character.corporation_name + else: + return '' + + _corporation.short_description = 'corporation (main)' + _corporation.admin_order_field \ + = 'user__profile__main_character__corporation_name' + + + def _alliance(self, obj): + if (obj.user.profile.main_character + and obj.user.profile.main_character.alliance_id + ): + return obj.user.profile.main_character.alliance_name + else: + return '' + + _alliance.short_description = 'alliance (main)' + _alliance.admin_order_field \ + = 'user__profile__main_character__alliance_name' + + + def _date_joined(self, obj): + return obj.user.date_joined + + _date_joined.short_description = 'date joined' + _date_joined.admin_order_field = 'user__date_joined' + admin.site.register(DiscordUser, DiscordUserAdmin) diff --git a/allianceauth/services/modules/teamspeak3/admin.py b/allianceauth/services/modules/teamspeak3/admin.py index ffde6642..c3cc2403 100644 --- a/allianceauth/services/modules/teamspeak3/admin.py +++ b/allianceauth/services/modules/teamspeak3/admin.py @@ -2,9 +2,58 @@ from django.contrib import admin from .models import AuthTS, Teamspeak3User, StateGroup -class Teamspeak3UserAdmin(admin.ModelAdmin): - list_display = ('user', 'uid', 'perm_key') - search_fields = ('user__username', 'uid', 'perm_key') +class Teamspeak3UserAdmin(admin.ModelAdmin): + ordering = ('user__username', ) + list_select_related = True + + list_display = ( + 'user', + 'uid', + '_corporation', + '_alliance', + '_date_joined', + 'perm_key', + ) + search_fields = ( + 'user__username', + 'uid', + 'perm_key' + ) + list_filter = ( + 'user__profile__main_character__corporation_name', + 'user__profile__main_character__alliance_name', + 'user__date_joined', + ) + + def _corporation(self, obj): + if obj.user.profile.main_character: + return obj.user.profile.main_character.corporation_name + else: + return '' + + _corporation.short_description = 'corporation (main)' + _corporation.admin_order_field \ + = 'user__profile__main_character__corporation_name' + + + def _alliance(self, obj): + if (obj.user.profile.main_character + and obj.user.profile.main_character.alliance_id + ): + return obj.user.profile.main_character.alliance_name + else: + return '' + + _alliance.short_description = 'alliance (main)' + _alliance.admin_order_field \ + = 'user__profile__main_character__alliance_name' + + + def _date_joined(self, obj): + return obj.user.date_joined + + _date_joined.short_description = 'date joined' + _date_joined.admin_order_field = 'user__date_joined' class AuthTSgroupAdmin(admin.ModelAdmin): From 2d24d064d5f792a72d2f90c4c04272189413201e Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 6 Feb 2020 18:51:52 +0100 Subject: [PATCH 05/16] Improve admin site --- allianceauth/authentication/admin.py | 16 ++--- allianceauth/groupmanagement/admin.py | 18 +++-- .../services/modules/discord/admin.py | 55 ++++++++++++++- .../services/modules/teamspeak3/admin.py | 67 ++++++++++++++++++- 4 files changed, 134 insertions(+), 22 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 03263aef..874858b0 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -122,10 +122,9 @@ class MainCorporationsFilter(admin.SimpleListFilter): parameter_name = 'main_corporations' def lookups(self, request, model_admin): - qs = UserProfile.objects\ - .exclude(main_character=None)\ - .values(corporation_id=F('main_character__corporation_id'))\ - .annotate(corporation_name=F('main_character__corporation_name'))\ + qs = EveCharacter.objects\ + .exclude(userprofile=None)\ + .values('corporation_id', 'corporation_name')\ .distinct() return tuple([ (x['corporation_id'], x['corporation_name']) for x in qs @@ -145,11 +144,10 @@ class MainAllianceFilter(admin.SimpleListFilter): parameter_name = 'main_alliances' def lookups(self, request, model_admin): - qs = UserProfile.objects\ - .exclude(main_character=None)\ - .exclude(main_character__alliance_id=None)\ - .values(alliance_id=F('main_character__alliance_id'))\ - .annotate(alliance_name=F('main_character__alliance_name'))\ + qs = EveCharacter.objects\ + .exclude(alliance_id=None)\ + .exclude(userprofile=None)\ + .values('alliance_id', 'alliance_name')\ .distinct() return tuple([ (x['alliance_id'], x['alliance_name']) for x in qs diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 59c05716..35a86354 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -52,7 +52,8 @@ class GroupAdmin(admin.ModelAdmin): 'authgroup__open', 'authgroup__public' ) - + search_fields = ('name', 'authgroup__description') + filter_horizontal = ('permissions',) inlines = (AuthGroupInlineAdmin,) @@ -83,12 +84,15 @@ class GroupAdmin(admin.ModelAdmin): def _properties(self, obj): properties = list() - if obj.authgroup.hidden: - properties.append('Hidden') - if obj.authgroup.open: - properties.append('Open') - if obj.authgroup.public: - properties.append('Public') + if obj.authgroup.internal: + properties.append('Internal') + else: + if obj.authgroup.hidden: + properties.append('Hidden') + if obj.authgroup.open: + properties.append('Open') + if obj.authgroup.public: + properties.append('Public') if not properties: properties.append('Default') diff --git a/allianceauth/services/modules/discord/admin.py b/allianceauth/services/modules/discord/admin.py index bdcdb62a..d7ca52a6 100644 --- a/allianceauth/services/modules/discord/admin.py +++ b/allianceauth/services/modules/discord/admin.py @@ -1,7 +1,56 @@ from django.contrib import admin +from allianceauth.eveonline.models import EveCharacter + from .models import DiscordUser +class MainCorporationsFilter(admin.SimpleListFilter): + """Custom filter to show corporations from service users only""" + title = 'corporation' + parameter_name = 'main_corporations' + + def lookups(self, request, model_admin): + qs = EveCharacter.objects\ + .exclude(userprofile=None)\ + .exclude(userprofile__user__discord=None)\ + .values('corporation_id', 'corporation_name')\ + .distinct() + 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""" + title = 'alliance' + parameter_name = 'main_alliances' + + def lookups(self, request, model_admin): + qs = EveCharacter.objects\ + .exclude(alliance_id=None)\ + .exclude(userprofile=None)\ + .exclude(userprofile__user__discord=None)\ + .values('alliance_id', 'alliance_name')\ + .distinct() + 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 DiscordUserAdmin(admin.ModelAdmin): ordering = ('user__username', ) list_select_related = True @@ -17,9 +66,9 @@ class DiscordUserAdmin(admin.ModelAdmin): 'user__username', 'uid' ) - list_filter = ( - 'user__profile__main_character__corporation_name', - 'user__profile__main_character__alliance_name', + list_filter = ( + MainCorporationsFilter, + MainAllianceFilter, 'user__date_joined', ) diff --git a/allianceauth/services/modules/teamspeak3/admin.py b/allianceauth/services/modules/teamspeak3/admin.py index c3cc2403..26dd1291 100644 --- a/allianceauth/services/modules/teamspeak3/admin.py +++ b/allianceauth/services/modules/teamspeak3/admin.py @@ -1,7 +1,56 @@ from django.contrib import admin +from allianceauth.eveonline.models import EveCharacter + from .models import AuthTS, Teamspeak3User, StateGroup +class MainCorporationsFilter(admin.SimpleListFilter): + """Custom filter to show corporations from service users only""" + title = 'corporation' + parameter_name = 'main_corporations' + + def lookups(self, request, model_admin): + qs = EveCharacter.objects\ + .exclude(userprofile=None)\ + .exclude(userprofile__user__teamspeak3=None)\ + .values('corporation_id', 'corporation_name')\ + .distinct() + 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""" + title = 'alliance' + parameter_name = 'main_alliances' + + def lookups(self, request, model_admin): + qs = EveCharacter.objects\ + .exclude(alliance_id=None)\ + .exclude(userprofile=None)\ + .exclude(userprofile__user__teamspeak3=None)\ + .values('alliance_id', 'alliance_name')\ + .distinct() + 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 Teamspeak3UserAdmin(admin.ModelAdmin): ordering = ('user__username', ) list_select_related = True @@ -20,8 +69,8 @@ class Teamspeak3UserAdmin(admin.ModelAdmin): 'perm_key' ) list_filter = ( - 'user__profile__main_character__corporation_name', - 'user__profile__main_character__alliance_name', + MainCorporationsFilter, + MainAllianceFilter, 'user__date_joined', ) @@ -57,9 +106,21 @@ class Teamspeak3UserAdmin(admin.ModelAdmin): class AuthTSgroupAdmin(admin.ModelAdmin): - fields = ['auth_group', 'ts_group'] + ordering = ('auth_group__name', ) + list_select_related = True + + list_display = ('auth_group', '_ts_group') + list_filter = ('ts_group', ) + + fields = ('auth_group', 'ts_group') filter_horizontal = ('ts_group',) + def _ts_group(self, obj): + return [x for x in obj.ts_group.all().order_by('ts_group_id')] + + _ts_group.short_description = 'ts groups' + #_ts_group.admin_order_field = 'profile__state' + @admin.register(StateGroup) class StateGroupAdmin(admin.ModelAdmin): From ab061ba7a63e571b372a53f1f9a63f03a0b7369e Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 6 Feb 2020 22:47:02 +0100 Subject: [PATCH 06/16] Further improvements to admin site --- allianceauth/authentication/admin.py | 27 +++-- .../services/modules/discord/admin.py | 91 ++++++++++---- .../discord/static/services/discord/admin.css | 6 + allianceauth/services/modules/mumble/admin.py | 112 +++++++++++++++++- .../services/modules/teamspeak3/admin.py | 19 +-- 5 files changed, 207 insertions(+), 48 deletions(-) create mode 100644 allianceauth/services/modules/discord/static/services/discord/admin.css diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 874858b0..df8c71be 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -8,6 +8,7 @@ from django.db.models import Q, F from allianceauth.services.hooks import ServicesHook from django.db.models.signals import pre_save, post_save, pre_delete, \ post_delete, m2m_changed +from django.db.models.functions import Lower from django.dispatch import receiver from django.forms import ModelForm from django.utils.html import format_html @@ -102,11 +103,11 @@ class RealGroupsFilter(admin.SimpleListFilter): parameter_name = 'real_groups' def lookups(self, request, model_admin): - qs = Group.objects.all().order_by('name') + qs = Group.objects.all().order_by(Lower('name')) if _has_auto_groups: qs = qs\ .filter(managedalliancegroup__exact=None)\ - .filter(managedcorpgroup__exact=None) + .filter(managedcorpgroup__exact=None) return tuple([(x.pk, x.name) for x in qs]) def queryset(self, request, queryset): @@ -125,10 +126,11 @@ class MainCorporationsFilter(admin.SimpleListFilter): qs = EveCharacter.objects\ .exclude(userprofile=None)\ .values('corporation_id', 'corporation_name')\ - .distinct() - return tuple([ - (x['corporation_id'], x['corporation_name']) for x in qs - ]) + .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: @@ -148,10 +150,11 @@ class MainAllianceFilter(admin.SimpleListFilter): .exclude(alliance_id=None)\ .exclude(userprofile=None)\ .values('alliance_id', 'alliance_name')\ - .distinct() - return tuple([ - (x['alliance_id'], x['alliance_name']) for x in qs - ]) + .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: @@ -234,8 +237,8 @@ class UserAdmin(BaseUserAdmin): def _user(self, obj): - #/admin////change/ - link = '/admin/authentication/{}/{}/change/'.format( + link = '/admin/{}/{}/{}/change/'.format( + __package__.rsplit('.', 1)[-1], type(obj).__name__.lower(), obj.pk ) diff --git a/allianceauth/services/modules/discord/admin.py b/allianceauth/services/modules/discord/admin.py index d7ca52a6..778e4d5d 100644 --- a/allianceauth/services/modules/discord/admin.py +++ b/allianceauth/services/modules/discord/admin.py @@ -1,4 +1,7 @@ from django.contrib import admin +from django.db.models.functions import Lower +from django.utils.html import format_html + from allianceauth.eveonline.models import EveCharacter from .models import DiscordUser @@ -14,10 +17,11 @@ class MainCorporationsFilter(admin.SimpleListFilter): .exclude(userprofile=None)\ .exclude(userprofile__user__discord=None)\ .values('corporation_id', 'corporation_name')\ - .distinct() - return tuple([ - (x['corporation_id'], x['corporation_name']) for x in qs - ]) + .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: @@ -38,10 +42,11 @@ class MainAllianceFilter(admin.SimpleListFilter): .exclude(userprofile=None)\ .exclude(userprofile__user__discord=None)\ .values('alliance_id', 'alliance_name')\ - .distinct() - return tuple([ - (x['alliance_id'], x['alliance_name']) for x in qs - ]) + .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: @@ -52,14 +57,19 @@ class MainAllianceFilter(admin.SimpleListFilter): class DiscordUserAdmin(admin.ModelAdmin): + class Media: + css = { + "all": ("services/discord/admin.css",) + } + ordering = ('user__username', ) list_select_related = True list_display = ( - 'user', - 'uid', - '_corporation', - '_alliance', + '_profile_pic', + '_user', + '_uid', + '_main_organization', '_date_joined' ) search_fields = ( @@ -72,28 +82,61 @@ class DiscordUserAdmin(admin.ModelAdmin): 'user__date_joined', ) - def _corporation(self, obj): + def _profile_pic(self, obj): if obj.user.profile.main_character: - return obj.user.profile.main_character.corporation_name + return format_html( + '', + obj.user.profile.main_character.portrait_url(size=32) + ) else: return '' + _profile_pic.short_description = '' + + + def _user(self, obj): + link = '/admin/{}/{}/{}/change/'.format( + __package__.rsplit('.', 1)[-1], + type(obj).__name__.lower(), + obj.pk + ) + return format_html( + '{}
{}', + link, + obj.user.username, + obj.user.profile.main_character.character_name \ + if obj.user.profile.main_character else '' + ) - _corporation.short_description = 'corporation (main)' - _corporation.admin_order_field \ - = 'user__profile__main_character__corporation_name' + _user.short_description = 'user / main' + _user.admin_order_field = 'user__username' - def _alliance(self, obj): + def _uid(self, obj): + return obj.uid + + _uid.short_description = 'Discord ID (UID)' + _uid.admin_order_field = 'uid' + + + 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 ): - return obj.user.profile.main_character.alliance_name + alliance = obj.user.profile.main_character.alliance_name else: - return '' - - _alliance.short_description = 'alliance (main)' - _alliance.admin_order_field \ - = 'user__profile__main_character__alliance_name' + alliance = '' + return format_html('{}
{}', + corporation, + alliance + ) + + _main_organization.short_description = 'Corporation / Alliance (Main)' + _main_organization.admin_order_field = \ + 'profile__main_character__corporation_name' def _date_joined(self, obj): diff --git a/allianceauth/services/modules/discord/static/services/discord/admin.css b/allianceauth/services/modules/discord/static/services/discord/admin.css new file mode 100644 index 00000000..55a52f35 --- /dev/null +++ b/allianceauth/services/modules/discord/static/services/discord/admin.css @@ -0,0 +1,6 @@ +/* +CSS for allianceauth services admin site +*/ + +.img-circle { border-radius: 50%; } +.column-_profile_pic { width: 50px; } \ No newline at end of file diff --git a/allianceauth/services/modules/mumble/admin.py b/allianceauth/services/modules/mumble/admin.py index 387f7549..11b6d177 100644 --- a/allianceauth/services/modules/mumble/admin.py +++ b/allianceauth/services/modules/mumble/admin.py @@ -1,10 +1,114 @@ from django.contrib import admin +from django.db.models.functions import Lower +from allianceauth.eveonline.models import EveCharacter + from .models import MumbleUser -class MumbleUserAdmin(admin.ModelAdmin): - fields = ('user', 'username', 'groups') # pwhash is hidden from admin panel - list_display = ('user', 'username', 'groups') - search_fields = ('user__username', 'username', 'groups') +class MainCorporationsFilter(admin.SimpleListFilter): + """Custom filter to show corporations from service users only""" + title = 'corporation' + parameter_name = 'main_corporations' + + def lookups(self, request, model_admin): + qs = EveCharacter.objects\ + .exclude(userprofile=None)\ + .exclude(userprofile__user__mumble=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""" + title = 'alliance' + parameter_name = 'main_alliances' + + def lookups(self, request, model_admin): + qs = EveCharacter.objects\ + .exclude(alliance_id=None)\ + .exclude(userprofile=None)\ + .exclude(userprofile__user__mumble=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 MumbleUserAdmin(admin.ModelAdmin): + ordering = ('user__username', ) + list_select_related = True + + list_display = ( + 'user', + 'username', + 'groups', + '_corporation', + '_alliance', + '_date_joined' + ) + search_fields = ( + 'user__username', + 'username', + 'groups' + ) + + list_filter = ( + MainCorporationsFilter, + MainAllianceFilter, + 'user__date_joined', + ) + + fields = ('user', 'username', 'groups') # pwhash is hidden from admin panel + + def _corporation(self, obj): + if obj.user.profile.main_character: + return obj.user.profile.main_character.corporation_name + else: + return '' + + _corporation.short_description = 'corporation (main)' + _corporation.admin_order_field \ + = 'user__profile__main_character__corporation_name' + + + def _alliance(self, obj): + if (obj.user.profile.main_character + and obj.user.profile.main_character.alliance_id + ): + return obj.user.profile.main_character.alliance_name + else: + return '' + + _alliance.short_description = 'alliance (main)' + _alliance.admin_order_field \ + = 'user__profile__main_character__alliance_name' + + + def _date_joined(self, obj): + return obj.user.date_joined + + _date_joined.short_description = 'date joined' + _date_joined.admin_order_field = 'user__date_joined' + admin.site.register(MumbleUser, MumbleUserAdmin) diff --git a/allianceauth/services/modules/teamspeak3/admin.py b/allianceauth/services/modules/teamspeak3/admin.py index 26dd1291..6fdacd67 100644 --- a/allianceauth/services/modules/teamspeak3/admin.py +++ b/allianceauth/services/modules/teamspeak3/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from django.db.models.functions import Lower from allianceauth.eveonline.models import EveCharacter from .models import AuthTS, Teamspeak3User, StateGroup @@ -14,10 +15,11 @@ class MainCorporationsFilter(admin.SimpleListFilter): .exclude(userprofile=None)\ .exclude(userprofile__user__teamspeak3=None)\ .values('corporation_id', 'corporation_name')\ - .distinct() - return tuple([ - (x['corporation_id'], x['corporation_name']) for x in qs - ]) + .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: @@ -38,10 +40,11 @@ class MainAllianceFilter(admin.SimpleListFilter): .exclude(userprofile=None)\ .exclude(userprofile__user__teamspeak3=None)\ .values('alliance_id', 'alliance_name')\ - .distinct() - return tuple([ - (x['alliance_id'], x['alliance_name']) for x in qs - ]) + .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: From 9d0a65a516e32f9aceaf0f9584348e87b2871817 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Fri, 7 Feb 2020 01:42:39 +0100 Subject: [PATCH 07/16] Add tooltips to users, add CSS --- allianceauth/authentication/admin.py | 66 +++++++++++++++---- .../static/authentication/css/admin.css | 23 +++++++ .../services/modules/discord/admin.py | 4 +- .../discord/static/services/discord/admin.css | 2 +- 4 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 allianceauth/authentication/static/authentication/css/admin.css diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index df8c71be..34231ca8 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -18,6 +18,7 @@ from allianceauth.authentication.models import State, get_guest_state,\ CharacterOwnership, UserProfile, OwnershipRecord from allianceauth.hooks import get_hooks from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo +from allianceauth.eveonline.tasks import update_character if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: _has_auto_groups = True @@ -26,6 +27,10 @@ else: _has_auto_groups = False +_USERS_MAX_GROUPS = 5 +_USERS_MAX_CHARACTERS = 3 + + def make_service_hooks_update_groups_action(service): """ Make a admin action for the given service @@ -164,13 +169,54 @@ class MainAllianceFilter(admin.SimpleListFilter): .filter(profile__main_character__alliance_id=self.value()) +def update_main_character_model(modeladmin, request, queryset): + tasks_count = 0 + for obj in queryset: + if obj.profile.main_character: + update_character.delay(obj.profile.main_character.character_id) + tasks_count += 1 + + modeladmin.message_user( + request, + 'Update from ESI started for {} characters'.format(tasks_count) + ) + +update_main_character_model.short_description = \ + 'Update main character model from ESI' + +def list_2_html_w_tooltips(my_items: list, max_items: int) -> str: + """converts list of strings into HTML with cutoff and tooltip when > max""" + items_truncated_str = ', '.join(my_items[:max_items]) + if len(my_items) <= max_items: + return items_truncated_str + else: + items_truncated_str += ' (...)' + items_all_str = ', '.join(my_items) + return format_html( + '{}', + items_all_str, + items_truncated_str + ) + class UserAdmin(BaseUserAdmin): """ Extending Django's UserAdmin model """ + + class Media: + css = { + "all": ("authentication/css/admin.css",) + } + def get_actions(self, request): actions = super(BaseUserAdmin, self).get_actions(request) + actions[update_main_character_model.__name__] = ( + update_main_character_model, + update_main_character_model.__name__, + update_main_character_model.short_description + ) + for hook in get_hooks('services_hook'): svc = hook() # Check update_groups is redefined/overloaded @@ -209,10 +255,8 @@ class UserAdmin(BaseUserAdmin): list_filter = ( 'profile__state', - RealGroupsFilter, - #'profile__main_character__corporation_name', - MainCorporationsFilter, - #'profile__main_character__alliance_name', + RealGroupsFilter, + MainCorporationsFilter, MainAllianceFilter, 'is_active', 'date_joined', @@ -221,14 +265,13 @@ class UserAdmin(BaseUserAdmin): ) search_fields = ( 'username', - 'character_ownerships__character__character_name', - 'groups__name' + 'character_ownerships__character__character_name' ) def _profile_pic(self, obj): if obj.profile.main_character: return format_html( - '', + '', obj.profile.main_character.portrait_url(size=32) ) else: @@ -275,12 +318,13 @@ class UserAdmin(BaseUserAdmin): def _characters(self, obj): - return [ + my_characters = [ x.character.character_name for x in CharacterOwnership.objects\ .filter(user=obj)\ .order_by('character__character_name') - ] + ] + return list_2_html_w_tooltips(my_characters, _USERS_MAX_CHARACTERS) _characters.short_description = 'characters' @@ -303,8 +347,8 @@ class UserAdmin(BaseUserAdmin): .order_by('name') ] - return ', '.join(my_groups) - + return list_2_html_w_tooltips(my_groups, _USERS_MAX_GROUPS) + _groups.short_description = 'groups' diff --git a/allianceauth/authentication/static/authentication/css/admin.css b/allianceauth/authentication/static/authentication/css/admin.css new file mode 100644 index 00000000..4408ea6e --- /dev/null +++ b/allianceauth/authentication/static/authentication/css/admin.css @@ -0,0 +1,23 @@ +/* +CSS for allianceauth admin site +*/ + +.img-circle { border-radius: 50%; } +.column-_profile_pic { width: 50px; } + +/* tooltip */ +.tooltip { + position: relative ; +} +.tooltip:hover::after { + content: attr(data-tooltip) ; + position: absolute ; + top: 1.1em ; + left: 1em ; + min-width: 200px ; + border: 1px #808080 solid ; + padding: 8px ; + color: black ; + background-color: rgb(255, 255, 204) ; + z-index: 1 ; +} \ No newline at end of file diff --git a/allianceauth/services/modules/discord/admin.py b/allianceauth/services/modules/discord/admin.py index 778e4d5d..67122cb1 100644 --- a/allianceauth/services/modules/discord/admin.py +++ b/allianceauth/services/modules/discord/admin.py @@ -12,10 +12,9 @@ class MainCorporationsFilter(admin.SimpleListFilter): title = 'corporation' parameter_name = 'main_corporations' - def lookups(self, request, model_admin): + def lookups(self, request, model_admin): qs = EveCharacter.objects\ .exclude(userprofile=None)\ - .exclude(userprofile__user__discord=None)\ .values('corporation_id', 'corporation_name')\ .distinct()\ .order_by(Lower('corporation_name')) @@ -40,7 +39,6 @@ class MainAllianceFilter(admin.SimpleListFilter): qs = EveCharacter.objects\ .exclude(alliance_id=None)\ .exclude(userprofile=None)\ - .exclude(userprofile__user__discord=None)\ .values('alliance_id', 'alliance_name')\ .distinct()\ .order_by(Lower('alliance_name')) diff --git a/allianceauth/services/modules/discord/static/services/discord/admin.css b/allianceauth/services/modules/discord/static/services/discord/admin.css index 55a52f35..72fedd99 100644 --- a/allianceauth/services/modules/discord/static/services/discord/admin.css +++ b/allianceauth/services/modules/discord/static/services/discord/admin.css @@ -1,5 +1,5 @@ /* -CSS for allianceauth services admin site +CSS for allianceauth admin site */ .img-circle { border-radius: 50%; } From 0caac20d77218bd3b2840504735efdc65375da09 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Fri, 7 Feb 2020 17:21:33 +0100 Subject: [PATCH 08/16] Adding settings for users lists, showing all auto groups in groups --- allianceauth/authentication/admin.py | 15 ++- allianceauth/authentication/app_settings.py | 46 ++++++++ .../static/authentication/css/admin.css | 1 + allianceauth/authentication/tests/__init__.py | 0 .../{tests.py => tests/test_all.py} | 37 +++--- .../authentication/tests/test_app_settings.py | 108 ++++++++++++++++++ allianceauth/groupmanagement/admin.py | 65 +++++++++-- 7 files changed, 241 insertions(+), 31 deletions(-) create mode 100644 allianceauth/authentication/app_settings.py create mode 100644 allianceauth/authentication/tests/__init__.py rename allianceauth/authentication/{tests.py => tests/test_all.py} (98%) create mode 100644 allianceauth/authentication/tests/test_app_settings.py diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 34231ca8..5681a9d6 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -19,6 +19,7 @@ from allianceauth.authentication.models import State, get_guest_state,\ from allianceauth.hooks import get_hooks from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo from allianceauth.eveonline.tasks import update_character +from .app_settings import * if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: _has_auto_groups = True @@ -27,10 +28,6 @@ else: _has_auto_groups = False -_USERS_MAX_GROUPS = 5 -_USERS_MAX_CHARACTERS = 3 - - def make_service_hooks_update_groups_action(service): """ Make a admin action for the given service @@ -324,7 +321,10 @@ class UserAdmin(BaseUserAdmin): .filter(user=obj)\ .order_by('character__character_name') ] - return list_2_html_w_tooltips(my_characters, _USERS_MAX_CHARACTERS) + return list_2_html_w_tooltips( + my_characters, + AUTHENTICATION_ADMIN_USERS_MAX_CHARS + ) _characters.short_description = 'characters' @@ -347,7 +347,10 @@ class UserAdmin(BaseUserAdmin): .order_by('name') ] - return list_2_html_w_tooltips(my_groups, _USERS_MAX_GROUPS) + return list_2_html_w_tooltips( + my_groups, + AUTHENTICATION_ADMIN_USERS_MAX_GROUPS + ) _groups.short_description = 'groups' diff --git a/allianceauth/authentication/app_settings.py b/allianceauth/authentication/app_settings.py new file mode 100644 index 00000000..9494953f --- /dev/null +++ b/allianceauth/authentication/app_settings.py @@ -0,0 +1,46 @@ +from django.conf import settings + + +def _clean_setting( + name: str, + default_value: object, + min_value: int = None, + max_value: int = None, + required_type: type = None +): + """cleans the input for a custom setting + + Will use `default_value` if settings does not exit or has the wrong type + or is outside define boundaries (for int only) + + Need to define `required_type` if `default_value` is `None` + + Will assume `min_value` of 0 for int (can be overriden) + + Returns cleaned value for setting + """ + if default_value is None and not required_type: + raise ValueError('You must specify a required_type for None defaults') + + if not required_type: + required_type = type(default_value) + + if min_value is None and required_type == int: + min_value = 0 + + if (hasattr(settings, name) + and isinstance(getattr(settings, name), required_type) + and (min_value is None or getattr(settings, name) >= min_value) + and (max_value is None or getattr(settings, name) <= max_value) + ): + return getattr(settings, name) + else: + return default_value + + +AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \ + _clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_GROUPS', 10) + +AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \ + _clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5) + diff --git a/allianceauth/authentication/static/authentication/css/admin.css b/allianceauth/authentication/static/authentication/css/admin.css index 4408ea6e..b3b2a8f0 100644 --- a/allianceauth/authentication/static/authentication/css/admin.css +++ b/allianceauth/authentication/static/authentication/css/admin.css @@ -2,6 +2,7 @@ CSS for allianceauth admin site */ +/* styling for profile pic */ .img-circle { border-radius: 50%; } .column-_profile_pic { width: 50px; } diff --git a/allianceauth/authentication/tests/__init__.py b/allianceauth/authentication/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/authentication/tests.py b/allianceauth/authentication/tests/test_all.py similarity index 98% rename from allianceauth/authentication/tests.py rename to allianceauth/authentication/tests/test_all.py index 5b763b28..2d7b9ec2 100644 --- a/allianceauth/authentication/tests.py +++ b/allianceauth/authentication/tests/test_all.py @@ -1,23 +1,28 @@ from unittest import mock from io import StringIO -from django.test import TestCase -from django.contrib.auth.models import User -from allianceauth.tests.auth_utils import AuthUtils -from .models import CharacterOwnership, UserProfile, State, get_guest_state, OwnershipRecord -from .backends import StateBackend -from .tasks import check_character_ownership -from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo -from esi.models import Token -from esi.errors import IncompleteResponseError -from allianceauth.authentication.decorators import main_character_required -from django.test.client import RequestFactory -from django.http.response import HttpResponse -from django.contrib.auth.models import AnonymousUser -from django.conf import settings -from django.shortcuts import reverse -from django.core.management import call_command from urllib import parse +from django.conf import settings +from django.contrib.auth.models import AnonymousUser, User +from django.core.management import call_command +from django.http.response import HttpResponse +from django.shortcuts import reverse +from django.test import TestCase +from django.test.client import RequestFactory + + +from allianceauth.authentication.decorators import main_character_required +from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\ + EveAllianceInfo +from allianceauth.tests.auth_utils import AuthUtils +from esi.errors import IncompleteResponseError +from esi.models import Token + +from ..backends import StateBackend +from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\ + OwnershipRecord +from ..tasks import check_character_ownership + MODULE_PATH = 'allianceauth.authentication' diff --git a/allianceauth/authentication/tests/test_app_settings.py b/allianceauth/authentication/tests/test_app_settings.py new file mode 100644 index 00000000..6bd2b8c1 --- /dev/null +++ b/allianceauth/authentication/tests/test_app_settings.py @@ -0,0 +1,108 @@ +from unittest.mock import Mock, patch + +from django.test import TestCase + +from .. import app_settings + +MODULE_PATH = 'allianceauth.authentication' + +class TestSetAppSetting(TestCase): + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_if_not_set(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = Mock(spec=None) + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + False, + ) + self.assertEqual(result, False) + + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_if_not_set_for_none(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = Mock(spec=None) + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + None, + required_type=int + ) + self.assertEqual(result, None) + + + @patch(MODULE_PATH + '.app_settings.settings') + def test_true_stays_true(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = True + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + False, + ) + self.assertEqual(result, True) + + @patch(MODULE_PATH + '.app_settings.settings') + def test_false_stays_false(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = False + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + False + ) + self.assertEqual(result, False) + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_for_invalid_type_bool(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = 'invalid type' + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + False + ) + self.assertEqual(result, False) + + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_for_invalid_type_int(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = 'invalid type' + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + 50 + ) + self.assertEqual(result, 50) + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_if_below_minimum_1(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = -5 + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + default_value=50 + ) + self.assertEqual(result, 50) + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_if_below_minimum_2(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = -50 + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + default_value=50, + min_value=-10 + ) + self.assertEqual(result, 50) + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_for_invalid_type_int(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = 1000 + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + default_value=50, + max_value=100 + ) + self.assertEqual(result, 50) + + + @patch(MODULE_PATH + '.app_settings.settings') + def test_default_is_none_needs_required_type(self, mock_settings): + mock_settings.TEST_SETTING_DUMMY = 'invalid type' + with self.assertRaises(ValueError): + result = app_settings._clean_setting( + 'TEST_SETTING_DUMMY', + default_value=None + ) + + diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 35a86354..5a8cf5e6 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -35,6 +35,49 @@ class AuthGroupInlineAdmin(admin.StackedInline): return request.user.has_perm('auth.change_group') +if _has_auto_groups: + class IsAutoGroupFilter(admin.SimpleListFilter): + title = 'auto group' + parameter_name = 'auto_group' + + def lookups(self, request, model_admin): + return ( + ('Yes', 'Yes'), + ('No', 'No'), + ) + + def queryset(self, request, queryset): + value = self.value() + if value == 'Yes': + return queryset.exclude( + managedalliancegroup__exact=None, + managedcorpgroup__exact=None + ) + elif value == 'No': + return queryset.filter(managedalliancegroup__exact=None).filter(managedcorpgroup__exact=None) + else: + return queryset + + +class HasLeaderFilter(admin.SimpleListFilter): + title = 'has leader' + parameter_name = 'has_leader' + + def lookups(self, request, model_admin): + return ( + ('Yes', 'Yes'), + ('No', 'No'), + ) + + def queryset(self, request, queryset): + value = self.value() + if value == 'Yes': + return queryset.filter(authgroup__group_leaders__isnull=False) + elif value == 'No': + return queryset.filter(authgroup__group_leaders__isnull=True) + else: + return queryset + class GroupAdmin(admin.ModelAdmin): list_select_related = True ordering = ('name', ) @@ -50,7 +93,9 @@ class GroupAdmin(admin.ModelAdmin): 'authgroup__internal', 'authgroup__hidden', 'authgroup__open', - 'authgroup__public' + 'authgroup__public', + IsAutoGroupFilter, + HasLeaderFilter ) search_fields = ('name', 'authgroup__description') @@ -58,13 +103,9 @@ class GroupAdmin(admin.ModelAdmin): inlines = (AuthGroupInlineAdmin,) def get_queryset(self, request): - qs = super().get_queryset(request) - if _has_auto_groups: - qs = qs\ - .filter(managedalliancegroup__exact=None)\ - .filter(managedcorpgroup__exact=None) + qs = super().get_queryset(request) qs = qs.annotate( - member_count=Count('user', distinct=True), + member_count=Count('user', distinct=True), ) return qs @@ -80,11 +121,17 @@ class GroupAdmin(admin.ModelAdmin): def has_leader(self, obj): return obj.authgroup.group_leaders.exists() - has_leader.boolean = True + has_leader.boolean = True + def _properties(self, obj): properties = list() - if obj.authgroup.internal: + if _has_auto_groups and ( + obj.managedalliancegroup_set.exists() + or obj.managedcorpgroup_set.exists() + ): + properties.append('Auto Group') + elif obj.authgroup.internal: properties.append('Internal') else: if obj.authgroup.hidden: From 3f4dfe9b0b5d04b3d423f04bee052024acec8cdb Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Fri, 7 Feb 2020 20:37:06 +0100 Subject: [PATCH 09/16] Move common service user list features into central admin class --- allianceauth/services/admin.py | 140 +++++++++++++++++- .../services/modules/discord/admin.py | 140 +----------------- allianceauth/services/modules/mumble/admin.py | 108 +------------- .../services/modules/teamspeak3/admin.py | 105 +------------ .../discord => static/services}/admin.css | 0 5 files changed, 156 insertions(+), 337 deletions(-) rename allianceauth/services/{modules/discord/static/services/discord => static/services}/admin.css (100%) diff --git a/allianceauth/services/admin.py b/allianceauth/services/admin.py index d37a30fb..bffd5c98 100644 --- a/allianceauth/services/admin.py +++ b/allianceauth/services/admin.py @@ -1,9 +1,147 @@ -from django.contrib import admin from django import forms +from django.contrib import admin +from django.db.models.functions import Lower +from django.utils.html import format_html + from allianceauth import hooks +from allianceauth.eveonline.models import EveCharacter + 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): + """Parent class for UserAdmin classes for all services""" + class Media: + css = { + "all": ("services/admin.css",) + } + + search_fields = ( + 'user__username', + 'uid' + ) + ordering = ('user__username', ) + list_select_related = True + list_display = ( + '_profile_pic', + '_user', + '_main_organization', + '_date_joined' + ) + list_filter = ( + MainCorporationsFilter, + MainAllianceFilter, + 'user__date_joined' + ) + + def _profile_pic(self, obj): + if obj.user.profile.main_character: + return format_html( + '', + obj.user.profile.main_character.portrait_url(size=32) + ) + else: + return '' + _profile_pic.short_description = '' + + + def _user(self, obj): + link = '/admin/{}/{}/{}/change/'.format( + __package__.rsplit('.', 1)[-1], + type(obj).__name__.lower(), + obj.pk + ) + return format_html( + '{}
{}', + 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('{}
{}', + corporation, + alliance + ) + + _main_organization.short_description = 'Corporation / Alliance (Main)' + _main_organization.admin_order_field = \ + 'profile__main_character__corporation_name' + + + def _date_joined(self, obj): + return obj.user.date_joined + + _date_joined.short_description = 'date joined' + _date_joined.admin_order_field = 'user__date_joined' + + class NameFormatConfigForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(NameFormatConfigForm, self).__init__(*args, **kwargs) diff --git a/allianceauth/services/modules/discord/admin.py b/allianceauth/services/modules/discord/admin.py index 67122cb1..b30cc354 100644 --- a/allianceauth/services/modules/discord/admin.py +++ b/allianceauth/services/modules/discord/admin.py @@ -1,114 +1,14 @@ from django.contrib import admin -from django.db.models.functions import Lower -from django.utils.html import format_html - -from allianceauth.eveonline.models import EveCharacter from .models import DiscordUser +from ...admin import ServicesUserAdmin -class MainCorporationsFilter(admin.SimpleListFilter): - """Custom filter to show corporations from service users 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 show alliances from service users 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()) - - -class DiscordUserAdmin(admin.ModelAdmin): - class Media: - css = { - "all": ("services/discord/admin.css",) - } - - ordering = ('user__username', ) - list_select_related = True - - list_display = ( - '_profile_pic', - '_user', - '_uid', - '_main_organization', - '_date_joined' - ) - search_fields = ( - 'user__username', - 'uid' - ) - list_filter = ( - MainCorporationsFilter, - MainAllianceFilter, - 'user__date_joined', - ) - - def _profile_pic(self, obj): - if obj.user.profile.main_character: - return format_html( - '', - obj.user.profile.main_character.portrait_url(size=32) - ) - else: - return '' - _profile_pic.short_description = '' - - - def _user(self, obj): - link = '/admin/{}/{}/{}/change/'.format( - __package__.rsplit('.', 1)[-1], - type(obj).__name__.lower(), - obj.pk - ) - return format_html( - '{}
{}', - 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' - - +class DiscordUserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ( + '_uid', + ) + def _uid(self, obj): return obj.uid @@ -116,32 +16,4 @@ class DiscordUserAdmin(admin.ModelAdmin): _uid.admin_order_field = 'uid' - 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('{}
{}', - corporation, - alliance - ) - - _main_organization.short_description = 'Corporation / Alliance (Main)' - _main_organization.admin_order_field = \ - 'profile__main_character__corporation_name' - - - def _date_joined(self, obj): - return obj.user.date_joined - - _date_joined.short_description = 'date joined' - _date_joined.admin_order_field = 'user__date_joined' - - admin.site.register(DiscordUser, DiscordUserAdmin) diff --git a/allianceauth/services/modules/mumble/admin.py b/allianceauth/services/modules/mumble/admin.py index 11b6d177..a895f1bb 100644 --- a/allianceauth/services/modules/mumble/admin.py +++ b/allianceauth/services/modules/mumble/admin.py @@ -1,114 +1,20 @@ from django.contrib import admin -from django.db.models.functions import Lower -from allianceauth.eveonline.models import EveCharacter from .models import MumbleUser +from ...admin import ServicesUserAdmin -class MainCorporationsFilter(admin.SimpleListFilter): - """Custom filter to show corporations from service users only""" - title = 'corporation' - parameter_name = 'main_corporations' - - def lookups(self, request, model_admin): - qs = EveCharacter.objects\ - .exclude(userprofile=None)\ - .exclude(userprofile__user__mumble=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""" - title = 'alliance' - parameter_name = 'main_alliances' - - def lookups(self, request, model_admin): - qs = EveCharacter.objects\ - .exclude(alliance_id=None)\ - .exclude(userprofile=None)\ - .exclude(userprofile__user__mumble=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 MumbleUserAdmin(admin.ModelAdmin): - ordering = ('user__username', ) - list_select_related = True - - list_display = ( - 'user', - 'username', - 'groups', - '_corporation', - '_alliance', - '_date_joined' - ) - search_fields = ( - 'user__username', +class MumbleUserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ( + 'username', + 'groups', + ) + search_fields = ServicesUserAdmin.search_fields + ( 'username', 'groups' ) - list_filter = ( - MainCorporationsFilter, - MainAllianceFilter, - 'user__date_joined', - ) - fields = ('user', 'username', 'groups') # pwhash is hidden from admin panel - def _corporation(self, obj): - if obj.user.profile.main_character: - return obj.user.profile.main_character.corporation_name - else: - return '' - - _corporation.short_description = 'corporation (main)' - _corporation.admin_order_field \ - = 'user__profile__main_character__corporation_name' - - - def _alliance(self, obj): - if (obj.user.profile.main_character - and obj.user.profile.main_character.alliance_id - ): - return obj.user.profile.main_character.alliance_name - else: - return '' - - _alliance.short_description = 'alliance (main)' - _alliance.admin_order_field \ - = 'user__profile__main_character__alliance_name' - - - def _date_joined(self, obj): - return obj.user.date_joined - - _date_joined.short_description = 'date joined' - _date_joined.admin_order_field = 'user__date_joined' - admin.site.register(MumbleUser, MumbleUserAdmin) diff --git a/allianceauth/services/modules/teamspeak3/admin.py b/allianceauth/services/modules/teamspeak3/admin.py index 6fdacd67..1c75e636 100644 --- a/allianceauth/services/modules/teamspeak3/admin.py +++ b/allianceauth/services/modules/teamspeak3/admin.py @@ -1,112 +1,15 @@ from django.contrib import admin -from django.db.models.functions import Lower -from allianceauth.eveonline.models import EveCharacter from .models import AuthTS, Teamspeak3User, StateGroup +from ...admin import ServicesUserAdmin -class MainCorporationsFilter(admin.SimpleListFilter): - """Custom filter to show corporations from service users only""" - title = 'corporation' - parameter_name = 'main_corporations' - - def lookups(self, request, model_admin): - qs = EveCharacter.objects\ - .exclude(userprofile=None)\ - .exclude(userprofile__user__teamspeak3=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""" - title = 'alliance' - parameter_name = 'main_alliances' - - def lookups(self, request, model_admin): - qs = EveCharacter.objects\ - .exclude(alliance_id=None)\ - .exclude(userprofile=None)\ - .exclude(userprofile__user__teamspeak3=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 Teamspeak3UserAdmin(admin.ModelAdmin): - ordering = ('user__username', ) - list_select_related = True - - list_display = ( - 'user', - 'uid', - '_corporation', - '_alliance', - '_date_joined', - 'perm_key', - ) - search_fields = ( - 'user__username', +class Teamspeak3UserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ( 'uid', - 'perm_key' + 'perm_key' ) - list_filter = ( - MainCorporationsFilter, - MainAllianceFilter, - 'user__date_joined', - ) - - def _corporation(self, obj): - if obj.user.profile.main_character: - return obj.user.profile.main_character.corporation_name - else: - return '' - _corporation.short_description = 'corporation (main)' - _corporation.admin_order_field \ - = 'user__profile__main_character__corporation_name' - - - def _alliance(self, obj): - if (obj.user.profile.main_character - and obj.user.profile.main_character.alliance_id - ): - return obj.user.profile.main_character.alliance_name - else: - return '' - - _alliance.short_description = 'alliance (main)' - _alliance.admin_order_field \ - = 'user__profile__main_character__alliance_name' - - - def _date_joined(self, obj): - return obj.user.date_joined - - _date_joined.short_description = 'date joined' - _date_joined.admin_order_field = 'user__date_joined' - class AuthTSgroupAdmin(admin.ModelAdmin): ordering = ('auth_group__name', ) diff --git a/allianceauth/services/modules/discord/static/services/discord/admin.css b/allianceauth/services/static/services/admin.css similarity index 100% rename from allianceauth/services/modules/discord/static/services/discord/admin.css rename to allianceauth/services/static/services/admin.css From c1388bf23fcc8041fc95ff354be05a8d8eb2b5d5 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Fri, 7 Feb 2020 23:01:13 +0100 Subject: [PATCH 10/16] Adopt all services user and auth user lists to new format --- allianceauth/authentication/admin.py | 362 ++++++++++++------ allianceauth/services/admin.py | 11 +- .../services/modules/discord/admin.py | 3 +- .../services/modules/discourse/admin.py | 12 +- allianceauth/services/modules/mumble/admin.py | 4 +- .../services/modules/openfire/admin.py | 10 +- allianceauth/services/modules/phpbb3/admin.py | 10 +- allianceauth/services/modules/smf/admin.py | 11 +- .../services/modules/teamspeak3/admin.py | 6 +- .../services/modules/xenforo/admin.py | 11 +- 10 files changed, 289 insertions(+), 151 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 5681a9d6..12ec14fa 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -12,6 +12,7 @@ from django.db.models.functions import Lower from django.dispatch import receiver from django.forms import ModelForm from django.utils.html import format_html +from django.urls import reverse from django.utils.text import slugify from allianceauth.authentication.models import State, get_guest_state,\ @@ -99,102 +100,6 @@ class UserProfileInline(admin.StackedInline): return False -class RealGroupsFilter(admin.SimpleListFilter): - """Custom filter to get groups w/o Autogroups""" - title = 'group' - parameter_name = 'real_groups' - - def lookups(self, request, model_admin): - qs = Group.objects.all().order_by(Lower('name')) - if _has_auto_groups: - qs = qs\ - .filter(managedalliancegroup__exact=None)\ - .filter(managedcorpgroup__exact=None) - return tuple([(x.pk, x.name) for x in qs]) - - def queryset(self, request, queryset): - if self.value() is None: - return queryset.all() - else: - return queryset.filter(groups__pk=self.value()) - - -class MainCorporationsFilter(admin.SimpleListFilter): - """Custom filter to show 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 show 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()) - - -def update_main_character_model(modeladmin, request, queryset): - tasks_count = 0 - for obj in queryset: - if obj.profile.main_character: - update_character.delay(obj.profile.main_character.character_id) - tasks_count += 1 - - modeladmin.message_user( - request, - 'Update from ESI started for {} characters'.format(tasks_count) - ) - -update_main_character_model.short_description = \ - 'Update main character model from ESI' - -def list_2_html_w_tooltips(my_items: list, max_items: int) -> str: - """converts list of strings into HTML with cutoff and tooltip when > max""" - items_truncated_str = ', '.join(my_items[:max_items]) - if len(my_items) <= max_items: - return items_truncated_str - else: - items_truncated_str += ' (...)' - items_all_str = ', '.join(my_items) - return format_html( - '{}', - items_all_str, - items_truncated_str - ) - class UserAdmin(BaseUserAdmin): """ Extending Django's UserAdmin model @@ -204,14 +109,98 @@ class UserAdmin(BaseUserAdmin): 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): + """Custom filter to get groups w/o Autogroups""" + title = 'group' + parameter_name = 'real_groups' + + def lookups(self, request, model_admin): + qs = Group.objects.all().order_by(Lower('name')) + if _has_auto_groups: + qs = qs\ + .filter(managedalliancegroup__exact=None)\ + .filter(managedcorpgroup__exact=None) + return tuple([(x.pk, x.name) for x in qs]) + + def queryset(self, request, queryset): + if self.value() is None: + return queryset.all() + else: + return queryset.filter(groups__pk=self.value()) + + + def update_main_character_model(self, request, queryset): + tasks_count = 0 + for obj in queryset: + if obj.profile.main_character: + update_character.delay(obj.profile.main_character.character_id) + tasks_count += 1 + + self.message_user( + request, + 'Update from ESI started for {} characters'.format(tasks_count) + ) + + update_main_character_model.short_description = \ + 'Update main character model from ESI' + + def get_actions(self, request): actions = super(BaseUserAdmin, self).get_actions(request) - actions[update_main_character_model.__name__] = ( - update_main_character_model, - update_main_character_model.__name__, - update_main_character_model.short_description + actions[self.update_main_character_model.__name__] = ( + self.update_main_character_model, + self.update_main_character_model.__name__, + self.update_main_character_model.short_description ) for hook in get_hooks('services_hook'): @@ -231,18 +220,33 @@ class UserAdmin(BaseUserAdmin): return actions + 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""" + items_truncated_str = ', '.join(my_items[:max_items]) + if len(my_items) <= max_items: + return items_truncated_str + else: + items_truncated_str += ' (...)' + items_all_str = ', '.join(my_items) + return format_html( + '{}', + items_all_str, + items_truncated_str + ) + + inlines = BaseUserAdmin.inlines + [UserProfileInline] ordering = ('username', ) - list_select_related = True + list_select_related = True show_full_result_count = True list_display = ( '_profile_pic', - '_user', + '_user', '_state', '_groups', - '_main_organization', + '_main_organization', '_characters', 'is_active', 'date_joined', @@ -265,7 +269,9 @@ class UserAdmin(BaseUserAdmin): '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( '', @@ -277,10 +283,13 @@ class UserAdmin(BaseUserAdmin): def _user(self, obj): - link = '/admin/{}/{}/{}/change/'.format( - __package__.rsplit('.', 1)[-1], - type(obj).__name__.lower(), - obj.pk + """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( '{}
{}', @@ -289,11 +298,13 @@ class UserAdmin(BaseUserAdmin): 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: @@ -313,7 +324,6 @@ class UserAdmin(BaseUserAdmin): _main_organization.admin_order_field = \ 'profile__main_character__corporation_name' - def _characters(self, obj): my_characters = [ x.character.character_name @@ -321,7 +331,7 @@ class UserAdmin(BaseUserAdmin): .filter(user=obj)\ .order_by('character__character_name') ] - return list_2_html_w_tooltips( + return self._list_2_html_w_tooltips( my_characters, AUTHENTICATION_ADMIN_USERS_MAX_CHARS ) @@ -347,7 +357,7 @@ class UserAdmin(BaseUserAdmin): .order_by('name') ] - return list_2_html_w_tooltips( + return self._list_2_html_w_tooltips( my_groups, AUTHENTICATION_ADMIN_USERS_MAX_GROUPS ) @@ -411,14 +421,138 @@ class StateAdmin(admin.ModelAdmin): class BaseOwnershipAdmin(admin.ModelAdmin): - list_display = ('user', 'character') - search_fields = ('user__user', 'character__character_name', 'character__corporation_name', 'character__alliance_name') + class Media: + 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_display = ( + '_profile_pic', + '_user', + '_main_organization', + 'character', + ) + search_fields = ( + 'user__user', + 'character__character_name', + 'character__corporation_name', + 'character__alliance_name' + ) + list_filter = ( + MainCorporationsFilter, + MainAllianceFilter, + ) def get_readonly_fields(self, request, obj=None): if obj and obj.pk: return 'owner_hash', 'character' return tuple() + + def _profile_pic(self, obj): + """profile pic column data for user objects""" + if obj.user.profile.main_character: + return format_html( + '', + 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( + '{}
{}', + 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('{}
{}', + corporation, + alliance + ) + + _main_organization.short_description = 'Corporation / Alliance (Main)' + _main_organization.admin_order_field = \ + 'user__profile__main_character__corporation_name' + @admin.register(OwnershipRecord) class OwnershipRecordAdmin(BaseOwnershipAdmin): diff --git a/allianceauth/services/admin.py b/allianceauth/services/admin.py index bffd5c98..97e6f1d6 100644 --- a/allianceauth/services/admin.py +++ b/allianceauth/services/admin.py @@ -1,6 +1,7 @@ from django import forms from django.contrib import admin from django.db.models.functions import Lower +from django.urls import reverse from django.utils.html import format_html from allianceauth import hooks @@ -97,10 +98,12 @@ class ServicesUserAdmin(admin.ModelAdmin): def _user(self, obj): - link = '/admin/{}/{}/{}/change/'.format( - __package__.rsplit('.', 1)[-1], - type(obj).__name__.lower(), - obj.pk + link = reverse( + 'admin:{}_{}_change'.format( + obj._meta.app_label, + type(obj).__name__.lower() + ), + args=(obj.pk,) ) return format_html( '{}
{}', diff --git a/allianceauth/services/modules/discord/admin.py b/allianceauth/services/modules/discord/admin.py index b30cc354..c5b12467 100644 --- a/allianceauth/services/modules/discord/admin.py +++ b/allianceauth/services/modules/discord/admin.py @@ -4,6 +4,7 @@ from .models import DiscordUser from ...admin import ServicesUserAdmin +@admin.register(DiscordUser) class DiscordUserAdmin(ServicesUserAdmin): list_display = ServicesUserAdmin.list_display + ( '_uid', @@ -15,5 +16,3 @@ class DiscordUserAdmin(ServicesUserAdmin): _uid.short_description = 'Discord ID (UID)' _uid.admin_order_field = 'uid' - -admin.site.register(DiscordUser, DiscordUserAdmin) diff --git a/allianceauth/services/modules/discourse/admin.py b/allianceauth/services/modules/discourse/admin.py index a6ad60c7..2e1abe26 100644 --- a/allianceauth/services/modules/discourse/admin.py +++ b/allianceauth/services/modules/discourse/admin.py @@ -1,9 +1,11 @@ from django.contrib import admin + from .models import DiscourseUser +from ...admin import ServicesUserAdmin -class DiscourseUserAdmin(admin.ModelAdmin): - list_display = ('user',) - search_fields = ('user__username',) - -admin.site.register(DiscourseUser, DiscourseUserAdmin) +@admin.register(DiscourseUser) +class DiscourseUserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ( + 'enabled', + ) diff --git a/allianceauth/services/modules/mumble/admin.py b/allianceauth/services/modules/mumble/admin.py index a895f1bb..153772ae 100644 --- a/allianceauth/services/modules/mumble/admin.py +++ b/allianceauth/services/modules/mumble/admin.py @@ -4,6 +4,7 @@ from .models import MumbleUser from ...admin import ServicesUserAdmin +@admin.register(MumbleUser) class MumbleUserAdmin(ServicesUserAdmin): list_display = ServicesUserAdmin.list_display + ( 'username', @@ -15,6 +16,3 @@ class MumbleUserAdmin(ServicesUserAdmin): ) fields = ('user', 'username', 'groups') # pwhash is hidden from admin panel - - -admin.site.register(MumbleUser, MumbleUserAdmin) diff --git a/allianceauth/services/modules/openfire/admin.py b/allianceauth/services/modules/openfire/admin.py index d2564634..c0a1a9c7 100644 --- a/allianceauth/services/modules/openfire/admin.py +++ b/allianceauth/services/modules/openfire/admin.py @@ -1,9 +1,11 @@ from django.contrib import admin + from .models import OpenfireUser +from ...admin import ServicesUserAdmin -class OpenfireUserAdmin(admin.ModelAdmin): - list_display = ('user', 'username') - search_fields = ('user__username', 'username') +@admin.register(OpenfireUser) +class OpenfireUserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ('username',) + search_fields = ServicesUserAdmin.search_fields + ('username', ) -admin.site.register(OpenfireUser, OpenfireUserAdmin) diff --git a/allianceauth/services/modules/phpbb3/admin.py b/allianceauth/services/modules/phpbb3/admin.py index 210c9042..70e7f831 100644 --- a/allianceauth/services/modules/phpbb3/admin.py +++ b/allianceauth/services/modules/phpbb3/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin from .models import Phpbb3User +from ...admin import ServicesUserAdmin -class Phpbb3UserAdmin(admin.ModelAdmin): - list_display = ('user', 'username') - search_fields = ('user__username', 'username') - -admin.site.register(Phpbb3User, Phpbb3UserAdmin) +@admin.register(Phpbb3User) +class Phpbb3UserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ('username',) + search_fields = ServicesUserAdmin.search_fields + ('username', ) \ No newline at end of file diff --git a/allianceauth/services/modules/smf/admin.py b/allianceauth/services/modules/smf/admin.py index 6afdca04..99c7f228 100644 --- a/allianceauth/services/modules/smf/admin.py +++ b/allianceauth/services/modules/smf/admin.py @@ -1,9 +1,10 @@ from django.contrib import admin + from .models import SmfUser +from ...admin import ServicesUserAdmin -class SmfUserAdmin(admin.ModelAdmin): - list_display = ('user', 'username') - search_fields = ('user__username', 'username') - -admin.site.register(SmfUser, SmfUserAdmin) +@admin.register(SmfUser) +class SmfUserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ('username',) + search_fields = ServicesUserAdmin.search_fields + ('username', ) \ No newline at end of file diff --git a/allianceauth/services/modules/teamspeak3/admin.py b/allianceauth/services/modules/teamspeak3/admin.py index 1c75e636..0a6379dd 100644 --- a/allianceauth/services/modules/teamspeak3/admin.py +++ b/allianceauth/services/modules/teamspeak3/admin.py @@ -4,6 +4,7 @@ from .models import AuthTS, Teamspeak3User, StateGroup from ...admin import ServicesUserAdmin +@admin.register(Teamspeak3User) class Teamspeak3UserAdmin(ServicesUserAdmin): list_display = ServicesUserAdmin.list_display + ( 'uid', @@ -11,6 +12,7 @@ class Teamspeak3UserAdmin(ServicesUserAdmin): ) +@admin.register(AuthTS) class AuthTSgroupAdmin(admin.ModelAdmin): ordering = ('auth_group__name', ) list_select_related = True @@ -32,7 +34,3 @@ class AuthTSgroupAdmin(admin.ModelAdmin): class StateGroupAdmin(admin.ModelAdmin): list_display = ('state', 'ts_group') search_fields = ('state__name', 'ts_group__ts_group_name') - - -admin.site.register(AuthTS, AuthTSgroupAdmin) -admin.site.register(Teamspeak3User, Teamspeak3UserAdmin) diff --git a/allianceauth/services/modules/xenforo/admin.py b/allianceauth/services/modules/xenforo/admin.py index 21321501..4da19bf2 100644 --- a/allianceauth/services/modules/xenforo/admin.py +++ b/allianceauth/services/modules/xenforo/admin.py @@ -1,9 +1,10 @@ from django.contrib import admin + from .models import XenforoUser +from ...admin import ServicesUserAdmin -class XenforoUserAdmin(admin.ModelAdmin): - list_display = ('user', 'username') - search_fields = ('user__username', 'username') - -admin.site.register(XenforoUser, XenforoUserAdmin) +@admin.register(XenforoUser) +class XenforoUserAdmin(ServicesUserAdmin): + list_display = ServicesUserAdmin.list_display + ('username',) + search_fields = ServicesUserAdmin.search_fields + ('username', ) \ No newline at end of file From 47babf2ed73837808c2cb871af708a4960935c10 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Sat, 8 Feb 2020 00:51:13 +0100 Subject: [PATCH 11/16] Refactor common functions for creating admin user list, peformance tweaks --- allianceauth/authentication/admin.py | 394 ++++++++---------- .../static/authentication/css/admin.css | 2 +- allianceauth/groupmanagement/admin.py | 9 +- allianceauth/services/admin.py | 112 +---- .../services/static/services/admin.css | 2 +- 5 files changed, 179 insertions(+), 340 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 12ec14fa..a3df70b2 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -100,63 +100,153 @@ class UserProfileInline(admin.StackedInline): 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( + '', + 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( + '{}
{}', + 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('{}
{}', + 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: 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): """Custom filter to get groups w/o Autogroups""" @@ -167,8 +257,8 @@ class UserAdmin(BaseUserAdmin): qs = Group.objects.all().order_by(Lower('name')) if _has_auto_groups: qs = qs\ - .filter(managedalliancegroup__exact=None)\ - .filter(managedcorpgroup__exact=None) + .filter(managedalliancegroup__isnull=True)\ + .filter(managedcorpgroup__isnull=True) return tuple([(x.pk, x.name) for x in qs]) def queryset(self, request, queryset): @@ -208,20 +298,24 @@ class UserAdmin(BaseUserAdmin): # Check update_groups is redefined/overloaded if svc.update_groups.__module__ != ServicesHook.update_groups.__module__: action = make_service_hooks_update_groups_action(svc) - actions[action.__name__] = (action, - action.__name__, - action.short_description) + actions[action.__name__] = ( + action, + action.__name__, + action.short_description + ) + # Create sync nickname action if service implements it if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__: action = make_service_hooks_sync_nickname_action(svc) - actions[action.__name__] = (action, - action.__name__, - action.short_description) - + actions[action.__name__] = ( + action, action.__name__, + action.short_description + ) return actions + 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]) if len(my_items) <= max_items: return items_truncated_str @@ -242,11 +336,11 @@ class UserAdmin(BaseUserAdmin): show_full_result_count = True list_display = ( - '_profile_pic', - '_user', + user_profile_pic, + user_username, '_state', '_groups', - '_main_organization', + user_main_organization, '_characters', 'is_active', 'date_joined', @@ -269,67 +363,13 @@ class UserAdmin(BaseUserAdmin): '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( - '', - 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( - '{}
{}', - 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('{}
{}', - corporation, - alliance - ) - - _main_organization.short_description = 'Corporation / Alliance (Main)' - _main_organization.admin_order_field = \ - 'profile__main_character__corporation_name' - def _characters(self, obj): my_characters = [ x.character.character_name for x in CharacterOwnership.objects\ .filter(user=obj)\ - .order_by('character__character_name') + .order_by('character__character_name')\ + .select_related() ] return self._list_2_html_w_tooltips( my_characters, @@ -352,8 +392,8 @@ class UserAdmin(BaseUserAdmin): else: my_groups = [ x.name for x in obj.groups\ - .filter(managedalliancegroup=None)\ - .filter(managedcorpgroup=None)\ + .filter(managedalliancegroup__isnull=True)\ + .filter(managedcorpgroup__isnull=True)\ .order_by('name') ] @@ -425,61 +465,12 @@ class BaseOwnershipAdmin(admin.ModelAdmin): 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_display = ( - '_profile_pic', - '_user', - '_main_organization', + user_profile_pic, + user_username, + user_main_organization, 'character', ) search_fields = ( @@ -498,61 +489,6 @@ class BaseOwnershipAdmin(admin.ModelAdmin): return 'owner_hash', 'character' return tuple() - - def _profile_pic(self, obj): - """profile pic column data for user objects""" - if obj.user.profile.main_character: - return format_html( - '', - 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( - '{}
{}', - 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('{}
{}', - corporation, - alliance - ) - - _main_organization.short_description = 'Corporation / Alliance (Main)' - _main_organization.admin_order_field = \ - 'user__profile__main_character__corporation_name' - @admin.register(OwnershipRecord) class OwnershipRecordAdmin(BaseOwnershipAdmin): diff --git a/allianceauth/authentication/static/authentication/css/admin.css b/allianceauth/authentication/static/authentication/css/admin.css index b3b2a8f0..5fe8a4d8 100644 --- a/allianceauth/authentication/static/authentication/css/admin.css +++ b/allianceauth/authentication/static/authentication/css/admin.css @@ -4,7 +4,7 @@ CSS for allianceauth admin site /* styling for profile pic */ .img-circle { border-radius: 50%; } -.column-_profile_pic { width: 50px; } +.column-user_profile_pic { width: 50px; } /* tooltip */ .tooltip { diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 5a8cf5e6..6d1a7ef0 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -50,11 +50,14 @@ if _has_auto_groups: value = self.value() if value == 'Yes': return queryset.exclude( - managedalliancegroup__exact=None, - managedcorpgroup__exact=None + managedalliancegroup__isnull=True, + managedcorpgroup__isnull=True ) elif value == 'No': - return queryset.filter(managedalliancegroup__exact=None).filter(managedcorpgroup__exact=None) + return queryset.filter( + managedalliancegroup__isnull=True, + managedcorpgroup__isnull=True + ) else: return queryset diff --git a/allianceauth/services/admin.py b/allianceauth/services/admin.py index 97e6f1d6..061b97e1 100644 --- a/allianceauth/services/admin.py +++ b/allianceauth/services/admin.py @@ -6,61 +6,13 @@ from django.utils.html import format_html from allianceauth import hooks 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 -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): """Parent class for UserAdmin classes for all services""" class Media: @@ -75,9 +27,9 @@ class ServicesUserAdmin(admin.ModelAdmin): ordering = ('user__username', ) list_select_related = True list_display = ( - '_profile_pic', - '_user', - '_main_organization', + user_profile_pic, + user_username, + user_main_organization, '_date_joined' ) list_filter = ( @@ -86,58 +38,6 @@ class ServicesUserAdmin(admin.ModelAdmin): 'user__date_joined' ) - def _profile_pic(self, obj): - if obj.user.profile.main_character: - return format_html( - '', - 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( - '{}
{}', - 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('{}
{}', - corporation, - alliance - ) - - _main_organization.short_description = 'Corporation / Alliance (Main)' - _main_organization.admin_order_field = \ - 'profile__main_character__corporation_name' - - def _date_joined(self, obj): return obj.user.date_joined diff --git a/allianceauth/services/static/services/admin.css b/allianceauth/services/static/services/admin.css index 72fedd99..46796c5f 100644 --- a/allianceauth/services/static/services/admin.css +++ b/allianceauth/services/static/services/admin.css @@ -3,4 +3,4 @@ CSS for allianceauth admin site */ .img-circle { border-radius: 50%; } -.column-_profile_pic { width: 50px; } \ No newline at end of file +.column-user_profile_pic { width: 50px; } \ No newline at end of file From ff168d1c9ee3b1b3732b2aa8603e7597a390897b Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 13 Feb 2020 00:40:41 +0100 Subject: [PATCH 12/16] Add admin tests, some fixes --- allianceauth/authentication/admin.py | 77 ++-- .../authentication/tests/test_admin.py | 384 ++++++++++++++++++ 2 files changed, 422 insertions(+), 39 deletions(-) create mode 100644 allianceauth/authentication/tests/test_admin.py diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index a3df70b2..dac057d8 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -3,7 +3,7 @@ from django.conf import settings from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User as BaseUser, \ - Permission as BasePermission + Permission as BasePermission, Group from django.db.models import Q, F from allianceauth.services.hooks import ServicesHook from django.db.models.signals import pre_save, post_save, pre_delete, \ @@ -113,7 +113,7 @@ def user_profile_pic(obj): user_obj.profile.main_character.portrait_url(size=32) ) else: - return '' + return None user_profile_pic.short_description = '' @@ -131,13 +131,19 @@ def user_username(obj): args=(obj.pk,) ) user_obj = obj.user if hasattr(obj, 'user') else obj - return format_html( - '{}
{}', - link, - user_obj.username, - user_obj.profile.main_character.character_name \ - if user_obj.profile.main_character else '' - ) + if user_obj.profile.main_character: + return format_html( + '{}
{}', + link, + user_obj.username, + user_obj.profile.main_character.character_name + ) + else: + return format_html( + '{}', + link, + user_obj.username, + ) user_username.short_description = 'user / main' user_username.admin_order_field = 'username' @@ -150,20 +156,18 @@ def user_main_organization(obj): 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: + if not user_obj.profile.main_character: + result = None + else: 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('{}
{}', - corporation, - alliance - ) + if user_obj.profile.main_character.alliance_id: + result = format_html('{}
{}', + corporation, + user_obj.profile.main_character.alliance_name + ) + else: + result = corporation + return result user_main_organization.short_description = 'Corporation / Alliance (Main)' user_main_organization.admin_order_field = \ @@ -177,7 +181,7 @@ class MainCorporationsFilter(admin.SimpleListFilter): To be used for all user based admin lists """ title = 'corporation' - parameter_name = 'main_corporations' + parameter_name = 'main_corporation_id__exact' def lookups(self, request, model_admin): qs = EveCharacter.objects\ @@ -210,7 +214,7 @@ class MainAllianceFilter(admin.SimpleListFilter): To be used for all user based admin lists """ title = 'alliance' - parameter_name = 'main_alliances' + parameter_name = 'main_alliance_id__exact' def lookups(self, request, model_admin): qs = EveCharacter.objects\ @@ -251,7 +255,7 @@ class UserAdmin(BaseUserAdmin): class RealGroupsFilter(admin.SimpleListFilter): """Custom filter to get groups w/o Autogroups""" title = 'group' - parameter_name = 'real_groups' + parameter_name = 'group_id__exact' def lookups(self, request, model_admin): qs = Group.objects.all().order_by(Lower('name')) @@ -267,7 +271,6 @@ class UserAdmin(BaseUserAdmin): else: return queryset.filter(groups__pk=self.value()) - def update_main_character_model(self, request, queryset): tasks_count = 0 for obj in queryset: @@ -283,7 +286,6 @@ class UserAdmin(BaseUserAdmin): update_main_character_model.short_description = \ 'Update main character model from ESI' - def get_actions(self, request): actions = super(BaseUserAdmin, self).get_actions(request) @@ -313,21 +315,22 @@ class UserAdmin(BaseUserAdmin): ) return actions - def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str: """converts list of strings into HTML with cutoff and tooltip""" items_truncated_str = ', '.join(my_items[:max_items]) - if len(my_items) <= max_items: - return items_truncated_str + if not my_items: + result = None + elif len(my_items) <= max_items: + result = items_truncated_str else: - items_truncated_str += ' (...)' + items_truncated_str += ', (...)' items_all_str = ', '.join(my_items) - return format_html( + result = format_html( '{}', items_all_str, items_truncated_str ) - + return result inlines = BaseUserAdmin.inlines + [UserProfileInline] @@ -380,12 +383,11 @@ class UserAdmin(BaseUserAdmin): def _state(self, obj): - return obj.profile.state + return obj.profile.state.name _state.short_description = 'state' _state.admin_order_field = 'profile__state' - def _groups(self, obj): if not _has_auto_groups: my_groups = [x.name for x in obj.groups.order_by('name')] @@ -404,20 +406,17 @@ class UserAdmin(BaseUserAdmin): _groups.short_description = 'groups' - def _role(self, obj): if obj.is_superuser: role = 'Superuser' elif obj.is_staff: role = 'Staff' else: - role = 'User' - + role = 'User' return role _role.short_description = 'role' - def has_change_permission(self, request, obj=None): return request.user.has_perm('auth.change_user') diff --git a/allianceauth/authentication/tests/test_admin.py b/allianceauth/authentication/tests/test_admin.py new file mode 100644 index 00000000..961ff3c9 --- /dev/null +++ b/allianceauth/authentication/tests/test_admin.py @@ -0,0 +1,384 @@ +from unittest.mock import patch + +from django.test import TestCase, RequestFactory +from django.contrib import admin +from django.contrib.admin.sites import AdminSite +from django.contrib.auth.models import User as BaseUser, Group + +from allianceauth.authentication.models import CharacterOwnership, State +from allianceauth.eveonline.autogroups.models import AutogroupsConfig +from allianceauth.eveonline.models import ( + EveCharacter, EveCorporationInfo, EveAllianceInfo +) + +from ..admin import ( + BaseUserAdmin, + MainCorporationsFilter, + MainAllianceFilter, + User, + UserAdmin, + user_main_organization, + user_profile_pic, + user_username, +) + + +MODULE_PATH = 'allianceauth.authentication.admin' + + +class MockRequest(object): + + def __init__(self, user=None): + self.user = user + + +class TestUserAdmin(TestCase): + + def setUp(self): + self.factory = RequestFactory() + self.modeladmin = UserAdmin( + model=User, admin_site=AdminSite() + ) + # groups + self.group_1 = Group.objects.create( + name='Group 1' + ) + self.group_2 = Group.objects.create( + name='Group 2' + ) + + # user 1 - corp and alliance, normal user + self.character_1 = EveCharacter.objects.create( + character_id='1001', + character_name='Bruce Wayne', + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + ) + self.character_1a = EveCharacter.objects.create( + character_id='1002', + character_name='Batman', + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + ) + alliance = EveAllianceInfo.objects.create( + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + executor_corp_id='2001' + ) + EveCorporationInfo.objects.create( + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + member_count=42, + alliance=alliance + ) + self.user_1 = User.objects.create_user( + self.character_1.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=self.character_1, + owner_hash='x1' + self.character_1.character_name, + user=self.user_1 + ) + CharacterOwnership.objects.create( + character=self.character_1a, + owner_hash='x1' + self.character_1a.character_name, + user=self.user_1 + ) + self.user_1.profile.main_character = self.character_1 + self.user_1.profile.save() + self.user_1.groups.add(self.group_1) + + # user 2 - corp only, staff + self.character_2 = EveCharacter.objects.create( + character_id=1003, + character_name='Clark Kent', + corporation_id=2002, + corporation_name='Daily Planet', + corporation_ticker='DP', + alliance_id=None + ) + EveCorporationInfo.objects.create( + corporation_id=2002, + corporation_name='Daily Plane', + corporation_ticker='DP', + member_count=99, + alliance=None + ) + self.user_2 = User.objects.create_user( + self.character_2.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=self.character_2, + owner_hash='x1' + self.character_2.character_name, + user=self.user_2 + ) + self.user_2.profile.main_character = self.character_2 + self.user_2.profile.save() + self.user_2.groups.add(self.group_2) + self.user_2.is_staff = True + self.user_2.save() + + # user 3 - no main, no group, superuser + self.character_3 = EveCharacter.objects.create( + character_id=1101, + character_name='Lex Luthor', + corporation_id=2101, + corporation_name='Lex Corp', + corporation_ticker='LC', + alliance_id=None + ) + EveCorporationInfo.objects.create( + corporation_id=2101, + corporation_name='Lex Corp', + corporation_ticker='LC', + member_count=666, + alliance=None + ) + EveAllianceInfo.objects.create( + alliance_id='3101', + alliance_name='Lex World Domination', + alliance_ticker='LWD', + executor_corp_id='' + ) + self.user_3 = User.objects.create_user( + self.character_3.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=self.character_3, + owner_hash='x1' + self.character_3.character_name, + user=self.user_3 + ) + self.user_3.is_superuser = True + self.user_3.save() + + # create autogroups for corps and alliances + autogroups_config = AutogroupsConfig( + corp_groups = True, + alliance_groups = True + ) + autogroups_config.save() + for state in State.objects.all(): + autogroups_config.states.add(state) + autogroups_config.update_corp_group_membership(self.user_1) + + # column rendering + + def test_user_profile_pic_1(self): + expected = ('') + self.assertEqual(user_profile_pic(self.user_1), expected) + + def test_user_profile_pic_3(self): + self.assertIsNone(user_profile_pic(self.user_3)) + + def test_user_username_1(self): + expected = ( + '' + 'Bruce_Wayne
Bruce Wayne'.format(self.user_1.pk) + ) + self.assertEqual(user_username(self.user_1), expected) + + def test_user_username_3(self): + expected = ( + '' + 'Lex_Luthor'.format(self.user_3.pk) + ) + self.assertEqual(user_username(self.user_3), expected) + + def test_user_main_organization_1(self): + expected = 'Wayne Technologies
Wayne Enterprises' + self.assertEqual(user_main_organization(self.user_1), expected) + + def test_user_main_organization_2(self): + expected = 'Daily Planet' + self.assertEqual(user_main_organization(self.user_2), expected) + + def test_user_main_organization_3(self): + expected = None + self.assertEqual(user_main_organization(self.user_3), expected) + + def test_characters_1(self): + expected = 'Batman, Bruce Wayne' + result = self.modeladmin._characters(self.user_1) + self.assertEqual(result, expected) + + def test_characters_2(self): + expected = 'Clark Kent' + result = self.modeladmin._characters(self.user_2) + self.assertEqual(result, expected) + + def test_characters_3(self): + expected = 'Lex Luthor' + result = self.modeladmin._characters(self.user_3) + self.assertEqual(result, expected) + + def test_groups_1(self): + expected = 'Group 1' + result = self.modeladmin._groups(self.user_1) + self.assertEqual(result, expected) + + def test_groups_2(self): + expected = 'Group 2' + result = self.modeladmin._groups(self.user_2) + self.assertEqual(result, expected) + + def test_groups_3(self): + result = self.modeladmin._groups(self.user_3) + self.assertIsNone(result) + + def test_state(self): + expected = 'Guest' + result = self.modeladmin._state(self.user_1) + self.assertEqual(result, expected) + + def test_role_1(self): + expected = 'User' + result = self.modeladmin._role(self.user_1) + self.assertEqual(result, expected) + + def test_role_2(self): + expected = 'Staff' + result = self.modeladmin._role(self.user_2) + self.assertEqual(result, expected) + + def test_role_3(self): + expected = 'Superuser' + result = self.modeladmin._role(self.user_3) + self.assertEqual(result, expected) + + def test_list_2_html_w_tooltips_no_cutoff(self): + items = ['one', 'two', 'three'] + expected = 'one, two, three' + result = self.modeladmin._list_2_html_w_tooltips(items, 5) + self.assertEqual(expected, result) + + def test_list_2_html_w_tooltips_w_cutoff(self): + items = ['one', 'two', 'three'] + expected = ('one, two, (...)') + result = self.modeladmin._list_2_html_w_tooltips(items, 2) + self.assertEqual(expected, result) + + def test_list_2_html_w_tooltips_empty_list(self): + items = [] + expected = None + result = self.modeladmin._list_2_html_w_tooltips(items, 5) + self.assertEqual(expected, result) + + # actions + + @patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True) + @patch(MODULE_PATH + '.update_character') + def test_action_update_main_character_model( + self, mock_task, mock_message_user + ): + users_qs = User.objects.filter(pk__in=[self.user_1.pk, self.user_2.pk]) + self.modeladmin.update_main_character_model( + MockRequest(self.user_1), users_qs + ) + self.assertEqual(mock_task.delay.call_count, 2) + self.assertTrue(mock_message_user.called) + + # filters + + def test_filter_real_groups(self): + + class UserAdminTest(BaseUserAdmin): + list_filter = (UserAdmin.RealGroupsFilter,) + + my_modeladmin = UserAdminTest(User, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + (self.group_1.pk, self.group_1.name), + (self.group_2.pk, self.group_2.name), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned + request = self.factory.get('/', {'group_id__exact': self.group_1.pk}) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = User.objects.filter(groups__in=[self.group_1]) + self.assertSetEqual(set(queryset), set(expected)) + + def test_filter_main_corporations(self): + + class UserAdminTest(BaseUserAdmin): + list_filter = (MainCorporationsFilter,) + + my_modeladmin = UserAdminTest(User, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + ('2002', 'Daily Planet'), + ('2001', 'Wayne Technologies'), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned + request = self.factory.get( + '/', + {'main_corporation_id__exact': self.character_1.corporation_id} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = [self.user_1] + self.assertSetEqual(set(queryset), set(expected)) + + def test_filter_main_alliances(self): + + class UserAdminTest(BaseUserAdmin): + list_filter = (MainAllianceFilter,) + + my_modeladmin = UserAdminTest(User, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + ('3001', 'Wayne Enterprises'), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned + request = self.factory.get( + '/', + {'main_alliance_id__exact': self.character_1.alliance_id} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = [self.user_1] + self.assertSetEqual(set(queryset), set(expected)) \ No newline at end of file From 80729b6b068c80c762ab0ee8a3716f16752ccd93 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 13 Feb 2020 01:14:46 +0100 Subject: [PATCH 13/16] Performance improve admin tests, test w/o auto groups --- .../authentication/tests/test_admin.py | 175 ++++++++++++------ 1 file changed, 115 insertions(+), 60 deletions(-) diff --git a/allianceauth/authentication/tests/test_admin.py b/allianceauth/authentication/tests/test_admin.py index 961ff3c9..4e94434e 100644 --- a/allianceauth/authentication/tests/test_admin.py +++ b/allianceauth/authentication/tests/test_admin.py @@ -34,21 +34,20 @@ class MockRequest(object): class TestUserAdmin(TestCase): - def setUp(self): - self.factory = RequestFactory() - self.modeladmin = UserAdmin( - model=User, admin_site=AdminSite() - ) + @classmethod + def setUpClass(cls): + super().setUpClass() + # groups - self.group_1 = Group.objects.create( + cls.group_1 = Group.objects.create( name='Group 1' ) - self.group_2 = Group.objects.create( + cls.group_2 = Group.objects.create( name='Group 2' ) # user 1 - corp and alliance, normal user - self.character_1 = EveCharacter.objects.create( + cls.character_1 = EveCharacter.objects.create( character_id='1001', character_name='Bruce Wayne', corporation_id='2001', @@ -58,7 +57,7 @@ class TestUserAdmin(TestCase): alliance_name='Wayne Enterprises', alliance_ticker='WE', ) - self.character_1a = EveCharacter.objects.create( + cls.character_1a = EveCharacter.objects.create( character_id='1002', character_name='Batman', corporation_id='2001', @@ -81,27 +80,27 @@ class TestUserAdmin(TestCase): member_count=42, alliance=alliance ) - self.user_1 = User.objects.create_user( - self.character_1.character_name.replace(' ', '_'), + cls.user_1 = User.objects.create_user( + cls.character_1.character_name.replace(' ', '_'), 'abc@example.com', 'password' ) CharacterOwnership.objects.create( - character=self.character_1, - owner_hash='x1' + self.character_1.character_name, - user=self.user_1 + character=cls.character_1, + owner_hash='x1' + cls.character_1.character_name, + user=cls.user_1 ) CharacterOwnership.objects.create( - character=self.character_1a, - owner_hash='x1' + self.character_1a.character_name, - user=self.user_1 + character=cls.character_1a, + owner_hash='x1' + cls.character_1a.character_name, + user=cls.user_1 ) - self.user_1.profile.main_character = self.character_1 - self.user_1.profile.save() - self.user_1.groups.add(self.group_1) + cls.user_1.profile.main_character = cls.character_1 + cls.user_1.profile.save() + cls.user_1.groups.add(cls.group_1) # user 2 - corp only, staff - self.character_2 = EveCharacter.objects.create( + cls.character_2 = EveCharacter.objects.create( character_id=1003, character_name='Clark Kent', corporation_id=2002, @@ -116,24 +115,24 @@ class TestUserAdmin(TestCase): member_count=99, alliance=None ) - self.user_2 = User.objects.create_user( - self.character_2.character_name.replace(' ', '_'), + cls.user_2 = User.objects.create_user( + cls.character_2.character_name.replace(' ', '_'), 'abc@example.com', 'password' ) CharacterOwnership.objects.create( - character=self.character_2, - owner_hash='x1' + self.character_2.character_name, - user=self.user_2 + character=cls.character_2, + owner_hash='x1' + cls.character_2.character_name, + user=cls.user_2 ) - self.user_2.profile.main_character = self.character_2 - self.user_2.profile.save() - self.user_2.groups.add(self.group_2) - self.user_2.is_staff = True - self.user_2.save() + cls.user_2.profile.main_character = cls.character_2 + cls.user_2.profile.save() + cls.user_2.groups.add(cls.group_2) + cls.user_2.is_staff = True + cls.user_2.save() # user 3 - no main, no group, superuser - self.character_3 = EveCharacter.objects.create( + cls.character_3 = EveCharacter.objects.create( character_id=1101, character_name='Lex Luthor', corporation_id=2101, @@ -154,20 +153,27 @@ class TestUserAdmin(TestCase): alliance_ticker='LWD', executor_corp_id='' ) - self.user_3 = User.objects.create_user( - self.character_3.character_name.replace(' ', '_'), + cls.user_3 = User.objects.create_user( + cls.character_3.character_name.replace(' ', '_'), 'abc@example.com', 'password' ) CharacterOwnership.objects.create( - character=self.character_3, - owner_hash='x1' + self.character_3.character_name, - user=self.user_3 + character=cls.character_3, + owner_hash='x1' + cls.character_3.character_name, + user=cls.user_3 ) - self.user_3.is_superuser = True - self.user_3.save() - - # create autogroups for corps and alliances + cls.user_3.is_superuser = True + cls.user_3.save() + + def setUp(self): + self.factory = RequestFactory() + self.modeladmin = UserAdmin( + model=User, admin_site=AdminSite() + ) + + def _create_autogroups(self): + """create autogroups for corps and alliances""" autogroups_config = AutogroupsConfig( corp_groups = True, alliance_groups = True @@ -175,70 +181,90 @@ class TestUserAdmin(TestCase): autogroups_config.save() for state in State.objects.all(): autogroups_config.states.add(state) - autogroups_config.update_corp_group_membership(self.user_1) + autogroups_config.update_corp_group_membership(self.user_1) # column rendering - def test_user_profile_pic_1(self): + def test_user_profile_pic_u1(self): expected = ('') self.assertEqual(user_profile_pic(self.user_1), expected) - def test_user_profile_pic_3(self): + def test_user_profile_pic_u3(self): self.assertIsNone(user_profile_pic(self.user_3)) - def test_user_username_1(self): + def test_user_username_u1(self): expected = ( '' 'Bruce_Wayne
Bruce Wayne'.format(self.user_1.pk) ) self.assertEqual(user_username(self.user_1), expected) - def test_user_username_3(self): + def test_user_username_u3(self): expected = ( '' 'Lex_Luthor'.format(self.user_3.pk) ) self.assertEqual(user_username(self.user_3), expected) - def test_user_main_organization_1(self): + def test_user_main_organization_u1(self): expected = 'Wayne Technologies
Wayne Enterprises' self.assertEqual(user_main_organization(self.user_1), expected) - def test_user_main_organization_2(self): + def test_user_main_organization_u2(self): expected = 'Daily Planet' self.assertEqual(user_main_organization(self.user_2), expected) - def test_user_main_organization_3(self): + def test_user_main_organization_u3(self): expected = None self.assertEqual(user_main_organization(self.user_3), expected) - def test_characters_1(self): + def test_characters_u1(self): expected = 'Batman, Bruce Wayne' result = self.modeladmin._characters(self.user_1) self.assertEqual(result, expected) - def test_characters_2(self): + def test_characters_u2(self): expected = 'Clark Kent' result = self.modeladmin._characters(self.user_2) self.assertEqual(result, expected) - def test_characters_3(self): + def test_characters_u3(self): expected = 'Lex Luthor' result = self.modeladmin._characters(self.user_3) self.assertEqual(result, expected) - - def test_groups_1(self): + + def test_groups_u1(self): + self._create_autogroups() expected = 'Group 1' result = self.modeladmin._groups(self.user_1) self.assertEqual(result, expected) - def test_groups_2(self): + def test_groups_u2(self): + self._create_autogroups() expected = 'Group 2' result = self.modeladmin._groups(self.user_2) self.assertEqual(result, expected) - def test_groups_3(self): + def test_groups_u3(self): + self._create_autogroups() + result = self.modeladmin._groups(self.user_3) + self.assertIsNone(result) + + @patch(MODULE_PATH + '._has_auto_groups', False) + def test_groups_u1_no_autogroups(self): + expected = 'Group 1' + result = self.modeladmin._groups(self.user_1) + self.assertEqual(result, expected) + + @patch(MODULE_PATH + '._has_auto_groups', False) + def test_groups_u2_no_autogroups(self): + expected = 'Group 2' + result = self.modeladmin._groups(self.user_2) + self.assertEqual(result, expected) + + @patch(MODULE_PATH + '._has_auto_groups', False) + def test_groups_u3_no_autogroups(self): result = self.modeladmin._groups(self.user_3) self.assertIsNone(result) @@ -247,17 +273,17 @@ class TestUserAdmin(TestCase): result = self.modeladmin._state(self.user_1) self.assertEqual(result, expected) - def test_role_1(self): + def test_role_u1(self): expected = 'User' result = self.modeladmin._role(self.user_1) self.assertEqual(result, expected) - def test_role_2(self): + def test_role_u2(self): expected = 'Staff' result = self.modeladmin._role(self.user_2) self.assertEqual(result, expected) - def test_role_3(self): + def test_role_u3(self): expected = 'Superuser' result = self.modeladmin._role(self.user_3) self.assertEqual(result, expected) @@ -297,11 +323,40 @@ class TestUserAdmin(TestCase): # filters - def test_filter_real_groups(self): + def test_filter_real_groups_with_autogroups(self): class UserAdminTest(BaseUserAdmin): list_filter = (UserAdmin.RealGroupsFilter,) + self._create_autogroups() + my_modeladmin = UserAdminTest(User, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + (self.group_1.pk, self.group_1.name), + (self.group_2.pk, self.group_2.name), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned + request = self.factory.get('/', {'group_id__exact': self.group_1.pk}) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = User.objects.filter(groups__in=[self.group_1]) + self.assertSetEqual(set(queryset), set(expected)) + + @patch(MODULE_PATH + '._has_auto_groups', False) + def test_filter_real_groups_no_autogroups(self): + + class UserAdminTest(BaseUserAdmin): + list_filter = (UserAdmin.RealGroupsFilter,) + my_modeladmin = UserAdminTest(User, AdminSite()) # Make sure the lookups are correct From a48c67de5ce7647b9c18f206d230a2b95ffca775 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Thu, 13 Feb 2020 23:20:22 +0100 Subject: [PATCH 14/16] Restructure Discord tests into folder and add admin testst --- .../modules/discord/tests/__init__.py | 10 + .../modules/discord/tests/test_admin.py | 268 ++++++++++++++++++ .../modules/discord/tests/test_hooks.py | 127 +++++++++ .../{tests.py => tests/test_managers.py} | 190 +------------ .../modules/discord/tests/test_views.py | 66 +++++ 5 files changed, 475 insertions(+), 186 deletions(-) create mode 100644 allianceauth/services/modules/discord/tests/__init__.py create mode 100644 allianceauth/services/modules/discord/tests/test_admin.py create mode 100644 allianceauth/services/modules/discord/tests/test_hooks.py rename allianceauth/services/modules/discord/{tests.py => tests/test_managers.py} (55%) create mode 100644 allianceauth/services/modules/discord/tests/test_views.py diff --git a/allianceauth/services/modules/discord/tests/__init__.py b/allianceauth/services/modules/discord/tests/__init__.py new file mode 100644 index 00000000..3d5481cb --- /dev/null +++ b/allianceauth/services/modules/discord/tests/__init__.py @@ -0,0 +1,10 @@ +from django.contrib.auth.models import User, Group, Permission +from allianceauth.tests.auth_utils import AuthUtils + +DEFAULT_AUTH_GROUP = 'Member' +MODULE_PATH = 'allianceauth.services.modules.discord' + +def add_permissions(): + permission = Permission.objects.get(codename='access_discord') + members = Group.objects.get_or_create(name=DEFAULT_AUTH_GROUP)[0] + AuthUtils.add_permissions_to_groups([permission], [members]) diff --git a/allianceauth/services/modules/discord/tests/test_admin.py b/allianceauth/services/modules/discord/tests/test_admin.py new file mode 100644 index 00000000..5d9c4dc1 --- /dev/null +++ b/allianceauth/services/modules/discord/tests/test_admin.py @@ -0,0 +1,268 @@ +from unittest.mock import patch + +from django.test import TestCase, RequestFactory +from django.contrib import admin +from django.contrib.admin.sites import AdminSite +from django.contrib.auth.models import User + +from allianceauth.authentication.models import CharacterOwnership +from allianceauth.eveonline.models import ( + EveCharacter, EveCorporationInfo, EveAllianceInfo +) + +from ....admin import ( + user_profile_pic, + user_username, + user_main_organization, + ServicesUserAdmin, + MainCorporationsFilter, + MainAllianceFilter +) +from ..admin import ( + DiscordUser, + DiscordUserAdmin +) + + +class TestDiscordUserAdmin(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # user 1 - corp and alliance, normal user + cls.character_1 = EveCharacter.objects.create( + character_id='1001', + character_name='Bruce Wayne', + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + ) + cls.character_1a = EveCharacter.objects.create( + character_id='1002', + character_name='Batman', + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + ) + alliance = EveAllianceInfo.objects.create( + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + executor_corp_id='2001' + ) + EveCorporationInfo.objects.create( + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + member_count=42, + alliance=alliance + ) + cls.user_1 = User.objects.create_user( + cls.character_1.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=cls.character_1, + owner_hash='x1' + cls.character_1.character_name, + user=cls.user_1 + ) + CharacterOwnership.objects.create( + character=cls.character_1a, + owner_hash='x1' + cls.character_1a.character_name, + user=cls.user_1 + ) + cls.user_1.profile.main_character = cls.character_1 + cls.user_1.profile.save() + DiscordUser.objects.create( + user=cls.user_1, + uid=1001 + ) + + # user 2 - corp only, staff + cls.character_2 = EveCharacter.objects.create( + character_id=1003, + character_name='Clark Kent', + corporation_id=2002, + corporation_name='Daily Planet', + corporation_ticker='DP', + alliance_id=None + ) + EveCorporationInfo.objects.create( + corporation_id=2002, + corporation_name='Daily Plane', + corporation_ticker='DP', + member_count=99, + alliance=None + ) + cls.user_2 = User.objects.create_user( + cls.character_2.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=cls.character_2, + owner_hash='x1' + cls.character_2.character_name, + user=cls.user_2 + ) + cls.user_2.profile.main_character = cls.character_2 + cls.user_2.profile.save() + DiscordUser.objects.create( + user=cls.user_2, + uid=1002 + ) + + # user 3 - no main, no group, superuser + cls.character_3 = EveCharacter.objects.create( + character_id=1101, + character_name='Lex Luthor', + corporation_id=2101, + corporation_name='Lex Corp', + corporation_ticker='LC', + alliance_id=None + ) + EveCorporationInfo.objects.create( + corporation_id=2101, + corporation_name='Lex Corp', + corporation_ticker='LC', + member_count=666, + alliance=None + ) + EveAllianceInfo.objects.create( + alliance_id='3101', + alliance_name='Lex World Domination', + alliance_ticker='LWD', + executor_corp_id='' + ) + cls.user_3 = User.objects.create_user( + cls.character_3.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=cls.character_3, + owner_hash='x1' + cls.character_3.character_name, + user=cls.user_3 + ) + DiscordUser.objects.create( + user=cls.user_3, + uid=1003 + ) + + + def setUp(self): + self.factory = RequestFactory() + self.modeladmin = DiscordUserAdmin( + model=DiscordUser, admin_site=AdminSite() + ) + + # column rendering + + def test_user_profile_pic_u1(self): + expected = ('') + self.assertEqual(user_profile_pic(self.user_1.discord), expected) + + def test_user_profile_pic_u3(self): + self.assertIsNone(user_profile_pic(self.user_3.discord)) + + def test_user_username_u1(self): + expected = ( + '' + 'Bruce_Wayne
Bruce Wayne'.format( + self.user_1.discord.pk + ) + ) + self.assertEqual(user_username(self.user_1.discord), expected) + + def test_user_username_u3(self): + expected = ( + '' + 'Lex_Luthor'.format(self.user_3.discord.pk) + ) + self.assertEqual(user_username(self.user_3.discord), expected) + + def test_user_main_organization_u1(self): + expected = 'Wayne Technologies
Wayne Enterprises' + result = user_main_organization(self.user_1.discord) + self.assertEqual(result, expected) + + def test_user_main_organization_u2(self): + expected = 'Daily Planet' + result = user_main_organization(self.user_2.discord) + self.assertEqual(result, expected) + + def test_user_main_organization_u3(self): + expected = None + result = user_main_organization(self.user_3.discord) + self.assertEqual(result, expected) + + # actions + + # filters + def test_filter_main_corporations(self): + + class DiscordUserAdminTest(ServicesUserAdmin): + list_filter = (MainCorporationsFilter,) + + my_modeladmin = DiscordUserAdminTest(DiscordUser, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + ('2002', 'Daily Planet'), + ('2001', 'Wayne Technologies'), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned + request = self.factory.get( + '/', + {'main_corporation_id__exact': self.character_1.corporation_id} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = [self.user_1.discord] + self.assertSetEqual(set(queryset), set(expected)) + + def test_filter_main_alliances(self): + + class DiscordUserAdminTest(ServicesUserAdmin): + list_filter = (MainAllianceFilter,) + + my_modeladmin = DiscordUserAdminTest(DiscordUser, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + ('3001', 'Wayne Enterprises'), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned + request = self.factory.get( + '/', + {'main_alliance_id__exact': self.character_1.alliance_id} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = [self.user_1.discord] + self.assertSetEqual(set(queryset), set(expected)) + \ No newline at end of file diff --git a/allianceauth/services/modules/discord/tests/test_hooks.py b/allianceauth/services/modules/discord/tests/test_hooks.py new file mode 100644 index 00000000..d7c82c8c --- /dev/null +++ b/allianceauth/services/modules/discord/tests/test_hooks.py @@ -0,0 +1,127 @@ +from unittest import mock + +from django.test import TestCase, RequestFactory +from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist +from allianceauth.tests.auth_utils import AuthUtils + +from ..auth_hooks import DiscordService +from ..models import DiscordUser +from ..tasks import DiscordTasks +from ..manager import DiscordOAuthManager + +from . import DEFAULT_AUTH_GROUP, add_permissions, MODULE_PATH + + +class DiscordHooksTestCase(TestCase): + def setUp(self): + self.member = 'member_user' + member = AuthUtils.create_member(self.member) + DiscordUser.objects.create(user=member, uid='12345') + self.none_user = 'none_user' + none_user = AuthUtils.create_user(self.none_user) + self.service = DiscordService + add_permissions() + + def test_has_account(self): + member = User.objects.get(username=self.member) + none_user = User.objects.get(username=self.none_user) + self.assertTrue(DiscordTasks.has_account(member)) + self.assertFalse(DiscordTasks.has_account(none_user)) + + def test_service_enabled(self): + service = self.service() + member = User.objects.get(username=self.member) + none_user = User.objects.get(username=self.none_user) + + self.assertTrue(service.service_active_for_user(member)) + self.assertFalse(service.service_active_for_user(none_user)) + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_update_all_groups(self, manager): + service = self.service() + service.update_all_groups() + # Check member and blue user have groups updated + self.assertTrue(manager.update_groups.called) + self.assertEqual(manager.update_groups.call_count, 1) + + def test_update_groups(self): + # Check member has Member group updated + with mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') as manager: + service = self.service() + member = User.objects.get(username=self.member) + AuthUtils.disconnect_signals() + service.update_groups(member) + self.assertTrue(manager.update_groups.called) + args, kwargs = manager.update_groups.call_args + user_id, groups = args + self.assertIn(DEFAULT_AUTH_GROUP, groups) + self.assertEqual(user_id, member.discord.uid) + + # Check none user does not have groups updated + with mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') as manager: + service = self.service() + none_user = User.objects.get(username=self.none_user) + service.update_groups(none_user) + self.assertFalse(manager.update_groups.called) + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_validate_user(self, manager): + service = self.service() + # Test member is not deleted + member = User.objects.get(username=self.member) + service.validate_user(member) + self.assertTrue(member.discord) + + # Test none user is deleted + none_user = User.objects.get(username=self.none_user) + DiscordUser.objects.create(user=none_user, uid='abc123') + service.validate_user(none_user) + self.assertTrue(manager.delete_user.called) + with self.assertRaises(ObjectDoesNotExist): + none_discord = User.objects.get(username=self.none_user).discord + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_sync_nickname(self, manager): + service = self.service() + member = User.objects.get(username=self.member) + AuthUtils.add_main_character(member, 'test user', '12345', corp_ticker='AAUTH') + + service.sync_nickname(member) + + self.assertTrue(manager.update_nickname.called) + args, kwargs = manager.update_nickname.call_args + self.assertEqual(args[0], member.discord.uid) + self.assertEqual(args[1], 'test user') + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_delete_user(self, manager): + member = User.objects.get(username=self.member) + + service = self.service() + result = service.delete_user(member) + + self.assertTrue(result) + self.assertTrue(manager.delete_user.called) + with self.assertRaises(ObjectDoesNotExist): + discord_user = User.objects.get(username=self.member).discord + + def test_render_services_ctrl(self): + service = self.service() + member = User.objects.get(username=self.member) + request = RequestFactory().get('/services/') + request.user = member + + response = service.render_services_ctrl(request) + self.assertTemplateUsed(service.service_ctrl_template) + self.assertIn('/discord/reset/', response) + self.assertIn('/discord/deactivate/', response) + + # Test register becomes available + member.discord.delete() + member = User.objects.get(username=self.member) + request.user = member + response = service.render_services_ctrl(request) + self.assertIn('/discord/activate/', response) + + # TODO: Test update nicknames diff --git a/allianceauth/services/modules/discord/tests.py b/allianceauth/services/modules/discord/tests/test_managers.py similarity index 55% rename from allianceauth/services/modules/discord/tests.py rename to allianceauth/services/modules/discord/tests/test_managers.py index 87916fd0..2ee838a7 100644 --- a/allianceauth/services/modules/discord/tests.py +++ b/allianceauth/services/modules/discord/tests/test_managers.py @@ -2,197 +2,15 @@ import json import urllib import datetime import requests_mock -from django_webtest import WebTest from unittest import mock -from django.test import TestCase, RequestFactory -from django.contrib.auth.models import User, Group, Permission -from django.core.exceptions import ObjectDoesNotExist +from django.test import TestCase from django.conf import settings -from allianceauth.tests.auth_utils import AuthUtils -from .auth_hooks import DiscordService -from .models import DiscordUser -from .tasks import DiscordTasks -from .manager import DiscordOAuthManager -from . import manager +from ..manager import DiscordOAuthManager +from .. import manager - -MODULE_PATH = 'allianceauth.services.modules.discord' -DEFAULT_AUTH_GROUP = 'Member' - - -def add_permissions(): - permission = Permission.objects.get(codename='access_discord') - members = Group.objects.get_or_create(name=DEFAULT_AUTH_GROUP)[0] - AuthUtils.add_permissions_to_groups([permission], [members]) - - -class DiscordHooksTestCase(TestCase): - def setUp(self): - self.member = 'member_user' - member = AuthUtils.create_member(self.member) - DiscordUser.objects.create(user=member, uid='12345') - self.none_user = 'none_user' - none_user = AuthUtils.create_user(self.none_user) - self.service = DiscordService - add_permissions() - - def test_has_account(self): - member = User.objects.get(username=self.member) - none_user = User.objects.get(username=self.none_user) - self.assertTrue(DiscordTasks.has_account(member)) - self.assertFalse(DiscordTasks.has_account(none_user)) - - def test_service_enabled(self): - service = self.service() - member = User.objects.get(username=self.member) - none_user = User.objects.get(username=self.none_user) - - self.assertTrue(service.service_active_for_user(member)) - self.assertFalse(service.service_active_for_user(none_user)) - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_update_all_groups(self, manager): - service = self.service() - service.update_all_groups() - # Check member and blue user have groups updated - self.assertTrue(manager.update_groups.called) - self.assertEqual(manager.update_groups.call_count, 1) - - def test_update_groups(self): - # Check member has Member group updated - with mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') as manager: - service = self.service() - member = User.objects.get(username=self.member) - AuthUtils.disconnect_signals() - service.update_groups(member) - self.assertTrue(manager.update_groups.called) - args, kwargs = manager.update_groups.call_args - user_id, groups = args - self.assertIn(DEFAULT_AUTH_GROUP, groups) - self.assertEqual(user_id, member.discord.uid) - - # Check none user does not have groups updated - with mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') as manager: - service = self.service() - none_user = User.objects.get(username=self.none_user) - service.update_groups(none_user) - self.assertFalse(manager.update_groups.called) - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_validate_user(self, manager): - service = self.service() - # Test member is not deleted - member = User.objects.get(username=self.member) - service.validate_user(member) - self.assertTrue(member.discord) - - # Test none user is deleted - none_user = User.objects.get(username=self.none_user) - DiscordUser.objects.create(user=none_user, uid='abc123') - service.validate_user(none_user) - self.assertTrue(manager.delete_user.called) - with self.assertRaises(ObjectDoesNotExist): - none_discord = User.objects.get(username=self.none_user).discord - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_sync_nickname(self, manager): - service = self.service() - member = User.objects.get(username=self.member) - AuthUtils.add_main_character(member, 'test user', '12345', corp_ticker='AAUTH') - - service.sync_nickname(member) - - self.assertTrue(manager.update_nickname.called) - args, kwargs = manager.update_nickname.call_args - self.assertEqual(args[0], member.discord.uid) - self.assertEqual(args[1], 'test user') - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_delete_user(self, manager): - member = User.objects.get(username=self.member) - - service = self.service() - result = service.delete_user(member) - - self.assertTrue(result) - self.assertTrue(manager.delete_user.called) - with self.assertRaises(ObjectDoesNotExist): - discord_user = User.objects.get(username=self.member).discord - - def test_render_services_ctrl(self): - service = self.service() - member = User.objects.get(username=self.member) - request = RequestFactory().get('/services/') - request.user = member - - response = service.render_services_ctrl(request) - self.assertTemplateUsed(service.service_ctrl_template) - self.assertIn('/discord/reset/', response) - self.assertIn('/discord/deactivate/', response) - - # Test register becomes available - member.discord.delete() - member = User.objects.get(username=self.member) - request.user = member - response = service.render_services_ctrl(request) - self.assertIn('/discord/activate/', response) - - # TODO: Test update nicknames - - -class DiscordViewsTestCase(WebTest): - def setUp(self): - self.member = AuthUtils.create_member('auth_member') - AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc') - add_permissions() - - def login(self): - self.app.set_user(self.member) - - @mock.patch(MODULE_PATH + '.views.DiscordOAuthManager') - def test_activate(self, manager): - self.login() - manager.generate_oauth_redirect_url.return_value = '/example.com/oauth/' - response = self.app.get('/discord/activate/', auto_follow=False) - self.assertRedirects(response, expected_url='/example.com/oauth/', target_status_code=404) - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_callback(self, manager): - self.login() - manager.add_user.return_value = '1234' - response = self.app.get('/discord/callback/', params={'code': '1234'}) - - self.member = User.objects.get(pk=self.member.pk) - - self.assertTrue(manager.add_user.called) - self.assertEqual(manager.update_nickname.called, settings.DISCORD_SYNC_NAMES) - self.assertEqual(self.member.discord.uid, '1234') - self.assertRedirects(response, expected_url='/services/', target_status_code=200) - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_reset(self, manager): - self.login() - DiscordUser.objects.create(user=self.member, uid='12345') - manager.delete_user.return_value = True - - response = self.app.get('/discord/reset/') - - self.assertRedirects(response, expected_url='/discord/activate/', target_status_code=302) - - @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') - def test_deactivate(self, manager): - self.login() - DiscordUser.objects.create(user=self.member, uid='12345') - manager.delete_user.return_value = True - - response = self.app.get('/discord/deactivate/') - - self.assertTrue(manager.delete_user.called) - self.assertRedirects(response, expected_url='/services/', target_status_code=200) - with self.assertRaises(ObjectDoesNotExist): - discord_user = User.objects.get(pk=self.member.pk).discord +from . import DEFAULT_AUTH_GROUP, add_permissions, MODULE_PATH class DiscordManagerTestCase(TestCase): diff --git a/allianceauth/services/modules/discord/tests/test_views.py b/allianceauth/services/modules/discord/tests/test_views.py new file mode 100644 index 00000000..8de0e2eb --- /dev/null +++ b/allianceauth/services/modules/discord/tests/test_views.py @@ -0,0 +1,66 @@ +from django_webtest import WebTest +from unittest import mock + +from django.test import TestCase +from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist +from django.conf import settings +from allianceauth.tests.auth_utils import AuthUtils + +from ..models import DiscordUser +from ..manager import DiscordOAuthManager + +from . import DEFAULT_AUTH_GROUP, add_permissions, MODULE_PATH + + +class DiscordViewsTestCase(WebTest): + def setUp(self): + self.member = AuthUtils.create_member('auth_member') + AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc') + add_permissions() + + def login(self): + self.app.set_user(self.member) + + @mock.patch(MODULE_PATH + '.views.DiscordOAuthManager') + def test_activate(self, manager): + self.login() + manager.generate_oauth_redirect_url.return_value = '/example.com/oauth/' + response = self.app.get('/discord/activate/', auto_follow=False) + self.assertRedirects(response, expected_url='/example.com/oauth/', target_status_code=404) + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_callback(self, manager): + self.login() + manager.add_user.return_value = '1234' + response = self.app.get('/discord/callback/', params={'code': '1234'}) + + self.member = User.objects.get(pk=self.member.pk) + + self.assertTrue(manager.add_user.called) + self.assertEqual(manager.update_nickname.called, settings.DISCORD_SYNC_NAMES) + self.assertEqual(self.member.discord.uid, '1234') + self.assertRedirects(response, expected_url='/services/', target_status_code=200) + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_reset(self, manager): + self.login() + DiscordUser.objects.create(user=self.member, uid='12345') + manager.delete_user.return_value = True + + response = self.app.get('/discord/reset/') + + self.assertRedirects(response, expected_url='/discord/activate/', target_status_code=302) + + @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager') + def test_deactivate(self, manager): + self.login() + DiscordUser.objects.create(user=self.member, uid='12345') + manager.delete_user.return_value = True + + response = self.app.get('/discord/deactivate/') + + self.assertTrue(manager.delete_user.called) + self.assertRedirects(response, expected_url='/services/', target_status_code=200) + with self.assertRaises(ObjectDoesNotExist): + discord_user = User.objects.get(pk=self.member.pk).discord From 3bd8107fcf6637ed475e1217432d495190be5cf4 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Fri, 14 Feb 2020 01:03:07 +0100 Subject: [PATCH 15/16] Move groupmgmt test to subfolder, add admin tests --- allianceauth/groupmanagement/admin.py | 31 +- .../groupmanagement/tests/__init__.py | 0 .../groupmanagement/tests/test_admin.py | 379 ++++++++++++++++++ .../{tests.py => tests/test_all.py} | 0 4 files changed, 393 insertions(+), 17 deletions(-) create mode 100644 allianceauth/groupmanagement/tests/__init__.py create mode 100644 allianceauth/groupmanagement/tests/test_admin.py rename allianceauth/groupmanagement/{tests.py => tests/test_all.py} (100%) diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 6d1a7ef0..6a5cf71c 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -38,22 +38,22 @@ class AuthGroupInlineAdmin(admin.StackedInline): if _has_auto_groups: class IsAutoGroupFilter(admin.SimpleListFilter): title = 'auto group' - parameter_name = 'auto_group' + parameter_name = 'is_auto_group__exact' def lookups(self, request, model_admin): return ( - ('Yes', 'Yes'), - ('No', 'No'), + ('yes', 'Yes'), + ('no', 'No'), ) def queryset(self, request, queryset): value = self.value() - if value == 'Yes': + if value == 'yes': return queryset.exclude( managedalliancegroup__isnull=True, managedcorpgroup__isnull=True ) - elif value == 'No': + elif value == 'no': return queryset.filter( managedalliancegroup__isnull=True, managedcorpgroup__isnull=True @@ -64,19 +64,19 @@ if _has_auto_groups: class HasLeaderFilter(admin.SimpleListFilter): title = 'has leader' - parameter_name = 'has_leader' + parameter_name = 'has_leader__exact' def lookups(self, request, model_admin): return ( - ('Yes', 'Yes'), - ('No', 'No'), + ('yes', 'Yes'), + ('no', 'No'), ) def queryset(self, request, queryset): value = self.value() - if value == 'Yes': + if value == 'yes': return queryset.filter(authgroup__group_leaders__isnull=False) - elif value == 'No': + elif value == 'no': return queryset.filter(authgroup__group_leaders__isnull=True) else: return queryset @@ -86,12 +86,11 @@ class GroupAdmin(admin.ModelAdmin): ordering = ('name', ) list_display = ( 'name', - 'description', + '_description', '_properties', '_member_count', 'has_leader' ) - list_filter = ( 'authgroup__internal', 'authgroup__hidden', @@ -101,7 +100,6 @@ class GroupAdmin(admin.ModelAdmin): HasLeaderFilter ) search_fields = ('name', 'authgroup__description') - filter_horizontal = ('permissions',) inlines = (AuthGroupInlineAdmin,) @@ -112,7 +110,7 @@ class GroupAdmin(admin.ModelAdmin): ) return qs - def description(self, obj): + def _description(self, obj): return obj.authgroup.description def _member_count(self, obj): @@ -125,7 +123,6 @@ class GroupAdmin(admin.ModelAdmin): return obj.authgroup.group_leaders.exists() has_leader.boolean = True - def _properties(self, obj): properties = list() @@ -145,8 +142,8 @@ class GroupAdmin(admin.ModelAdmin): properties.append('Public') if not properties: properties.append('Default') - - return ', '.join(properties) + + return properties _properties.short_description = "properties" diff --git a/allianceauth/groupmanagement/tests/__init__.py b/allianceauth/groupmanagement/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/groupmanagement/tests/test_admin.py b/allianceauth/groupmanagement/tests/test_admin.py new file mode 100644 index 00000000..2d9f452b --- /dev/null +++ b/allianceauth/groupmanagement/tests/test_admin.py @@ -0,0 +1,379 @@ +from unittest.mock import patch + +from django.test import TestCase, RequestFactory +from django.contrib import admin +from django.contrib.admin.sites import AdminSite +from django.contrib.auth.models import User + +from allianceauth.authentication.models import CharacterOwnership, State +from allianceauth.eveonline.autogroups.models import AutogroupsConfig +from allianceauth.eveonline.models import ( + EveCharacter, EveCorporationInfo, EveAllianceInfo +) + +from ..admin import ( + IsAutoGroupFilter, + HasLeaderFilter, + GroupAdmin, + Group +) + + +MODULE_PATH = 'allianceauth.groupmanagement.admin' + + +class MockRequest(object): + + def __init__(self, user=None): + self.user = user + + +class TestGroupAdmin(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # group 1 - has leader + cls.group_1 = Group.objects.create(name='Group 1') + cls.group_1.authgroup.description = 'Default Group' + cls.group_1.authgroup.internal = False + cls.group_1.authgroup.hidden = False + cls.group_1.authgroup.save() + + # group 2 - no leader + cls.group_2 = Group.objects.create(name='Group 2') + cls.group_2.authgroup.description = 'Internal Group' + cls.group_2.authgroup.internal = True + cls.group_2.authgroup.save() + + # group 3 - has leader + cls.group_3 = Group.objects.create(name='Group 3') + cls.group_3.authgroup.description = 'Hidden Group' + cls.group_3.authgroup.internal = False + cls.group_3.authgroup.hidden = True + cls.group_3.authgroup.save() + + # group 4 - no leader + cls.group_4 = Group.objects.create(name='Group 4') + cls.group_4.authgroup.description = 'Open Group' + cls.group_4.authgroup.internal = False + cls.group_4.authgroup.hidden = False + cls.group_4.authgroup.open = True + cls.group_4.authgroup.save() + + # group 5 - no leader + cls.group_5 = Group.objects.create(name='Group 5') + cls.group_5.authgroup.description = 'Public Group' + cls.group_5.authgroup.internal = False + cls.group_5.authgroup.hidden = False + cls.group_5.authgroup.public = True + cls.group_5.authgroup.save() + + # group 6 - no leader + cls.group_6 = Group.objects.create(name='Group 6') + cls.group_6.authgroup.description = 'Mixed Group' + cls.group_6.authgroup.internal = False + cls.group_6.authgroup.hidden = True + cls.group_6.authgroup.open = True + cls.group_6.authgroup.public = True + cls.group_6.authgroup.save() + + # user 1 - corp and alliance, normal user + cls.character_1 = EveCharacter.objects.create( + character_id='1001', + character_name='Bruce Wayne', + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + ) + cls.character_1a = EveCharacter.objects.create( + character_id='1002', + character_name='Batman', + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + ) + alliance = EveAllianceInfo.objects.create( + alliance_id='3001', + alliance_name='Wayne Enterprises', + alliance_ticker='WE', + executor_corp_id='2001' + ) + EveCorporationInfo.objects.create( + corporation_id='2001', + corporation_name='Wayne Technologies', + corporation_ticker='WT', + member_count=42, + alliance=alliance + ) + cls.user_1 = User.objects.create_user( + cls.character_1.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=cls.character_1, + owner_hash='x1' + cls.character_1.character_name, + user=cls.user_1 + ) + CharacterOwnership.objects.create( + character=cls.character_1a, + owner_hash='x1' + cls.character_1a.character_name, + user=cls.user_1 + ) + cls.user_1.profile.main_character = cls.character_1 + cls.user_1.profile.save() + cls.user_1.groups.add(cls.group_1) + cls.group_1.authgroup.group_leaders.add(cls.user_1) + + # user 2 - corp only, staff + cls.character_2 = EveCharacter.objects.create( + character_id=1003, + character_name='Clark Kent', + corporation_id=2002, + corporation_name='Daily Planet', + corporation_ticker='DP', + alliance_id=None + ) + EveCorporationInfo.objects.create( + corporation_id=2002, + corporation_name='Daily Plane', + corporation_ticker='DP', + member_count=99, + alliance=None + ) + cls.user_2 = User.objects.create_user( + cls.character_2.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=cls.character_2, + owner_hash='x1' + cls.character_2.character_name, + user=cls.user_2 + ) + cls.user_2.profile.main_character = cls.character_2 + cls.user_2.profile.save() + cls.user_2.groups.add(cls.group_2) + cls.user_2.is_staff = True + cls.user_2.save() + + # user 3 - no main, no group, superuser + cls.character_3 = EveCharacter.objects.create( + character_id=1101, + character_name='Lex Luthor', + corporation_id=2101, + corporation_name='Lex Corp', + corporation_ticker='LC', + alliance_id=None + ) + EveCorporationInfo.objects.create( + corporation_id=2101, + corporation_name='Lex Corp', + corporation_ticker='LC', + member_count=666, + alliance=None + ) + EveAllianceInfo.objects.create( + alliance_id='3101', + alliance_name='Lex World Domination', + alliance_ticker='LWD', + executor_corp_id='' + ) + cls.user_3 = User.objects.create_user( + cls.character_3.character_name.replace(' ', '_'), + 'abc@example.com', + 'password' + ) + CharacterOwnership.objects.create( + character=cls.character_3, + owner_hash='x1' + cls.character_3.character_name, + user=cls.user_3 + ) + cls.user_3.is_superuser = True + cls.user_3.save() + cls.user_3.groups.add(cls.group_3) + cls.group_3.authgroup.group_leaders.add(cls.user_3) + + def setUp(self): + self.factory = RequestFactory() + self.modeladmin = GroupAdmin( + model=Group, admin_site=AdminSite() + ) + + def _create_autogroups(self): + """create autogroups for corps and alliances""" + autogroups_config = AutogroupsConfig( + corp_groups = True, + alliance_groups = True + ) + autogroups_config.save() + for state in State.objects.all(): + autogroups_config.states.add(state) + autogroups_config.update_corp_group_membership(self.user_1) + + # column rendering + + def test_description(self): + expected = 'Default Group' + result = self.modeladmin._description(self.group_1) + self.assertEqual(result, expected) + + def test_member_count(self): + expected = 1 + obj = self.modeladmin.get_queryset(MockRequest(user=self.user_1))\ + .get(pk=self.group_1.pk) + result = self.modeladmin._member_count(obj) + self.assertEqual(result, expected) + + def test_has_leader(self): + result = self.modeladmin.has_leader(self.group_1) + self.assertTrue(result) + + def test_properties_1(self): + expected = ['Default'] + result = self.modeladmin._properties(self.group_1) + self.assertListEqual(result, expected) + + def test_properties_2(self): + expected = ['Internal'] + result = self.modeladmin._properties(self.group_2) + self.assertListEqual(result, expected) + + def test_properties_3(self): + expected = ['Hidden'] + result = self.modeladmin._properties(self.group_3) + self.assertListEqual(result, expected) + + def test_properties_4(self): + expected = ['Open'] + result = self.modeladmin._properties(self.group_4) + self.assertListEqual(result, expected) + + def test_properties_5(self): + expected = ['Public'] + result = self.modeladmin._properties(self.group_5) + self.assertListEqual(result, expected) + + def test_properties_6(self): + expected = ['Hidden', 'Open', 'Public'] + result = self.modeladmin._properties(self.group_6) + self.assertListEqual(result, expected) + + @patch(MODULE_PATH + '._has_auto_groups', True) + def test_properties_6(self): + self._create_autogroups() + expected = ['Auto Group'] + my_group = Group.objects\ + .filter(managedcorpgroup__isnull=False)\ + .first() + result = self.modeladmin._properties(my_group) + self.assertListEqual(result, expected) + + # actions + + # filters + + @patch(MODULE_PATH + '._has_auto_groups', True) + def test_filter_is_auto_group(self): + + class GroupAdminTest(admin.ModelAdmin): + list_filter = (IsAutoGroupFilter,) + + self._create_autogroups() + my_modeladmin = GroupAdminTest(Group, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + ('yes', 'Yes'), + ('no', 'No'), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned - no + request = self.factory.get( + '/', {'is_auto_group__exact': 'no'} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = [ + self.group_1, + self.group_2, + self.group_3, + self.group_4, + self.group_5, + self.group_6 + ] + self.assertSetEqual(set(queryset), set(expected)) + + # Make sure the correct queryset is returned - yes + request = self.factory.get( + '/', {'is_auto_group__exact': 'yes'} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = Group.objects.exclude( + managedalliancegroup__isnull=True, + managedcorpgroup__isnull=True + ) + self.assertSetEqual(set(queryset), set(expected)) + + def test_filter_has_leader(self): + + class GroupAdminTest(admin.ModelAdmin): + list_filter = (HasLeaderFilter,) + + self._create_autogroups() + my_modeladmin = GroupAdminTest(Group, AdminSite()) + + # Make sure the lookups are correct + request = self.factory.get('/') + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + filters = changelist.get_filters(request) + filterspec = filters[0][0] + expected = [ + ('yes', 'Yes'), + ('no', 'No'), + ] + self.assertEqual(filterspec.lookup_choices, expected) + + # Make sure the correct queryset is returned - no + request = self.factory.get( + '/', {'has_leader__exact': 'no'} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = Group.objects.exclude(pk__in=[ + self.group_1.pk, self.group_3.pk + ]) + self.assertSetEqual(set(queryset), set(expected)) + + # Make sure the correct queryset is returned - yes + request = self.factory.get( + '/', {'has_leader__exact': 'yes'} + ) + request.user = self.user_1 + changelist = my_modeladmin.get_changelist_instance(request) + queryset = changelist.get_queryset(request) + expected = [ + self.group_1, + self.group_3 + ] + self.assertSetEqual(set(queryset), set(expected)) + \ No newline at end of file diff --git a/allianceauth/groupmanagement/tests.py b/allianceauth/groupmanagement/tests/test_all.py similarity index 100% rename from allianceauth/groupmanagement/tests.py rename to allianceauth/groupmanagement/tests/test_all.py From f2ba7414993f3eba2b0613bbe5a367bda255b641 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Tue, 18 Feb 2020 01:35:17 +0100 Subject: [PATCH 16/16] Add sorting to user for group, state for character, corporation, alliance and group for group user leaders, group group leaders --- allianceauth/authentication/admin.py | 54 +++++++++++++++---- .../static/authentication/css/admin.css | 9 +++- allianceauth/groupmanagement/admin.py | 19 +++++-- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index dac057d8..0393168e 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -20,7 +20,8 @@ from allianceauth.authentication.models import State, get_guest_state,\ from allianceauth.hooks import get_hooks from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo from allianceauth.eveonline.tasks import update_character -from .app_settings import * +from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \ + AUTHENTICATION_ADMIN_USERS_MAX_CHARS if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: _has_auto_groups = True @@ -365,7 +366,7 @@ class UserAdmin(BaseUserAdmin): 'username', 'character_ownerships__character__character_name' ) - + def _characters(self, obj): my_characters = [ x.character.character_name @@ -426,19 +427,54 @@ class UserAdmin(BaseUserAdmin): def has_delete_permission(self, request, obj=None): return request.user.has_perm('auth.delete_user') + def formfield_for_manytomany(self, db_field, request, **kwargs): + """overriding this formfield to have sorted lists in the form""" + if db_field.name == "groups": + kwargs["queryset"] = Group.objects.all().order_by(Lower('name')) + return super().formfield_for_manytomany(db_field, request, **kwargs) + @admin.register(State) -class StateAdmin(admin.ModelAdmin): +class StateAdmin(admin.ModelAdmin): + list_select_related = True + list_display = ('name', 'priority', '_user_count') + + def _user_count(self, obj): + return obj.userprofile_set.all().count() + _user_count.short_description = 'Users' + fieldsets = ( (None, { 'fields': ('name', 'permissions', 'priority'), }), ('Membership', { - 'fields': ('public', 'member_characters', 'member_corporations', 'member_alliances'), + 'fields': ( + 'public', + 'member_characters', + 'member_corporations', + 'member_alliances' + ), }) ) - filter_horizontal = ['member_characters', 'member_corporations', 'member_alliances', 'permissions'] - list_display = ('name', 'priority', 'user_count') + filter_horizontal = [ + 'member_characters', + 'member_corporations', + 'member_alliances', + 'permissions' + ] + + def formfield_for_manytomany(self, db_field, request, **kwargs): + """overriding this formfield to have sorted lists in the form""" + if db_field.name == "member_characters": + kwargs["queryset"] = EveCharacter.objects.all()\ + .order_by(Lower('character_name')) + elif db_field.name == "member_corporations": + kwargs["queryset"] = EveCorporationInfo.objects.all()\ + .order_by(Lower('corporation_name')) + elif db_field.name == "member_alliances": + kwargs["queryset"] = EveAllianceInfo.objects.all()\ + .order_by(Lower('alliance_name')) + return super().formfield_for_manytomany(db_field, request, **kwargs) def has_delete_permission(self, request, obj=None): if obj == get_guest_state(): @@ -453,11 +489,7 @@ class StateAdmin(admin.ModelAdmin): }), ) return super(StateAdmin, self).get_fieldsets(request, obj=obj) - - @staticmethod - def user_count(obj): - return obj.userprofile_set.all().count() - + class BaseOwnershipAdmin(admin.ModelAdmin): class Media: diff --git a/allianceauth/authentication/static/authentication/css/admin.css b/allianceauth/authentication/static/authentication/css/admin.css index 5fe8a4d8..489edd2e 100644 --- a/allianceauth/authentication/static/authentication/css/admin.css +++ b/allianceauth/authentication/static/authentication/css/admin.css @@ -3,8 +3,13 @@ CSS for allianceauth admin site */ /* styling for profile pic */ -.img-circle { border-radius: 50%; } -.column-user_profile_pic { width: 50px; } +.img-circle { + border-radius: 50%; +} +.column-user_profile_pic { + width: 1px; + white-space: nowrap; +} /* tooltip */ .tooltip { diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index 6a5cf71c..eaf9060d 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -3,6 +3,7 @@ from django.conf import settings from django.contrib import admin from django.contrib.auth.models import Group as BaseGroup from django.db.models import Count +from django.db.models.functions import Lower from django.db.models.signals import pre_save, post_save, pre_delete, \ post_delete, m2m_changed from django.dispatch import receiver @@ -25,6 +26,17 @@ class AuthGroupInlineAdmin(admin.StackedInline): verbose_name_plural = 'Auth Settings' verbose_name = '' + def formfield_for_manytomany(self, db_field, request, **kwargs): + """overriding this formfield to have sorted lists in the form""" + if db_field.name == "group_leaders": + kwargs["queryset"] = User.objects\ + .filter(profile__state__name='Member')\ + .order_by(Lower('username')) + elif db_field.name == "group_leader_groups": + kwargs["queryset"] = Group.objects\ + .order_by(Lower('name')) + return super().formfield_for_manytomany(db_field, request, **kwargs) + def has_add_permission(self, request): return False @@ -100,9 +112,7 @@ class GroupAdmin(admin.ModelAdmin): HasLeaderFilter ) search_fields = ('name', 'authgroup__description') - filter_horizontal = ('permissions',) - inlines = (AuthGroupInlineAdmin,) - + def get_queryset(self, request): qs = super().get_queryset(request) qs = qs.annotate( @@ -147,6 +157,9 @@ class GroupAdmin(admin.ModelAdmin): _properties.short_description = "properties" + filter_horizontal = ('permissions',) + inlines = (AuthGroupInlineAdmin,) + class Group(BaseGroup): class Meta: