This commit is contained in:
Ariel Rin 2021-05-13 21:38:22 +10:00
commit 192d286cf2
14 changed files with 417 additions and 390 deletions

View File

@ -7,15 +7,21 @@ include:
- template: Dependency-Scanning.gitlab-ci.yml - template: Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.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: sast:
stage: gitlab stage: gitlab
before_script: []
dependency_scanning: dependency_scanning:
stage: gitlab stage: gitlab
before_script: before_script:
- apt-get update && apt-get install redis-server libmariadbclient-dev -y - apt-get update && apt-get install redis-server libmariadbclient-dev -y
- redis-server --daemonize yes - redis-server --daemonize yes
- redis-cli ping
- python -V - python -V
- pip install wheel tox - pip install wheel tox
@ -23,12 +29,6 @@ test-3.7-core:
image: python:3.7-buster image: python:3.7-buster
script: script:
- tox -e py37-core - 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: artifacts:
when: always when: always
reports: reports:
@ -38,12 +38,6 @@ test-3.8-core:
image: python:3.8-buster image: python:3.8-buster
script: script:
- tox -e py38-core - 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: artifacts:
when: always when: always
reports: reports:
@ -53,12 +47,6 @@ test-3.9-core:
image: python:3.9-buster image: python:3.9-buster
script: script:
- tox -e py39-core - tox -e py39-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: artifacts:
when: always when: always
reports: reports:
@ -68,12 +56,6 @@ test-3.7-all:
image: python:3.7-buster image: python:3.7-buster
script: script:
- tox -e py37-all - 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: artifacts:
when: always when: always
reports: reports:
@ -83,12 +65,6 @@ test-3.8-all:
image: python:3.8-buster image: python:3.8-buster
script: script:
- tox -e py38-all - 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: artifacts:
when: always when: always
reports: reports:
@ -98,12 +74,6 @@ test-3.9-all:
image: python:3.9-buster image: python:3.9-buster
script: script:
- tox -e py39-all - tox -e py39-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: artifacts:
when: always when: always
reports: reports:

View File

@ -1,10 +1,8 @@
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User as BaseUser, \ from django.contrib.auth.models import User as BaseUser, \
Permission as BasePermission, Group 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 allianceauth.services.hooks import ServicesHook
from django.db.models.signals import pre_save, post_save, pre_delete, \ from django.db.models.signals import pre_save, post_save, pre_delete, \
post_delete, m2m_changed post_delete, m2m_changed
@ -24,11 +22,6 @@ from allianceauth.eveonline.tasks import update_character
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \ from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS 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): def make_service_hooks_update_groups_action(service):
""" """
@ -92,7 +85,6 @@ class UserProfileInline(admin.StackedInline):
query |= Q(userprofile__isnull=True) query |= Q(userprofile__isnull=True)
else: else:
query |= Q(character_ownership__user=obj) query |= Q(character_ownership__user=obj)
qs = EveCharacter.objects.filter(query)
formset = super().get_formset(request, obj=obj, **kwargs) formset = super().get_formset(request, obj=obj, **kwargs)
def get_kwargs(self, index): def get_kwargs(self, index):
@ -121,6 +113,8 @@ def user_profile_pic(obj):
) )
else: else:
return None return None
user_profile_pic.short_description = '' user_profile_pic.short_description = ''
@ -152,6 +146,7 @@ def user_username(obj):
user_obj.username, user_obj.username,
) )
user_username.short_description = 'user / main' user_username.short_description = 'user / main'
user_username.admin_order_field = 'username' user_username.admin_order_field = 'username'
@ -168,7 +163,8 @@ def user_main_organization(obj):
else: else:
corporation = user_obj.profile.main_character.corporation_name corporation = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id: if user_obj.profile.main_character.alliance_id:
result = format_html('{}<br>{}', result = format_html(
'{}<br>{}',
corporation, corporation,
user_obj.profile.main_character.alliance_name user_obj.profile.main_character.alliance_name
) )
@ -176,6 +172,7 @@ def user_main_organization(obj):
result = corporation result = corporation
return result return result
user_main_organization.short_description = 'Corporation / Alliance (Main)' user_main_organization.short_description = 'Corporation / Alliance (Main)'
user_main_organization.admin_order_field = \ user_main_organization.admin_order_field = \
'profile__main_character__corporation_name' 'profile__main_character__corporation_name'
@ -205,13 +202,13 @@ class MainCorporationsFilter(admin.SimpleListFilter):
return qs.all() return qs.all()
else: else:
if qs.model == User: if qs.model == User:
return qs\ return qs.filter(
.filter(profile__main_character__corporation_id=\ profile__main_character__corporation_id=self.value()
self.value()) )
else: else:
return qs\ return qs.filter(
.filter(user__profile__main_character__corporation_id=\ user__profile__main_character__corporation_id=self.value()
self.value()) )
class MainAllianceFilter(admin.SimpleListFilter): class MainAllianceFilter(admin.SimpleListFilter):
@ -239,12 +236,11 @@ class MainAllianceFilter(admin.SimpleListFilter):
return qs.all() return qs.all()
else: else:
if qs.model == User: if qs.model == User:
return qs\ return qs.filter(profile__main_character__alliance_id=self.value())
.filter(profile__main_character__alliance_id=self.value())
else: else:
return qs\ return qs.filter(
.filter(user__profile__main_character__alliance_id=\ user__profile__main_character__alliance_id=self.value()
self.value()) )
def update_main_character_model(modeladmin, request, queryset): 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 from ESI started for {} characters'.format(tasks_count)
) )
update_main_character_model.short_description = \ update_main_character_model.short_description = \
'Update main character model from ESI' 'Update main character model from ESI'
@ -267,7 +264,6 @@ class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model """Extending Django's UserAdmin model
Behavior of groups and characters columns can be configured via settings Behavior of groups and characters columns can be configured via settings
""" """
class Media: class Media:
@ -275,24 +271,9 @@ class UserAdmin(BaseUserAdmin):
"all": ("authentication/css/admin.css",) "all": ("authentication/css/admin.css",)
} }
class RealGroupsFilter(admin.SimpleListFilter): def get_queryset(self, request):
"""Custom filter to get groups w/o Autogroups""" qs = super().get_queryset(request)
title = 'group' return qs.prefetch_related("character_ownerships__character", "groups")
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_actions(self, request): def get_actions(self, request):
actions = super(BaseUserAdmin, self).get_actions(request) actions = super(BaseUserAdmin, self).get_actions(request)
@ -341,11 +322,9 @@ class UserAdmin(BaseUserAdmin):
return result return result
inlines = BaseUserAdmin.inlines + [UserProfileInline] inlines = BaseUserAdmin.inlines + [UserProfileInline]
ordering = ('username', ) ordering = ('username', )
list_select_related = True list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True show_full_result_count = True
list_display = ( list_display = (
user_profile_pic, user_profile_pic,
user_username, user_username,
@ -358,10 +337,9 @@ class UserAdmin(BaseUserAdmin):
'_role' '_role'
) )
list_display_links = None list_display_links = None
list_filter = ( list_filter = (
'profile__state', 'profile__state',
RealGroupsFilter, 'groups',
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
'is_active', 'is_active',
@ -375,21 +353,15 @@ class UserAdmin(BaseUserAdmin):
) )
def _characters(self, obj): def _characters(self, obj):
my_characters = [ character_ownerships = list(obj.character_ownerships.all())
x.character.character_name characters = [obj.character.character_name for obj in character_ownerships]
for x in CharacterOwnership.objects\
.filter(user=obj)\
.order_by('character__character_name')\
.select_related()
]
return self._list_2_html_w_tooltips( return self._list_2_html_w_tooltips(
my_characters, sorted(characters),
AUTHENTICATION_ADMIN_USERS_MAX_CHARS AUTHENTICATION_ADMIN_USERS_MAX_CHARS
) )
_characters.short_description = 'characters' _characters.short_description = 'characters'
def _state(self, obj): def _state(self, obj):
return obj.profile.state.name return obj.profile.state.name
@ -397,19 +369,9 @@ class UserAdmin(BaseUserAdmin):
_state.admin_order_field = 'profile__state' _state.admin_order_field = 'profile__state'
def _groups(self, obj): def _groups(self, obj):
if not _has_auto_groups: my_groups = sorted([group.name for group in list(obj.groups.all())])
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')
]
return self._list_2_html_w_tooltips( return self._list_2_html_w_tooltips(
my_groups, my_groups, AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
) )
_groups.short_description = 'groups' _groups.short_description = 'groups'
@ -446,9 +408,14 @@ class StateAdmin(admin.ModelAdmin):
list_select_related = True list_select_related = True
list_display = ('name', 'priority', '_user_count') 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): def _user_count(self, obj):
return obj.userprofile_set.all().count() return obj.user_count
_user_count.short_description = 'Users' _user_count.short_description = 'Users'
_user_count.admin_order_field = 'user_count'
fieldsets = ( fieldsets = (
(None, { (None, {
@ -504,7 +471,8 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
"all": ("authentication/css/admin.css",) "all": ("authentication/css/admin.css",)
} }
list_select_related = True list_select_related = (
'user__profile__state', 'user__profile__main_character', 'character')
list_display = ( list_display = (
user_profile_pic, user_profile_pic,
user_username, user_username,
@ -542,6 +510,7 @@ class CharacterOwnershipAdmin(BaseOwnershipAdmin):
class PermissionAdmin(admin.ModelAdmin): class PermissionAdmin(admin.ModelAdmin):
actions = None actions = None
readonly_fields = [field.name for field in BasePermission._meta.fields] readonly_fields = [field.name for field in BasePermission._meta.fields]
search_fields = ('codename', )
list_display = ('admin_name', 'name', 'codename', 'content_type') list_display = ('admin_name', 'name', 'codename', 'content_type')
list_filter = ('content_type__app_label',) list_filter = ('content_type__app_label',)

View File

@ -10,6 +10,7 @@ def get_admin_change_view_url(obj: object) -> str:
args=(obj.pk,) args=(obj.pk,)
) )
def get_admin_search_url(ModelClass: type) -> str: def get_admin_search_url(ModelClass: type) -> str:
"""returns URL to search URL for model of given object""" """returns URL to search URL for model of given object"""
return '{}{}/'.format( return '{}{}/'.format(

View File

@ -1,10 +1,8 @@
from urllib.parse import quote from urllib.parse import quote
from unittest.mock import patch, MagicMock 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.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 django.test import TestCase, RequestFactory, Client
from allianceauth.authentication.models import ( from allianceauth.authentication.models import (
@ -19,7 +17,6 @@ from allianceauth.tests.auth_utils import AuthUtils
from ..admin import ( from ..admin import (
BaseUserAdmin, BaseUserAdmin,
CharacterOwnershipAdmin, CharacterOwnershipAdmin,
PermissionAdmin,
StateAdmin, StateAdmin,
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
@ -35,11 +32,6 @@ from ..admin import (
) )
from . import get_admin_change_view_url, get_admin_search_url 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' MODULE_PATH = 'allianceauth.authentication.admin'
@ -48,6 +40,7 @@ class MockRequest(object):
def __init__(self, user=None): def __init__(self, user=None):
self.user = user self.user = user
class TestCaseWithTestData(TestCase): class TestCaseWithTestData(TestCase):
@classmethod @classmethod
@ -279,6 +272,7 @@ class TestStateAdmin(TestCaseWithTestData):
expected = 200 expected = 200
self.assertEqual(response.status_code, expected) self.assertEqual(response.status_code, expected)
class TestUserAdmin(TestCaseWithTestData): class TestUserAdmin(TestCaseWithTestData):
def setUp(self): def setUp(self):
@ -288,23 +282,11 @@ class TestUserAdmin(TestCaseWithTestData):
) )
self.character_1 = self.user_1.character_ownerships.first().character 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): def test_user_profile_pic_u1(self):
expected = ('<img src="https://images.evetech.net/characters/1001/' expected = (
'portrait?size=32" class="img-circle">') '<img src="https://images.evetech.net/characters/1001/'
'portrait?size=32" class="img-circle">'
)
self.assertEqual(user_profile_pic(self.user_1), expected) self.assertEqual(user_profile_pic(self.user_1), expected)
def test_user_profile_pic_u3(self): def test_user_profile_pic_u3(self):
@ -352,36 +334,16 @@ class TestUserAdmin(TestCaseWithTestData):
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_groups_u1(self): def test_groups_u1(self):
self._create_autogroups()
expected = 'Group 1' expected = 'Group 1'
result = self.modeladmin._groups(self.user_1) result = self.modeladmin._groups(self.user_1)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_groups_u2(self): def test_groups_u2(self):
self._create_autogroups()
expected = 'Group 2' expected = 'Group 2'
result = self.modeladmin._groups(self.user_2) result = self.modeladmin._groups(self.user_2)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_groups_u3(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) result = self.modeladmin._groups(self.user_3)
self.assertIsNone(result) self.assertIsNone(result)
@ -413,8 +375,10 @@ class TestUserAdmin(TestCaseWithTestData):
def test_list_2_html_w_tooltips_w_cutoff(self): def test_list_2_html_w_tooltips_w_cutoff(self):
items = ['one', 'two', 'three'] items = ['one', 'two', 'three']
expected = ('<span data-tooltip="one, two, three" ' expected = (
'class="tooltip">one, two, (...)</span>') '<span data-tooltip="one, two, three" '
'class="tooltip">one, two, (...)</span>'
)
result = self.modeladmin._list_2_html_w_tooltips(items, 2) result = self.modeladmin._list_2_html_w_tooltips(items, 2)
self.assertEqual(expected, result) self.assertEqual(expected, result)
@ -440,62 +404,6 @@ class TestUserAdmin(TestCaseWithTestData):
# filters # 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): def test_filter_main_corporations(self):
class UserAdminTest(BaseUserAdmin): class UserAdminTest(BaseUserAdmin):
@ -603,7 +511,6 @@ class TestMakeServicesHooksActions(TestCaseWithTestData):
def sync_nicknames_bulk(self, user): def sync_nicknames_bulk(self, user):
pass pass
def test_service_has_update_groups_only(self): def test_service_has_update_groups_only(self):
service = self.MyServicesHookTypeA() service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service) mock_service = MagicMock(spec=service)

View File

@ -1,10 +1,10 @@
from math import ceil from math import ceil
from unittest.mock import patch from unittest.mock import patch
from requests import RequestException
import requests_mock import requests_mock
from packaging.version import Version as Pep440Version from packaging.version import Version as Pep440Version
from django.core.cache import cache
from django.test import TestCase from django.test import TestCase
from allianceauth.templatetags.admin_status import ( from allianceauth.templatetags.admin_status import (
@ -13,7 +13,6 @@ from allianceauth.templatetags.admin_status import (
_current_notifications, _current_notifications,
_current_version_summary, _current_version_summary,
_fetch_notification_issues_from_gitlab, _fetch_notification_issues_from_gitlab,
_fetch_tags_from_gitlab,
_latests_versions _latests_versions
) )
@ -103,35 +102,51 @@ class TestStatusOverviewTag(TestCase):
class TestNotifications(TestCase): class TestNotifications(TestCase):
def setUp(self) -> None:
cache.clear()
@requests_mock.mock() @requests_mock.mock()
def test_fetch_notification_issues_from_gitlab(self, requests_mocker): def test_fetch_notification_issues_from_gitlab(self, requests_mocker):
# given
url = ( url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues' 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement' '?labels=announcement'
) )
requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES) requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES)
# when
result = _fetch_notification_issues_from_gitlab() result = _fetch_notification_issues_from_gitlab()
# then
self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES) self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES)
@patch(MODULE_PATH + '.admin_status.cache') @patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_normal(self, mock_cache): def test_current_notifications_normal(self, mock_cache):
# given
mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES
# when
result = _current_notifications() result = _current_notifications()
# then
self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5]) self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5])
@patch(MODULE_PATH + '.admin_status.cache') @requests_mock.mock()
def test_current_notifications_failed(self, mock_cache): def test_current_notifications_failed(self, requests_mocker):
mock_cache.get_or_set.side_effect = RequestException # given
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement'
)
requests_mocker.get(url, status_code=404)
# when
result = _current_notifications() result = _current_notifications()
# then
self.assertEqual(result['notifications'], list()) self.assertEqual(result['notifications'], list())
@patch(MODULE_PATH + '.admin_status.cache') @patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_is_none(self, mock_cache): def test_current_notifications_is_none(self, mock_cache):
# given
mock_cache.get_or_set.return_value = None mock_cache.get_or_set.return_value = None
# when
result = _current_notifications() result = _current_notifications()
# then
self.assertEqual(result['notifications'], list()) self.assertEqual(result['notifications'], list())
@ -143,12 +158,17 @@ class TestCeleryQueueLength(TestCase):
class TestVersionTags(TestCase): class TestVersionTags(TestCase):
def setUp(self) -> None:
cache.clear()
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache') @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 mock_cache.get_or_set.return_value = GITHUB_TAGS
# when
result = _current_version_summary() result = _current_version_summary()
# then
self.assertTrue(result['latest_major']) self.assertTrue(result['latest_major'])
self.assertTrue(result['latest_minor']) self.assertTrue(result['latest_minor'])
self.assertTrue(result['latest_patch']) self.assertTrue(result['latest_patch'])
@ -158,32 +178,41 @@ class TestVersionTags(TestCase):
self.assertEqual(result['latest_beta_version'], '2.4.6a1') self.assertEqual(result['latest_beta_version'], '2.4.6a1')
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache') @requests_mock.mock()
def test_current_version_info_failed(self, mock_cache): def test_current_version_info_failed(self, requests_mocker):
mock_cache.get_or_set.side_effect = RequestException # given
url = (
expected = {} 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags'
)
requests_mocker.get(url, status_code=500)
# when
result = _current_version_summary() result = _current_version_summary()
self.assertEqual(result, expected) # then
self.assertEqual(result, {})
@requests_mock.mock() @requests_mock.mock()
def test_fetch_tags_from_gitlab(self, requests_mocker): def test_fetch_tags_from_gitlab(self, requests_mocker):
# given
url = ( url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth' 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags' '/repository/tags'
) )
requests_mocker.get(url, json=GITHUB_TAGS) requests_mocker.get(url, json=GITHUB_TAGS)
result = _fetch_tags_from_gitlab() # when
self.assertEqual(result, GITHUB_TAGS) result = _current_version_summary()
# then
self.assertTrue(result)
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache') @patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_return_no_data(self, mock_cache): def test_current_version_info_return_no_data(self, mock_cache):
# given
mock_cache.get_or_set.return_value = None mock_cache.get_or_set.return_value = None
# when
expected = {}
result = _current_version_summary() result = _current_version_summary()
self.assertEqual(result, expected) # then
self.assertEqual(result, {})
class TestLatestsVersion(TestCase): class TestLatestsVersion(TestCase):

View File

@ -96,24 +96,62 @@ class EveAllianceForm(EveEntityForm):
@admin.register(EveCorporationInfo) @admin.register(EveCorporationInfo)
class EveCorporationInfoAdmin(admin.ModelAdmin): 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): def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk: if not obj or not obj.pk:
return EveCorporationForm return EveCorporationForm
return super(EveCorporationInfoAdmin, self).get_form(request, obj=obj, **kwargs) return super().get_form(request, obj=obj, **kwargs)
@admin.register(EveAllianceInfo) @admin.register(EveAllianceInfo)
class EveAllianceInfoAdmin(admin.ModelAdmin): 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): def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk: if not obj or not obj.pk:
return EveAllianceForm return EveAllianceForm
return super(EveAllianceInfoAdmin, self).get_form(request, obj=obj, **kwargs) return super().get_form(request, obj=obj, **kwargs)
@admin.register(EveCharacter) @admin.register(EveCharacter)
class EveCharacterAdmin(admin.ModelAdmin): class EveCharacterAdmin(admin.ModelAdmin):
search_fields = ['character_name', 'corporation_name', 'alliance_name', 'character_ownership__user__username'] search_fields = [
list_display = ('character_name', 'corporation_name', 'alliance_name', 'user', 'main_character') '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 @staticmethod
def user(obj): def user(obj):

View File

@ -1,5 +1,4 @@
from django.conf import settings from django.apps import apps
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import Group as BaseGroup, User from django.contrib.auth.models import Group as BaseGroup, User
from django.db.models import Count from django.db.models import Count
@ -10,9 +9,8 @@ from django.dispatch import receiver
from .models import AuthGroup from .models import AuthGroup
from .models import GroupRequest 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 _has_auto_groups = True
else: else:
_has_auto_groups = False _has_auto_groups = False
@ -97,8 +95,9 @@ class HasLeaderFilter(admin.SimpleListFilter):
else: else:
return queryset return queryset
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
list_select_related = True list_select_related = ('authgroup',)
ordering = ('name',) ordering = ('name',)
list_display = ( list_display = (
'name', 'name',
@ -121,6 +120,9 @@ class GroupAdmin(admin.ModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(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( qs = qs.annotate(
member_count=Count('user', distinct=True), member_count=Count('user', distinct=True),
) )
@ -173,13 +175,29 @@ class Group(BaseGroup):
verbose_name = BaseGroup._meta.verbose_name verbose_name = BaseGroup._meta.verbose_name
verbose_name_plural = BaseGroup._meta.verbose_name_plural verbose_name_plural = BaseGroup._meta.verbose_name_plural
try: try:
admin.site.unregister(BaseGroup) admin.site.unregister(BaseGroup)
finally: finally:
admin.site.register(Group, GroupAdmin) 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) @receiver(pre_save, sender=Group)

View File

@ -5,3 +5,6 @@ class GroupManagementConfig(AppConfig):
name = 'allianceauth.groupmanagement' name = 'allianceauth.groupmanagement'
label = 'groupmanagement' label = 'groupmanagement'
verbose_name = 'Group Management' verbose_name = 'Group Management'
def ready(self):
from . import signals # noqa: F401

View File

@ -40,9 +40,11 @@
</li> </li>
</ul> </ul>
<div class="tab-content"> <div class="panel panel-default panel-tabs-aa">
<div id="add" class="tab-pane fade in active panel panel-default">
<div class="panel-body"> <div class="panel-body">
<div class="tab-content">
<div id="add" class="tab-pane active">
{% if acceptrequests %} {% if acceptrequests %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-aa"> <table class="table table-aa">
@ -97,10 +99,8 @@
<div class="alert alert-warning text-center">{% trans "No group add requests." %}</div> <div class="alert alert-warning text-center">{% trans "No group add requests." %}</div>
{% endif %} {% endif %}
</div> </div>
</div>
<div id="leave" class="tab-pane fade panel panel-default"> <div id="leave" class="tab-pane">
<div class="panel-body">
{% if leaverequests %} {% if leaverequests %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-aa"> <table class="table table-aa">
@ -158,4 +158,5 @@
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -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'),
),
]

View File

@ -74,7 +74,41 @@ class MumbleUser(AbstractServiceModel):
editable=False, editable=False,
help_text="Hash of Mumble client certificate as presented when user authenticates" 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() objects = MumbleManager()

View File

@ -43,28 +43,52 @@ ul.list-group.list-group-horizontal > li.list-group-item {
justify-content: center; justify-content: center;
} }
@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;
}
.panel-tabs-aa {
border-top: none;
border-top-left-radius: 0%;
border-top-right-radius: 0%;
}
/* style group headers within a table */ /* style group headers within a table */
.tr-group { .template-light-mode .tr-group {
font-weight: bold; font-weight: bold;
background-color: #e6e6e6 !important; background-color: #e6e6e6 !important;
} }
.template-dark-mode .tr-group {
font-weight: bold;
background-color: rgb(105, 105, 105) !important;
}
/* default style for tables */ /* default style for tables */
.table-aa > thead > tr > th{ .template-light-mode .table-aa > thead > tr > th{
border-bottom: 1px solid #f2f2f2; 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{ .table-aa > thead > tr > th{
vertical-align: middle; vertical-align: middle;
} }
.table-aa > tbody > tr > td{ .template-light-mode .table-aa > tbody > tr > td{
border-bottom: 1px solid #f2f2f2; 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 { .table-aa > tbody > tr > td {
vertical-align: middle; vertical-align: middle;
} }
.table-aa > tbody > tr:last-child { .table-aa > tbody > tr:last-child {
border-bottom: none; border-bottom: none;
} }
}
/* highlight active menu items /* highlight active menu items
--------------------------------------------------------------------------------------------------------------------- */ --------------------------------------------------------------------------------------------------------------------- */

View File

@ -70,8 +70,8 @@ def _current_notifications() -> dict:
_fetch_notification_issues_from_gitlab, _fetch_notification_issues_from_gitlab,
NOTIFICATION_CACHE_TIME NOTIFICATION_CACHE_TIME
) )
except requests.RequestException: except requests.HTTPError:
logger.exception('Error while getting gitlab notifications') logger.warning('Error while getting gitlab notifications', exc_info=True)
top_notifications = [] top_notifications = []
else: else:
if notifications: if notifications:
@ -95,8 +95,8 @@ def _current_version_summary() -> dict:
tags = cache.get_or_set( tags = cache.get_or_set(
'git_release_tags', _fetch_tags_from_gitlab, TAG_CACHE_TIME 'git_release_tags', _fetch_tags_from_gitlab, TAG_CACHE_TIME
) )
except requests.RequestException: except requests.HTTPError:
logger.exception('Error while getting gitlab release tags') logger.warning('Error while getting gitlab release tags', exc_info=True)
return {} return {}
if not tags: if not tags:

View File

@ -26,7 +26,7 @@ DISCORD_SYNC_NAMES = False
CELERYBEAT_SCHEDULE['discord.update_all_usernames'] = { CELERYBEAT_SCHEDULE['discord.update_all_usernames'] = {
'task': 'discord.update_all_usernames', 'task': 'discord.update_all_usernames',
'schedule': crontab(hour='*/12'), 'schedule': crontab(minute=0, hour='*/12'),
} }
``` ```