From 148e208476b612937ac9d2b891166d328f3692ef Mon Sep 17 00:00:00 2001 From: colcrunch Date: Sun, 18 Apr 2021 22:14:52 -0400 Subject: [PATCH 1/9] Update cron schedule for `discord.update_all_usernames` task in docs. --- docs/features/services/discord.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/services/discord.md b/docs/features/services/discord.md index 3a13125c..78364d56 100644 --- a/docs/features/services/discord.md +++ b/docs/features/services/discord.md @@ -26,7 +26,7 @@ DISCORD_SYNC_NAMES = False CELERYBEAT_SCHEDULE['discord.update_all_usernames'] = { 'task': 'discord.update_all_usernames', - 'schedule': crontab(hour='*/12'), + 'schedule': crontab(minute=0, hour='*/12'), } ``` From 937d65622771e16c2a8a91a3a3df5fee793e763a Mon Sep 17 00:00:00 2001 From: Erik Kalkoken Date: Wed, 5 May 2021 09:04:43 +0000 Subject: [PATCH 2/9] Remove redis ping and consolidate before steps --- .gitlab-ci.yml | 46 ++++++++-------------------------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e8ea27d..d973dd57 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,28 +7,28 @@ include: - template: Dependency-Scanning.gitlab-ci.yml - template: Security/SAST.gitlab-ci.yml +before_script: + - apt-get update && apt-get install redis-server -y + - redis-server --daemonize yes + - python -V + - pip install wheel tox + sast: stage: gitlab + before_script: [] dependency_scanning: stage: gitlab before_script: - apt-get update && apt-get install redis-server libmariadbclient-dev -y - redis-server --daemonize yes - - redis-cli ping - python -V - pip install wheel tox test-3.6-core: image: python:3.6-buster script: - - tox -e py36-core - before_script: - - apt-get update && apt-get install redis-server -y - - redis-server --daemonize yes - - redis-cli ping - - python -V - - pip install wheel tox + - tox -e py36-core artifacts: when: always reports: @@ -38,12 +38,6 @@ test-3.7-core: image: python:3.7-buster script: - tox -e py37-core - before_script: - - apt-get update && apt-get install redis-server -y - - redis-server --daemonize yes - - redis-cli ping - - python -V - - pip install wheel tox artifacts: when: always reports: @@ -53,12 +47,6 @@ test-3.8-core: image: python:3.8-buster script: - tox -e py38-core - before_script: - - apt-get update && apt-get install redis-server -y - - redis-server --daemonize yes - - redis-cli ping - - python -V - - pip install wheel tox artifacts: when: always reports: @@ -68,12 +56,6 @@ test-3.6-all: image: python:3.6-buster script: - tox -e py36-all - before_script: - - apt-get update && apt-get install redis-server -y - - redis-server --daemonize yes - - redis-cli ping - - python -V - - pip install wheel tox artifacts: when: always reports: @@ -83,12 +65,6 @@ test-3.7-all: image: python:3.7-buster script: - tox -e py37-all - before_script: - - apt-get update && apt-get install redis-server -y - - redis-server --daemonize yes - - redis-cli ping - - python -V - - pip install wheel tox artifacts: when: always reports: @@ -98,12 +74,6 @@ test-3.8-all: image: python:3.8-buster script: - tox -e py38-all - before_script: - - apt-get update && apt-get install redis-server -y - - redis-server --daemonize yes - - redis-cli ping - - python -V - - pip install wheel tox artifacts: when: always reports: From 07258a6914ca76e3df6fdc326c18d264a76f73b5 Mon Sep 17 00:00:00 2001 From: ErikKalkoken Date: Sat, 8 May 2021 13:20:57 +0200 Subject: [PATCH 3/9] Log HTTP errors from gitlab as warning instead of error --- .../authentication/tests/test_templatetags.py | 75 +++++++++++++------ allianceauth/templatetags/admin_status.py | 8 +- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/allianceauth/authentication/tests/test_templatetags.py b/allianceauth/authentication/tests/test_templatetags.py index b556f9de..57b315b6 100644 --- a/allianceauth/authentication/tests/test_templatetags.py +++ b/allianceauth/authentication/tests/test_templatetags.py @@ -1,10 +1,10 @@ from math import ceil from unittest.mock import patch -from requests import RequestException import requests_mock from packaging.version import Version as Pep440Version +from django.core.cache import cache from django.test import TestCase from allianceauth.templatetags.admin_status import ( @@ -12,8 +12,7 @@ from allianceauth.templatetags.admin_status import ( _fetch_list_from_gitlab, _current_notifications, _current_version_summary, - _fetch_notification_issues_from_gitlab, - _fetch_tags_from_gitlab, + _fetch_notification_issues_from_gitlab, _latests_versions ) @@ -103,35 +102,51 @@ class TestStatusOverviewTag(TestCase): class TestNotifications(TestCase): + def setUp(self) -> None: + cache.clear() + @requests_mock.mock() def test_fetch_notification_issues_from_gitlab(self, requests_mocker): + # given url = ( 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues' '?labels=announcement' ) requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES) + # when result = _fetch_notification_issues_from_gitlab() + # then self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES) @patch(MODULE_PATH + '.admin_status.cache') def test_current_notifications_normal(self, mock_cache): + # given mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES - + # when result = _current_notifications() + # then self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5]) - @patch(MODULE_PATH + '.admin_status.cache') - def test_current_notifications_failed(self, mock_cache): - mock_cache.get_or_set.side_effect = RequestException - + @requests_mock.mock() + def test_current_notifications_failed(self, requests_mocker): + # given + url = ( + 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues' + '?labels=announcement' + ) + requests_mocker.get(url, status_code=404) + # when result = _current_notifications() + # then self.assertEqual(result['notifications'], list()) @patch(MODULE_PATH + '.admin_status.cache') def test_current_notifications_is_none(self, mock_cache): + # given mock_cache.get_or_set.return_value = None - + # when result = _current_notifications() + # then self.assertEqual(result['notifications'], list()) @@ -143,12 +158,17 @@ class TestCeleryQueueLength(TestCase): class TestVersionTags(TestCase): + def setUp(self) -> None: + cache.clear() + @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.cache') - def test_current_version_info_normal(self, mock_cache): + def test_current_version_info_normal(self, mock_cache): + # given mock_cache.get_or_set.return_value = GITHUB_TAGS - + # when result = _current_version_summary() + # then self.assertTrue(result['latest_major']) self.assertTrue(result['latest_minor']) self.assertTrue(result['latest_patch']) @@ -158,32 +178,41 @@ class TestVersionTags(TestCase): self.assertEqual(result['latest_beta_version'], '2.4.6a1') @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) - @patch(MODULE_PATH + '.admin_status.cache') - def test_current_version_info_failed(self, mock_cache): - mock_cache.get_or_set.side_effect = RequestException - - expected = {} + @requests_mock.mock() + def test_current_version_info_failed(self, requests_mocker): + # given + url = ( + 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth' + '/repository/tags' + ) + requests_mocker.get(url, status_code=500) + # when result = _current_version_summary() - self.assertEqual(result, expected) + # then + self.assertEqual(result, {}) @requests_mock.mock() def test_fetch_tags_from_gitlab(self, requests_mocker): + # given url = ( 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth' '/repository/tags' ) requests_mocker.get(url, json=GITHUB_TAGS) - result = _fetch_tags_from_gitlab() - self.assertEqual(result, GITHUB_TAGS) + # when + result = _current_version_summary() + # then + self.assertTrue(result) @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.cache') def test_current_version_info_return_no_data(self, mock_cache): - mock_cache.get_or_set.return_value = None - - expected = {} + # given + mock_cache.get_or_set.return_value = None + # when result = _current_version_summary() - self.assertEqual(result, expected) + # then + self.assertEqual(result, {}) class TestLatestsVersion(TestCase): diff --git a/allianceauth/templatetags/admin_status.py b/allianceauth/templatetags/admin_status.py index 2280f5ce..94c6be63 100644 --- a/allianceauth/templatetags/admin_status.py +++ b/allianceauth/templatetags/admin_status.py @@ -70,8 +70,8 @@ def _current_notifications() -> dict: _fetch_notification_issues_from_gitlab, NOTIFICATION_CACHE_TIME ) - except requests.RequestException: - logger.exception('Error while getting gitlab notifications') + except requests.HTTPError: + logger.warning('Error while getting gitlab notifications', exc_info=True) top_notifications = [] else: if notifications: @@ -95,8 +95,8 @@ def _current_version_summary() -> dict: tags = cache.get_or_set( 'git_release_tags', _fetch_tags_from_gitlab, TAG_CACHE_TIME ) - except requests.RequestException: - logger.exception('Error while getting gitlab release tags') + except requests.HTTPError: + logger.warning('Error while getting gitlab release tags', exc_info=True) return {} if not tags: From 1473259e410e677a39ff51ff126c09c15f1c20cc Mon Sep 17 00:00:00 2001 From: Erik Kalkoken Date: Sun, 9 May 2021 05:25:45 +0000 Subject: [PATCH 4/9] Fix table styles and nav tabs for dark mode --- .../templates/groupmanagement/index.html | 217 +++++++++--------- allianceauth/static/css/auth-base.css | 64 ++++-- 2 files changed, 153 insertions(+), 128 deletions(-) diff --git a/allianceauth/groupmanagement/templates/groupmanagement/index.html b/allianceauth/groupmanagement/templates/groupmanagement/index.html index 060a5670..d4fc0048 100644 --- a/allianceauth/groupmanagement/templates/groupmanagement/index.html +++ b/allianceauth/groupmanagement/templates/groupmanagement/index.html @@ -40,120 +40,121 @@ -
-
-
- {% if acceptrequests %} -
- - - - - - - - - - - - {% for acceptrequest in acceptrequests %} +
+
+
+ +
+ {% if acceptrequests %} +
+
{% trans "Character" %}{% trans "Organization" %}{% trans "Group" %}
+ - - - - + + + + - {% endfor %} - -
- - {% if acceptrequest.main_char %} - - {{ acceptrequest.main_char.character_name }} - - {% else %} - {{ acceptrequest.user.username }} - {% endif %} - - {% if acceptrequest.main_char %} - - {{ acceptrequest.main_char.corporation_name }} -
- {{ acceptrequest.main_char.alliance_name|default_if_none:"" }} - {% else %} - {% trans "(unknown)" %} - {% endif %} -
{{ acceptrequest.group.name }} - - {% trans "Accept" %} - - - - {% trans "Reject" %} - - {% trans "Character" %}{% trans "Organization" %}{% trans "Group" %}
-
- {% else %} -
{% trans "No group add requests." %}
- {% endif %} -
-
+ -
-
- {% if leaverequests %} -
- - - - - - - - - + + {% for acceptrequest in acceptrequests %} + + + + + - {% for leaverequest in leaverequests %} + + {% trans "Reject" %} + + + + {% endfor %} + +
{% trans "Character" %}{% trans "Organization" %}{% trans "Group" %}
+ + {% if acceptrequest.main_char %} + + {{ acceptrequest.main_char.character_name }} + + {% else %} + {{ acceptrequest.user.username }} + {% endif %} + + {% if acceptrequest.main_char %} + + {{ acceptrequest.main_char.corporation_name }} +
+ {{ acceptrequest.main_char.alliance_name|default_if_none:"" }} + {% else %} + {% trans "(unknown)" %} + {% endif %} +
{{ acceptrequest.group.name }} + + {% trans "Accept" %} + -
+
+ {% else %} +
{% trans "No group add requests." %}
+ {% endif %} +
+ +
+ {% if leaverequests %} +
+ + - - - - + + + + - {% endfor %} - -
- - {% if leaverequest.main_char %} - - {{ leaverequest.main_char.character_name }} - - {% else %} - {{ leaverequest.user.username }} - {% endif %} - - {% if leaverequest.main_char %} - - {{ leaverequest.main_char.corporation_name }} -
- {{ leaverequest.main_char.alliance_name|default_if_none:"" }} - {% else %} - {% trans "(unknown)" %} - {% endif %} -
{{ leaverequest.group.name }} - - {% trans "Accept" %} - - - - {% trans "Reject" %} - - {% trans "Character" %}{% trans "Organization" %}{% trans "Group" %}
-
- {% else %} -
{% trans "No group leave requests." %}
- {% endif %} + + + + {% for leaverequest in leaverequests %} + + + + {% if leaverequest.main_char %} + + {{ leaverequest.main_char.character_name }} + + {% else %} + {{ leaverequest.user.username }} + {% endif %} + + + {% if leaverequest.main_char %} + + {{ leaverequest.main_char.corporation_name }} +
+ {{ leaverequest.main_char.alliance_name|default_if_none:"" }} + {% else %} + {% trans "(unknown)" %} + {% endif %} + + {{ leaverequest.group.name }} + + + {% trans "Accept" %} + + + + {% trans "Reject" %} + + + + {% endfor %} + + +
+ {% else %} +
{% trans "No group leave requests." %}
+ {% endif %} +
diff --git a/allianceauth/static/css/auth-base.css b/allianceauth/static/css/auth-base.css index 32f03efd..32afaba0 100644 --- a/allianceauth/static/css/auth-base.css +++ b/allianceauth/static/css/auth-base.css @@ -43,27 +43,51 @@ ul.list-group.list-group-horizontal > li.list-group-item { justify-content: center; } -/* style group headers within a table */ -.tr-group { - font-weight: bold; - background-color: #e6e6e6 !important; -} +@media all { + /* style nav tabs in dark mode*/ + .template-dark-mode .nav-tabs > li.active > a { + background-color: rgb(70, 69, 69)!important; + color: rgb(255, 255, 255) !important; + } -/* default style for tables */ -.table-aa > thead > tr > th{ - border-bottom: 1px solid #f2f2f2; -} -.table-aa > thead > tr > th{ - vertical-align: middle; -} -.table-aa > tbody > tr > td{ - border-bottom: 1px solid #f2f2f2; -} -.table-aa > tbody > tr > td { - vertical-align: middle; -} -.table-aa > tbody > tr:last-child { - border-bottom: none; + .panel-tabs-aa { + border-top: none; + border-top-left-radius: 0%; + border-top-right-radius: 0%; + } + + /* style group headers within a table */ + .template-light-mode .tr-group { + font-weight: bold; + background-color: #e6e6e6 !important; + } + .template-dark-mode .tr-group { + font-weight: bold; + background-color: rgb(105, 105, 105) !important; + } + + /* default style for tables */ + .template-light-mode .table-aa > thead > tr > th{ + border-bottom: 1px solid #f2f2f2; + } + .template-dark-mode .table-aa > thead > tr > th{ + border-bottom: 1px solid rgb(70, 69, 69); + } + .table-aa > thead > tr > th{ + vertical-align: middle; + } + .template-light-mode .table-aa > tbody > tr > td{ + border-bottom: 1px solid #f2f2f2; + } + .template-dark-mode .table-aa > tbody > tr > td{ + border-bottom: 1px solid rgb(70, 69, 69); + } + .table-aa > tbody > tr > td { + vertical-align: middle; + } + .table-aa > tbody > tr:last-child { + border-bottom: none; + } } /* highlight active menu items From e1843fe1f1631f6ea1c6c3180e81617e88027c1b Mon Sep 17 00:00:00 2001 From: Erik Kalkoken Date: Sun, 9 May 2021 05:31:45 +0000 Subject: [PATCH 5/9] Improve authentication admin --- allianceauth/authentication/admin.py | 115 ++++++---------- allianceauth/authentication/tests/__init__.py | 3 +- .../authentication/tests/test_admin.py | 127 +++--------------- 3 files changed, 61 insertions(+), 184 deletions(-) diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 69658f8f..5191981c 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -1,10 +1,8 @@ -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, Group -from django.db.models import Q, F +from django.db.models import Count, Q from allianceauth.services.hooks import ServicesHook from django.db.models.signals import pre_save, post_save, pre_delete, \ post_delete, m2m_changed @@ -24,11 +22,6 @@ from allianceauth.eveonline.tasks import update_character 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 -else: - _has_auto_groups = False - def make_service_hooks_update_groups_action(service): """ @@ -91,8 +84,7 @@ class UserProfileInline(admin.StackedInline): if request.user.is_superuser: query |= Q(userprofile__isnull=True) else: - query |= Q(character_ownership__user=obj) - qs = EveCharacter.objects.filter(query) + query |= Q(character_ownership__user=obj) formset = super().get_formset(request, obj=obj, **kwargs) def get_kwargs(self, index): @@ -121,6 +113,8 @@ def user_profile_pic(obj): ) else: return None + + user_profile_pic.short_description = '' @@ -152,6 +146,7 @@ def user_username(obj): user_obj.username, ) + user_username.short_description = 'user / main' user_username.admin_order_field = 'username' @@ -168,7 +163,8 @@ def user_main_organization(obj): else: corporation = user_obj.profile.main_character.corporation_name if user_obj.profile.main_character.alliance_id: - result = format_html('{}
{}', + result = format_html( + '{}
{}', corporation, user_obj.profile.main_character.alliance_name ) @@ -176,6 +172,7 @@ def user_main_organization(obj): result = corporation return result + user_main_organization.short_description = 'Corporation / Alliance (Main)' user_main_organization.admin_order_field = \ 'profile__main_character__corporation_name' @@ -205,13 +202,13 @@ class MainCorporationsFilter(admin.SimpleListFilter): return qs.all() else: if qs.model == User: - return qs\ - .filter(profile__main_character__corporation_id=\ - self.value()) + return qs.filter( + profile__main_character__corporation_id=self.value() + ) else: - return qs\ - .filter(user__profile__main_character__corporation_id=\ - self.value()) + return qs.filter( + user__profile__main_character__corporation_id=self.value() + ) class MainAllianceFilter(admin.SimpleListFilter): @@ -239,12 +236,11 @@ class MainAllianceFilter(admin.SimpleListFilter): return qs.all() else: if qs.model == User: - return qs\ - .filter(profile__main_character__alliance_id=self.value()) + return qs.filter(profile__main_character__alliance_id=self.value()) else: - return qs\ - .filter(user__profile__main_character__alliance_id=\ - self.value()) + return qs.filter( + user__profile__main_character__alliance_id=self.value() + ) def update_main_character_model(modeladmin, request, queryset): @@ -259,6 +255,7 @@ def update_main_character_model(modeladmin, request, queryset): 'Update from ESI started for {} characters'.format(tasks_count) ) + update_main_character_model.short_description = \ 'Update main character model from ESI' @@ -267,32 +264,16 @@ 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 RealGroupsFilter(admin.SimpleListFilter): - """Custom filter to get groups w/o Autogroups""" - title = 'group' - parameter_name = 'group_id__exact' - - def lookups(self, request, model_admin): - qs = Group.objects.all().order_by(Lower('name')) - if _has_auto_groups: - qs = qs\ - .filter(managedalliancegroup__isnull=True)\ - .filter(managedcorpgroup__isnull=True) - 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 get_queryset(self, request): + qs = super().get_queryset(request) + return qs.prefetch_related("character_ownerships__character", "groups") def get_actions(self, request): actions = super(BaseUserAdmin, self).get_actions(request) @@ -341,11 +322,9 @@ class UserAdmin(BaseUserAdmin): return result inlines = BaseUserAdmin.inlines + [UserProfileInline] - - ordering = ('username', ) - list_select_related = True - show_full_result_count = True - + ordering = ('username', ) + list_select_related = ('profile__state', 'profile__main_character') + show_full_result_count = True list_display = ( user_profile_pic, user_username, @@ -358,10 +337,9 @@ class UserAdmin(BaseUserAdmin): '_role' ) list_display_links = None - list_filter = ( 'profile__state', - RealGroupsFilter, + 'groups', MainCorporationsFilter, MainAllianceFilter, 'is_active', @@ -375,41 +353,25 @@ class UserAdmin(BaseUserAdmin): ) def _characters(self, obj): - my_characters = [ - x.character.character_name - for x in CharacterOwnership.objects\ - .filter(user=obj)\ - .order_by('character__character_name')\ - .select_related() - ] + character_ownerships = list(obj.character_ownerships.all()) + characters = [obj.character.character_name for obj in character_ownerships] return self._list_2_html_w_tooltips( - my_characters, + sorted(characters), AUTHENTICATION_ADMIN_USERS_MAX_CHARS ) _characters.short_description = 'characters' - def _state(self, obj): 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')] - else: - my_groups = [ - x.name for x in obj.groups\ - .filter(managedalliancegroup__isnull=True)\ - .filter(managedcorpgroup__isnull=True)\ - .order_by('name') - ] - + def _groups(self, obj): + my_groups = sorted([group.name for group in list(obj.groups.all())]) return self._list_2_html_w_tooltips( - my_groups, - AUTHENTICATION_ADMIN_USERS_MAX_GROUPS + my_groups, AUTHENTICATION_ADMIN_USERS_MAX_GROUPS ) _groups.short_description = 'groups' @@ -446,9 +408,14 @@ class StateAdmin(admin.ModelAdmin): list_select_related = True list_display = ('name', 'priority', '_user_count') + def get_queryset(self, request): + qs = super().get_queryset(request) + return qs.annotate(user_count=Count("userprofile__id")) + def _user_count(self, obj): - return obj.userprofile_set.all().count() + return obj.user_count _user_count.short_description = 'Users' + _user_count.admin_order_field = 'user_count' fieldsets = ( (None, { @@ -504,7 +471,8 @@ class BaseOwnershipAdmin(admin.ModelAdmin): "all": ("authentication/css/admin.css",) } - list_select_related = True + list_select_related = ( + 'user__profile__state', 'user__profile__main_character', 'character') list_display = ( user_profile_pic, user_username, @@ -542,6 +510,7 @@ class CharacterOwnershipAdmin(BaseOwnershipAdmin): class PermissionAdmin(admin.ModelAdmin): actions = None readonly_fields = [field.name for field in BasePermission._meta.fields] + search_fields = ('codename', ) list_display = ('admin_name', 'name', 'codename', 'content_type') list_filter = ('content_type__app_label',) diff --git a/allianceauth/authentication/tests/__init__.py b/allianceauth/authentication/tests/__init__.py index ba343c6d..7c31c2fe 100644 --- a/allianceauth/authentication/tests/__init__.py +++ b/allianceauth/authentication/tests/__init__.py @@ -10,9 +10,10 @@ def get_admin_change_view_url(obj: object) -> str: args=(obj.pk,) ) + def get_admin_search_url(ModelClass: type) -> str: """returns URL to search URL for model of given object""" return '{}{}/'.format( reverse('admin:app_list', args=(ModelClass._meta.app_label,)), ModelClass.__name__.lower() - ) \ No newline at end of file + ) diff --git a/allianceauth/authentication/tests/test_admin.py b/allianceauth/authentication/tests/test_admin.py index 10347afa..b43da12b 100644 --- a/allianceauth/authentication/tests/test_admin.py +++ b/allianceauth/authentication/tests/test_admin.py @@ -1,10 +1,8 @@ from urllib.parse import quote from unittest.mock import patch, MagicMock -from django.conf import settings -from django.contrib import admin from django.contrib.admin.sites import AdminSite -from django.contrib.auth.models import User as BaseUser, Group +from django.contrib.auth.models import Group from django.test import TestCase, RequestFactory, Client from allianceauth.authentication.models import ( @@ -18,8 +16,7 @@ from allianceauth.tests.auth_utils import AuthUtils from ..admin import ( BaseUserAdmin, - CharacterOwnershipAdmin, - PermissionAdmin, + CharacterOwnershipAdmin, StateAdmin, MainCorporationsFilter, MainAllianceFilter, @@ -35,11 +32,6 @@ from ..admin import ( ) from . import get_admin_change_view_url, get_admin_search_url -if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: - _has_auto_groups = True - from allianceauth.eveonline.autogroups.models import AutogroupsConfig -else: - _has_auto_groups = False MODULE_PATH = 'allianceauth.authentication.admin' @@ -48,6 +40,7 @@ class MockRequest(object): def __init__(self, user=None): self.user = user + class TestCaseWithTestData(TestCase): @classmethod @@ -279,6 +272,7 @@ class TestStateAdmin(TestCaseWithTestData): expected = 200 self.assertEqual(response.status_code, expected) + class TestUserAdmin(TestCaseWithTestData): def setUp(self): @@ -287,24 +281,12 @@ class TestUserAdmin(TestCaseWithTestData): model=User, admin_site=AdminSite() ) self.character_1 = self.user_1.character_ownerships.first().character - - def _create_autogroups(self): - """create autogroups for corps and alliances""" - if _has_auto_groups: - 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_u1(self): - expected = ('') + expected = ( + '' + ) self.assertEqual(user_profile_pic(self.user_1), expected) def test_user_profile_pic_u3(self): @@ -351,37 +333,17 @@ class TestUserAdmin(TestCaseWithTestData): result = self.modeladmin._characters(self.user_3) self.assertEqual(result, expected) - def test_groups_u1(self): - self._create_autogroups() + def test_groups_u1(self): expected = 'Group 1' result = self.modeladmin._groups(self.user_1) self.assertEqual(result, expected) - def test_groups_u2(self): - self._create_autogroups() + def test_groups_u2(self): expected = 'Group 2' result = self.modeladmin._groups(self.user_2) self.assertEqual(result, expected) - 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): + def test_groups_u3(self): result = self.modeladmin._groups(self.user_3) self.assertIsNone(result) @@ -413,8 +375,10 @@ class TestUserAdmin(TestCaseWithTestData): def test_list_2_html_w_tooltips_w_cutoff(self): items = ['one', 'two', 'three'] - expected = ('one, two, (...)') + expected = ( + 'one, two, (...)' + ) result = self.modeladmin._list_2_html_w_tooltips(items, 2) self.assertEqual(expected, result) @@ -439,63 +403,7 @@ class TestUserAdmin(TestCaseWithTestData): self.assertTrue(mock_message_user.called) # filters - - 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 - 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): @@ -603,7 +511,6 @@ class TestMakeServicesHooksActions(TestCaseWithTestData): def sync_nicknames_bulk(self, user): pass - def test_service_has_update_groups_only(self): service = self.MyServicesHookTypeA() mock_service = MagicMock(spec=service) From c86abef07dcb8bc80d5523eed40064679da1dc5c Mon Sep 17 00:00:00 2001 From: Erik Kalkoken Date: Sun, 9 May 2021 05:38:28 +0000 Subject: [PATCH 6/9] Improve group management admin pages --- allianceauth/groupmanagement/admin.py | 34 ++++++++++++++++++++------- allianceauth/groupmanagement/apps.py | 3 +++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/allianceauth/groupmanagement/admin.py b/allianceauth/groupmanagement/admin.py index de04f912..3f5b4a39 100644 --- a/allianceauth/groupmanagement/admin.py +++ b/allianceauth/groupmanagement/admin.py @@ -1,5 +1,4 @@ -from django.conf import settings - +from django.apps import apps from django.contrib import admin from django.contrib.auth.models import Group as BaseGroup, User from django.db.models import Count @@ -10,9 +9,8 @@ from django.dispatch import receiver from .models import AuthGroup from .models import GroupRequest -from . import signals -if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: +if 'eve_autogroups' in apps.app_configs: _has_auto_groups = True else: _has_auto_groups = False @@ -97,9 +95,10 @@ class HasLeaderFilter(admin.SimpleListFilter): else: return queryset + class GroupAdmin(admin.ModelAdmin): - list_select_related = True - ordering = ('name', ) + list_select_related = ('authgroup',) + ordering = ('name',) list_display = ( 'name', '_description', @@ -118,9 +117,12 @@ class GroupAdmin(admin.ModelAdmin): list_filter.append(HasLeaderFilter) search_fields = ('name', 'authgroup__description') - + def get_queryset(self, request): qs = super().get_queryset(request) + if _has_auto_groups: + qs = qs.prefetch_related('managedalliancegroup_set', 'managedcorpgroup_set') + qs = qs.prefetch_related('authgroup__group_leaders') qs = qs.annotate( member_count=Count('user', distinct=True), ) @@ -173,13 +175,29 @@ class Group(BaseGroup): verbose_name = BaseGroup._meta.verbose_name verbose_name_plural = BaseGroup._meta.verbose_name_plural + try: admin.site.unregister(BaseGroup) finally: admin.site.register(Group, GroupAdmin) -admin.site.register(GroupRequest) +@admin.register(GroupRequest) +class GroupRequestAdmin(admin.ModelAdmin): + search_fields = ('user__username', ) + list_display = ('id', 'group', 'user', '_leave_request', 'status') + list_filter = ( + ('group', admin.RelatedOnlyFieldListFilter), + ('user', admin.RelatedOnlyFieldListFilter), + 'leave_request', + 'status' + ) + + def _leave_request(self, obj) -> True: + return obj.leave_request + + _leave_request.short_description = 'is leave request' + _leave_request.boolean = True @receiver(pre_save, sender=Group) diff --git a/allianceauth/groupmanagement/apps.py b/allianceauth/groupmanagement/apps.py index 16bafac2..40eec3b2 100644 --- a/allianceauth/groupmanagement/apps.py +++ b/allianceauth/groupmanagement/apps.py @@ -5,3 +5,6 @@ class GroupManagementConfig(AppConfig): name = 'allianceauth.groupmanagement' label = 'groupmanagement' verbose_name = 'Group Management' + + def ready(self): + from . import signals # noqa: F401 From 93af920b8f4d7170ffb97576e334e111d639adfb Mon Sep 17 00:00:00 2001 From: Erik Kalkoken Date: Sun, 9 May 2021 05:39:46 +0000 Subject: [PATCH 7/9] Improve admin pages for eveonline models --- allianceauth/eveonline/admin.py | 46 ++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/allianceauth/eveonline/admin.py b/allianceauth/eveonline/admin.py index 12184a17..9c0447a1 100644 --- a/allianceauth/eveonline/admin.py +++ b/allianceauth/eveonline/admin.py @@ -96,24 +96,62 @@ class EveAllianceForm(EveEntityForm): @admin.register(EveCorporationInfo) class EveCorporationInfoAdmin(admin.ModelAdmin): + search_fields = ['corporation_name'] + list_display = ('corporation_name', 'alliance') + list_select_related = ('alliance',) + list_filter = (('alliance', admin.RelatedOnlyFieldListFilter),) + ordering = ('corporation_name',) + + def has_change_permission(self, request, obj=None): + return False + def get_form(self, request, obj=None, **kwargs): if not obj or not obj.pk: return EveCorporationForm - return super(EveCorporationInfoAdmin, self).get_form(request, obj=obj, **kwargs) + return super().get_form(request, obj=obj, **kwargs) @admin.register(EveAllianceInfo) class EveAllianceInfoAdmin(admin.ModelAdmin): + search_fields = ['alliance_name'] + list_display = ('alliance_name',) + ordering = ('alliance_name',) + + def has_change_permission(self, request, obj=None): + return False + def get_form(self, request, obj=None, **kwargs): if not obj or not obj.pk: return EveAllianceForm - return super(EveAllianceInfoAdmin, self).get_form(request, obj=obj, **kwargs) + return super().get_form(request, obj=obj, **kwargs) @admin.register(EveCharacter) class EveCharacterAdmin(admin.ModelAdmin): - search_fields = ['character_name', 'corporation_name', 'alliance_name', 'character_ownership__user__username'] - list_display = ('character_name', 'corporation_name', 'alliance_name', 'user', 'main_character') + search_fields = [ + 'character_name', + 'corporation_name', + 'alliance_name', + 'character_ownership__user__username' + ] + list_display = ( + 'character_name', 'corporation_name', 'alliance_name', 'user', 'main_character' + ) + list_select_related = ( + 'character_ownership', 'character_ownership__user__profile__main_character' + ) + list_filter = ( + 'corporation_name', + 'alliance_name', + ( + 'character_ownership__user__profile__main_character', + admin.RelatedOnlyFieldListFilter + ), + ) + ordering = ('character_name', ) + + def has_change_permission(self, request, obj=None): + return False @staticmethod def user(obj): From 85351b2c661c18d8f188db14ca716ed5f68ed684 Mon Sep 17 00:00:00 2001 From: Ariel Rin Date: Sun, 9 May 2021 05:41:05 +0000 Subject: [PATCH 8/9] Model updates for Mumble Authenticator 1.1 --- .../migrations/0012_mumble_client_info.py | 33 +++++++++++++++++ .../services/modules/mumble/models.py | 36 ++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 allianceauth/services/modules/mumble/migrations/0012_mumble_client_info.py diff --git a/allianceauth/services/modules/mumble/migrations/0012_mumble_client_info.py b/allianceauth/services/modules/mumble/migrations/0012_mumble_client_info.py new file mode 100644 index 00000000..20295767 --- /dev/null +++ b/allianceauth/services/modules/mumble/migrations/0012_mumble_client_info.py @@ -0,0 +1,33 @@ +# Generated by Django 3.1.6 on 2021-03-23 13:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mumble', '0001_squashed_0011_auto_20201011_1009'), + ] + + operations = [ + migrations.AddField( + model_name='mumbleuser', + name='last_connect', + field=models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Connection to Mumble', max_length=254, null=True, verbose_name='Last Connection'), + ), + migrations.AddField( + model_name='mumbleuser', + name='last_disconnect', + field=models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Disconnection to Mumble', max_length=254, null=True, verbose_name='Last Disconnection'), + ), + migrations.AddField( + model_name='mumbleuser', + name='release', + field=models.TextField(blank=True, editable=False, help_text='The Mumble Release the user last authenticated with', max_length=254, null=True, verbose_name='Mumble Release'), + ), + migrations.AddField( + model_name='mumbleuser', + name='version', + field=models.IntegerField(blank=True, editable=False, help_text='Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203.', null=True, verbose_name='Mumble Version'), + ), + ] diff --git a/allianceauth/services/modules/mumble/models.py b/allianceauth/services/modules/mumble/models.py index 773c644f..c429f8da 100644 --- a/allianceauth/services/modules/mumble/models.py +++ b/allianceauth/services/modules/mumble/models.py @@ -74,7 +74,41 @@ class MumbleUser(AbstractServiceModel): editable=False, help_text="Hash of Mumble client certificate as presented when user authenticates" ) - display_name = models.CharField(max_length=254, unique=True) + display_name = models.CharField( + max_length=254, + unique=True + ) + release = models.TextField( + verbose_name="Mumble Release", + max_length=254, + blank=True, + null=True, + editable=False, + help_text="The Mumble Release the user last authenticated with" + ) + version = models.IntegerField( + verbose_name="Mumble Version", + blank=True, + null=True, + editable=False, + help_text="Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203." + ) + last_connect = models.DateTimeField( + verbose_name="Last Connection", + max_length=254, + blank=True, + null=True, + editable=False, + help_text="Timestamp of the users Last Connection to Mumble" + ) + last_disconnect = models.DateTimeField( + verbose_name="Last Disconnection", + max_length=254, + blank=True, + null=True, + editable=False, + help_text="Timestamp of the users Last Disconnection to Mumble" + ) objects = MumbleManager() From 5a2c9243c4f23b1a26e2af5648c148985ef26c50 Mon Sep 17 00:00:00 2001 From: Ariel Rin Date: Sun, 9 May 2021 05:52:20 +0000 Subject: [PATCH 9/9] Version Bump v2.8.4 --- allianceauth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allianceauth/__init__.py b/allianceauth/__init__.py index 7bf8530f..9b09e9b6 100644 --- a/allianceauth/__init__.py +++ b/allianceauth/__init__.py @@ -1,7 +1,7 @@ # This will make sure the app is always imported when # Django starts so that shared_task will use this app. -__version__ = '2.8.3' +__version__ = '2.8.4' __title__ = 'Alliance Auth' __url__ = 'https://gitlab.com/allianceauth/allianceauth' NAME = '%s v%s' % (__title__, __version__)