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: