mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-09 20:40:17 +02:00
Add menu item badge feature and update group icons
This commit is contained in:
parent
f729c6b650
commit
228af38a4a
37
allianceauth/groupmanagement/auth_hooks.py
Normal file
37
allianceauth/groupmanagement/auth_hooks.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth import hooks
|
||||
|
||||
from . import urls
|
||||
from .managers import GroupManager
|
||||
|
||||
|
||||
class GroupManagementMenuItem(MenuItemHook):
|
||||
""" This class ensures only authorized users will see the menu entry """
|
||||
def __init__(self):
|
||||
# setup menu entry for sidebar
|
||||
MenuItemHook.__init__(
|
||||
self,
|
||||
text=_('Group Management'),
|
||||
classes='fas fa-users-cog fa-fw',
|
||||
url_name='groupmanagement:management',
|
||||
order=50,
|
||||
navactive=['groupmanagement:management']
|
||||
)
|
||||
|
||||
def render(self, request):
|
||||
if GroupManager.can_manage_groups(request.user):
|
||||
self.count = GroupManager.pending_requests_count_for_user(request.user)
|
||||
return MenuItemHook.render(self, request)
|
||||
return ''
|
||||
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
def register_menu():
|
||||
return GroupManagementMenuItem()
|
||||
|
||||
|
||||
@hooks.register('url_hook')
|
||||
def register_urls():
|
||||
return UrlHook(urls, 'group', r'^group/')
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, User
|
||||
from django.db.models import Q, QuerySet
|
||||
|
||||
from allianceauth.authentication.models import State
|
||||
from .models import GroupRequest
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -101,3 +102,18 @@ class GroupManager:
|
||||
if user.is_authenticated:
|
||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def pending_requests_count_for_user(cls, user: User) -> int:
|
||||
"""Returns the number of pending group requests for the given user"""
|
||||
|
||||
if cls.has_management_permission(user):
|
||||
return GroupRequest.objects.filter(status="pending").count()
|
||||
else:
|
||||
return (
|
||||
GroupRequest.objects
|
||||
.filter(status="pending")
|
||||
.filter(group__authgroup__group_leaders__exact=user)
|
||||
.select_related("group__authgroup__group_leaders")
|
||||
.count()
|
||||
)
|
||||
|
@ -4,7 +4,6 @@ from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from allianceauth.authentication.models import State
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class GroupRequest(models.Model):
|
||||
|
@ -1,15 +0,0 @@
|
||||
from django import template
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from allianceauth.groupmanagement.managers import GroupManager
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def can_manage_groups(user: User) -> bool:
|
||||
"""returns True if the given user can manage groups. Returns False otherwise."""
|
||||
if not isinstance(user, User):
|
||||
return False
|
||||
return GroupManager.can_manage_groups(user)
|
@ -7,7 +7,7 @@ from django.urls import reverse
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..models import AuthGroup
|
||||
from ..models import GroupRequest
|
||||
from ..managers import GroupManager
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ class MockUserNotAuthenticated():
|
||||
def __init__(self):
|
||||
self.is_authenticated = False
|
||||
|
||||
|
||||
class GroupManagementVisibilityTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@ -37,22 +38,20 @@ class GroupManagementVisibilityTestCase(TestCase):
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
|
||||
def test_get_group_leaders_groups(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
|
||||
def test_can_manage_group(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.user.groups.add(self.group1)
|
||||
@ -182,7 +181,6 @@ class TestGroupManager(TestCase):
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
|
||||
def test_joinable_group_guest(self):
|
||||
guest_state = AuthUtils.get_guest_state()
|
||||
for x in [
|
||||
@ -200,7 +198,6 @@ class TestGroupManager(TestCase):
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
|
||||
def test_get_all_non_internal_groups(self):
|
||||
result = GroupManager.get_all_non_internal_groups()
|
||||
expected = {
|
||||
@ -224,7 +221,7 @@ class TestGroupManager(TestCase):
|
||||
def test_get_joinable_groups_for_user_no_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected= {self.group_public_1, self.group_public_2}
|
||||
expected = {self.group_public_1, self.group_public_2}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_guest_w_permission_(self):
|
||||
@ -335,3 +332,96 @@ class TestGroupManager(TestCase):
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
|
||||
class TestPendingRequestsCountForUser(TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.group_1 = Group.objects.create(name="Group 1")
|
||||
self.group_2 = Group.objects.create(name="Group 2")
|
||||
self.user_leader_1 = AuthUtils.create_member('Clark Kent')
|
||||
self.group_1.authgroup.group_leaders.add(self.user_leader_1)
|
||||
self.user_leader_2 = AuthUtils.create_member('Peter Parker')
|
||||
self.group_2.authgroup.group_leaders.add(self.user_leader_2)
|
||||
self.user_requestor = AuthUtils.create_member('Bruce Wayne')
|
||||
|
||||
def test_single_request_for_leader(self):
|
||||
# given user_leader_1 is leader of group_1
|
||||
# and user_leader_2 is leader of group_2
|
||||
# when user_requestor is requesting access to group 1
|
||||
# then return 1 for user_leader 1 and 0 for user_leader_2
|
||||
GroupRequest.objects.create(
|
||||
status="pending", user=self.user_requestor, group=self.group_1
|
||||
)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
|
||||
)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_leader_2), 0
|
||||
)
|
||||
|
||||
def test_return_none_for_none_leader(self):
|
||||
# given user_requestor is leader of no group
|
||||
# when user_requestor is requesting access to group 1
|
||||
# then return 0 for user_requestor
|
||||
GroupRequest.objects.create(
|
||||
status="pending", user=self.user_requestor, group=self.group_1
|
||||
)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_requestor), 0
|
||||
)
|
||||
|
||||
def test_single_leave_request(self):
|
||||
# given user_leader_2 is leader of group_2
|
||||
# and user_requestor is member of group 2
|
||||
# when user_requestor is requesting to leave group 2
|
||||
# then return 1 for user_leader_2
|
||||
self.user_requestor.groups.add(self.group_2)
|
||||
|
||||
GroupRequest.objects.create(
|
||||
status="pending",
|
||||
user=self.user_requestor,
|
||||
group=self.group_2,
|
||||
leave_request=True
|
||||
)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_leader_2), 1
|
||||
)
|
||||
|
||||
def test_join_and_leave_request(self):
|
||||
# given user_leader_2 is leader of group_2
|
||||
# and user_requestor is member of group 2
|
||||
# when user_requestor is requesting to leave group 2
|
||||
# and user_requestor_2 is requesting to join group 2
|
||||
# then return 2 for user_leader_2
|
||||
self.user_requestor.groups.add(self.group_2)
|
||||
user_requestor_2 = AuthUtils.create_member("Lex Luther")
|
||||
GroupRequest.objects.create(
|
||||
status="pending",
|
||||
user=user_requestor_2,
|
||||
group=self.group_2
|
||||
)
|
||||
GroupRequest.objects.create(
|
||||
status="pending",
|
||||
user=self.user_requestor,
|
||||
group=self.group_2,
|
||||
leave_request=True
|
||||
)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_leader_2), 2
|
||||
)
|
||||
|
||||
def test_single_request_for_user_with_management_perm(self):
|
||||
# given user_leader_4 which is leafer of no group
|
||||
# but has the management permissions
|
||||
# when user_requestor is requesting access to group 1
|
||||
# then return 1 for user_leader_4
|
||||
user_leader_4 = AuthUtils.create_member("Lex Luther")
|
||||
AuthUtils.add_permission_to_user_by_name("auth.group_management", user_leader_4)
|
||||
user_leader_4 = User.objects.get(pk=user_leader_4.pk)
|
||||
GroupRequest.objects.create(
|
||||
status="pending", user=self.user_requestor, group=self.group_1
|
||||
)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
|
||||
)
|
||||
|
@ -1,27 +0,0 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..templatetags.groupmanagement import can_manage_groups
|
||||
|
||||
MODULE_PATH = 'allianceauth.groupmanagement.templatetags.groupmanagement'
|
||||
|
||||
|
||||
@patch(MODULE_PATH + '.GroupManager.can_manage_groups')
|
||||
class TestCanManageGroups(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_return_normal_result(self, mock_can_manage_groups):
|
||||
mock_can_manage_groups.return_value = True
|
||||
|
||||
self.assertTrue(can_manage_groups(self.user))
|
||||
self.assertTrue(mock_can_manage_groups.called)
|
||||
|
||||
def test_return_false_if_not_user(self, mock_can_manage_groups):
|
||||
mock_can_manage_groups.return_value = True
|
||||
|
||||
self.assertFalse(can_manage_groups('invalid'))
|
||||
self.assertFalse(mock_can_manage_groups.called)
|
@ -1,32 +1,29 @@
|
||||
from . import views
|
||||
|
||||
from django.conf.urls import include, url
|
||||
from django.conf.urls import url
|
||||
app_name = 'groupmanagement'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^groups/', views.groups_view, name='groups'),
|
||||
url(r'^group/', include([
|
||||
url(r'^management/', views.group_management,
|
||||
name='management'),
|
||||
url(r'^membership/$', views.group_membership,
|
||||
name='membership'),
|
||||
url(r'^membership/(\w+)/$', views.group_membership_list,
|
||||
name='membership_list'),
|
||||
url(r'^membership/(\w+)/audit/$', views.group_membership_audit, name="audit_log"),
|
||||
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
|
||||
name='membership_remove'),
|
||||
url(r'^request_add/(\w+)', views.group_request_add,
|
||||
name='request_add'),
|
||||
url(r'^request/accept/(\w+)', views.group_accept_request,
|
||||
name='accept_request'),
|
||||
url(r'^request/reject/(\w+)', views.group_reject_request,
|
||||
name='reject_request'),
|
||||
|
||||
url(r'^request_leave/(\w+)', views.group_request_leave,
|
||||
name='request_leave'),
|
||||
url(r'leave_request/accept/(\w+)', views.group_leave_accept_request,
|
||||
name='leave_accept_request'),
|
||||
url(r'^leave_request/reject/(\w+)', views.group_leave_reject_request,
|
||||
name='leave_reject_request'),
|
||||
])),
|
||||
url(r'^management/', views.group_management,
|
||||
name='management'),
|
||||
url(r'^membership/$', views.group_membership,
|
||||
name='membership'),
|
||||
url(r'^membership/(\w+)/$', views.group_membership_list,
|
||||
name='membership_list'),
|
||||
url(r'^membership/(\w+)/audit/$', views.group_membership_audit, name="audit_log"),
|
||||
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
|
||||
name='membership_remove'),
|
||||
url(r'^request_add/(\w+)', views.group_request_add,
|
||||
name='request_add'),
|
||||
url(r'^request/accept/(\w+)', views.group_accept_request,
|
||||
name='accept_request'),
|
||||
url(r'^request/reject/(\w+)', views.group_reject_request,
|
||||
name='reject_request'),
|
||||
url(r'^request_leave/(\w+)', views.group_request_leave,
|
||||
name='request_leave'),
|
||||
url(r'leave_request/accept/(\w+)', views.group_leave_accept_request,
|
||||
name='leave_accept_request'),
|
||||
url(r'^leave_request/reject/(\w+)', views.group_leave_reject_request,
|
||||
name='leave_reject_request'),
|
||||
]
|
||||
|
@ -1,7 +1,10 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.hrapplications import urls
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
|
||||
from . import urls
|
||||
from .models import Application
|
||||
|
||||
|
||||
class ApplicationsMenu(MenuItemHook):
|
||||
@ -12,6 +15,11 @@ class ApplicationsMenu(MenuItemHook):
|
||||
'hrapplications:index',
|
||||
navactive=['hrapplications:'])
|
||||
|
||||
def render(self, request):
|
||||
app_count = Application.objects.pending_requests_count_for_user(request.user)
|
||||
self.count = app_count if app_count and app_count > 0 else None
|
||||
return MenuItemHook.render(self, request)
|
||||
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
def register_menu():
|
||||
|
25
allianceauth/hrapplications/managers.py
Normal file
25
allianceauth/hrapplications/managers.py
Normal file
@ -0,0 +1,25 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ApplicationManager(models.Manager):
|
||||
|
||||
def pending_requests_count_for_user(self, user: User) -> Optional[int]:
|
||||
"""Returns the number of pending group requests for the given user"""
|
||||
if user.is_superuser:
|
||||
return self.filter(approved__isnull=True).count()
|
||||
elif user.has_perm("auth.human_resources"):
|
||||
main_character = user.profile.main_character
|
||||
if main_character:
|
||||
return (
|
||||
self
|
||||
.select_related("form__corp")
|
||||
.filter(form__corp__corporation_id=main_character.corporation_id)
|
||||
.filter(approved__isnull=True)
|
||||
.count()
|
||||
)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
@ -2,8 +2,9 @@ from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from allianceauth.eveonline.models import EveCorporationInfo
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo
|
||||
|
||||
from .managers import ApplicationManager
|
||||
|
||||
|
||||
class ApplicationQuestion(models.Model):
|
||||
@ -22,6 +23,7 @@ class ApplicationChoice(models.Model):
|
||||
def __str__(self):
|
||||
return self.choice_text
|
||||
|
||||
|
||||
class ApplicationForm(models.Model):
|
||||
questions = SortedManyToManyField(ApplicationQuestion)
|
||||
corp = models.OneToOneField(EveCorporationInfo, on_delete=models.CASCADE)
|
||||
@ -38,6 +40,8 @@ class Application(models.Model):
|
||||
reviewer_character = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, blank=True, null=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = ApplicationManager()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user) + " Application To " + str(self.form)
|
||||
|
||||
|
@ -1 +1,103 @@
|
||||
# Create your tests here.
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from .models import Application, ApplicationForm, ApplicationQuestion, ApplicationChoice
|
||||
|
||||
|
||||
class TestApplicationManagersPendingRequestsCountForUser(TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.corporation_1 = EveCorporationInfo.objects.create(
|
||||
corporation_id=2001, corporation_name="Wayne Tech", member_count=42
|
||||
)
|
||||
self.corporation_2 = EveCorporationInfo.objects.create(
|
||||
corporation_id=2011, corporation_name="Lex Corp", member_count=666
|
||||
)
|
||||
question = ApplicationQuestion.objects.create(title="Dummy Question")
|
||||
ApplicationChoice.objects.create(question=question, choice_text="yes")
|
||||
ApplicationChoice.objects.create(question=question, choice_text="no")
|
||||
self.form_corporation_1 = ApplicationForm.objects.create(
|
||||
corp=self.corporation_1
|
||||
)
|
||||
self.form_corporation_1.questions.add(question)
|
||||
self.form_corporation_2 = ApplicationForm.objects.create(
|
||||
corp=self.corporation_2
|
||||
)
|
||||
self.form_corporation_2.questions.add(question)
|
||||
|
||||
self.user_requestor = AuthUtils.create_member("Peter Parker")
|
||||
|
||||
self.user_manager = AuthUtils.create_member("Bruce Wayne")
|
||||
AuthUtils.add_main_character_2(
|
||||
self.user_manager,
|
||||
self.user_manager.username,
|
||||
1001,
|
||||
self.corporation_1.corporation_id,
|
||||
self.corporation_1.corporation_name,
|
||||
)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
"auth.human_resources", self.user_manager
|
||||
)
|
||||
self.user_manager = User.objects.get(pk=self.user_manager.pk)
|
||||
|
||||
def test_no_pending_application(self):
|
||||
# given manager of corporation 1 has permission
|
||||
# when no application is pending for corporation 1
|
||||
# return 0
|
||||
self.assertEqual(
|
||||
Application.objects.pending_requests_count_for_user(self.user_manager), 0
|
||||
)
|
||||
|
||||
def test_single_pending_application(self):
|
||||
# given manager of corporation 1 has permission
|
||||
# when 1 application is pending for corporation 1
|
||||
# return 1
|
||||
Application.objects.create(
|
||||
form=self.form_corporation_1, user=self.user_requestor
|
||||
)
|
||||
self.assertEqual(
|
||||
Application.objects.pending_requests_count_for_user(self.user_manager), 1
|
||||
)
|
||||
|
||||
def test_user_has_no_permission(self):
|
||||
# given user has no permission
|
||||
# when 1 application is pending
|
||||
# return None
|
||||
self.assertIsNone(
|
||||
Application.objects.pending_requests_count_for_user(self.user_requestor)
|
||||
)
|
||||
|
||||
def test_two_pending_applications_for_different_corporations_normal_manager(self):
|
||||
# given manager of corporation 1 has permission
|
||||
# when 1 application is pending for corporation 1
|
||||
# and 1 application is pending for corporation 2
|
||||
# return 1
|
||||
Application.objects.create(
|
||||
form=self.form_corporation_1, user=self.user_requestor
|
||||
)
|
||||
Application.objects.create(
|
||||
form=self.form_corporation_2, user=self.user_requestor
|
||||
)
|
||||
self.assertEqual(
|
||||
Application.objects.pending_requests_count_for_user(self.user_manager), 1
|
||||
)
|
||||
|
||||
def test_two_pending_applications_for_different_corporations_manager_is_super(self):
|
||||
# given manager of corporation 1 has permission
|
||||
# when 1 application is pending for corporation 1
|
||||
# and 1 application is pending for corporation 2
|
||||
# return 1
|
||||
Application.objects.create(
|
||||
form=self.form_corporation_1, user=self.user_requestor
|
||||
)
|
||||
Application.objects.create(
|
||||
form=self.form_corporation_2, user=self.user_requestor
|
||||
)
|
||||
superuser = User.objects.create_superuser(
|
||||
"Superman", "superman@example.com", "password"
|
||||
)
|
||||
self.assertEqual(
|
||||
Application.objects.pending_requests_count_for_user(superuser), 2
|
||||
)
|
||||
|
@ -139,6 +139,11 @@ class MenuItemHook:
|
||||
self.url_name = url_name
|
||||
self.template = 'public/menuitem.html'
|
||||
self.order = order if order is not None else 9999
|
||||
|
||||
# count is an integer shown next to the menu item as badge when count != None
|
||||
# apps need to set the count in their child class, e.g. in render() method
|
||||
self.count = None
|
||||
|
||||
navactive = navactive or []
|
||||
navactive.append(url_name)
|
||||
self.navactive = navactive
|
||||
|
@ -2,7 +2,10 @@
|
||||
{% load navactive %}
|
||||
|
||||
<li>
|
||||
<a class="{% navactive request item.navactive|join:" " %}" href="{% url item.url_name %}">
|
||||
<a class="{% navactive request item.navactive|join:' ' %}" href="{% url item.url_name %}">
|
||||
<i class="{{ item.classes }}"></i> {% trans item.text %}
|
||||
{% if item.count != None %}
|
||||
<span class="badge">{{ item.count }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -1,7 +1,10 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
|
||||
from . import urls
|
||||
from .managers import SRPManager
|
||||
|
||||
|
||||
class SrpMenu(MenuItemHook):
|
||||
@ -13,6 +16,8 @@ class SrpMenu(MenuItemHook):
|
||||
|
||||
def render(self, request):
|
||||
if request.user.has_perm('srp.access_srp'):
|
||||
app_count = SRPManager.pending_requests_count_for_user(request.user)
|
||||
self.count = app_count if app_count and app_count > 0 else None
|
||||
return MenuItemHook.render(self, request)
|
||||
return ''
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from allianceauth import NAME
|
||||
from allianceauth.eveonline.providers import provider
|
||||
|
||||
from .models import SrpUserRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -50,3 +52,12 @@ class SRPManager:
|
||||
return ship_type, ship_value, victim_id
|
||||
else:
|
||||
raise ValueError("Invalid Kill ID or Hash.")
|
||||
|
||||
@staticmethod
|
||||
def pending_requests_count_for_user(user: User):
|
||||
"""returns the number of open SRP requests for given user
|
||||
or None if user has no permission"""
|
||||
if user.has_perm("auth.srp_management"):
|
||||
return SrpUserRequest.objects.filter(srp_status="pending").count()
|
||||
else:
|
||||
return None
|
||||
|
@ -1,11 +1,16 @@
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import patch, Mock
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.timezone import now
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..managers import SRPManager
|
||||
from ..models import SrpUserRequest, SrpFleetMain
|
||||
|
||||
MODULE_PATH = 'allianceauth.srp.managers'
|
||||
|
||||
@ -13,6 +18,7 @@ currentdir = os.path.dirname(os.path.abspath(inspect.getfile(
|
||||
inspect.currentframe()
|
||||
)))
|
||||
|
||||
|
||||
def load_data(filename):
|
||||
"""loads given JSON file from `testdata` sub folder and returns content"""
|
||||
with open(
|
||||
@ -52,7 +58,7 @@ class TestSrpManager(TestCase):
|
||||
mock_get.return_value.json.return_value = ['']
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
SRPManager.get_kill_data(81973979)
|
||||
|
||||
@patch(MODULE_PATH + '.provider')
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
@ -67,6 +73,34 @@ class TestSrpManager(TestCase):
|
||||
result.return_value = None
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
SRPManager.get_kill_data(81973979)
|
||||
|
||||
def test_pending_requests_count_for_user(self):
|
||||
user = AuthUtils.create_member("Bruce Wayne")
|
||||
|
||||
# when no permission to approve SRP requests
|
||||
# then return None
|
||||
self.assertIsNone(SRPManager.pending_requests_count_for_user(user))
|
||||
|
||||
# given permission to approve SRP requests
|
||||
# when no open requests
|
||||
# then return 0
|
||||
AuthUtils.add_permission_to_user_by_name("auth.srp_management", user)
|
||||
user = User.objects.get(pk=user.pk)
|
||||
self.assertEqual(SRPManager.pending_requests_count_for_user(user), 0)
|
||||
|
||||
# given permission to approve SRP requests
|
||||
# when 1 pending request
|
||||
# then return 1
|
||||
fleet = SrpFleetMain.objects.create(fleet_time=now())
|
||||
SrpUserRequest.objects.create(
|
||||
killboard_link="https://zkillboard.com/kill/79111612/",
|
||||
srp_status="Pending",
|
||||
srp_fleet_main=fleet,
|
||||
)
|
||||
SrpUserRequest.objects.create(
|
||||
killboard_link="https://zkillboard.com/kill/79111612/",
|
||||
srp_status="Approved",
|
||||
srp_fleet_main=fleet,
|
||||
)
|
||||
self.assertEqual(SRPManager.pending_requests_count_for_user(user), 1)
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
{% load menu_items %}
|
||||
{% load groupmanagement %}
|
||||
|
||||
<div class="col-sm-2 auth-side-navbar" role="navigation">
|
||||
<div class="collapse navbar-collapse auth-menus-collapse auth-side-navbar-collapse">
|
||||
@ -14,19 +13,10 @@
|
||||
</li>
|
||||
<li>
|
||||
<a class="{% navactive request 'groupmanagement:groups' %}" href="{% url 'groupmanagement:groups' %}">
|
||||
<i class="fas fa-sitemap fa-fw"></i> {% trans "Groups" %}
|
||||
<i class="fas fa-users fa-fw"></i> {% trans "Groups" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if request.user|can_manage_groups %}
|
||||
<li>
|
||||
<a class="{% navactive request 'groupmanagement:management groupmanagement:membership groupmanagement:membership_list' %}"
|
||||
href="{% url 'groupmanagement:management' %}">
|
||||
<i class="fas fa-sitemap fa-fw"></i> {% trans "Group Management" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% menu_items %}
|
||||
|
||||
</ul>
|
||||
|
@ -4,24 +4,58 @@ The menu hooks allow you to dynamically specify menu items from your plugin app
|
||||
|
||||
To register a MenuItemHook class you would do the following:
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
def register_menu():
|
||||
return MenuItemHook('Example Item', 'glyphicon glyphicon-heart', 'example_url_name', 150)
|
||||
|
||||
```Python
|
||||
@hooks.register('menu_item_hook')
|
||||
def register_menu():
|
||||
return MenuItemHook('Example Item', 'glyphicon glyphicon-heart', 'example_url_name',150)
|
||||
```
|
||||
|
||||
The `MenuItemHook` class specifies some parameters/instance variables required for menu item display.
|
||||
|
||||
`MenuItemHook(text, classes, url_name, order=None)`
|
||||
## MenuItemHook(text, classes, url_name, order=None)
|
||||
|
||||
### text
|
||||
|
||||
The text shown as menu item, e.g. usually the name of the app.
|
||||
|
||||
### classes
|
||||
|
||||
#### text
|
||||
The text value of the link
|
||||
#### classes
|
||||
The classes that should be applied to the bootstrap menu item icon
|
||||
#### url_name
|
||||
|
||||
### url_name
|
||||
|
||||
The name of the Django URL to use
|
||||
#### order
|
||||
An integer which specifies the order of the menu item, lowest to highest
|
||||
#### navactive
|
||||
|
||||
### order
|
||||
|
||||
An integer which specifies the order of the menu item, lowest to highest. Community apps are free ot use an oder above `1000`. Numbers below are served for Auth.
|
||||
|
||||
### navactive
|
||||
|
||||
A list of views or namespaces the link should be highlighted on. See [django-navhelper](https://github.com/geelweb/django-navhelper#navactive) for usage. Defaults to the supplied `url_name`.
|
||||
|
||||
### count
|
||||
|
||||
`count` is an integer shown next to the menu item as badge when `count` is not `None`.
|
||||
|
||||
This is a great feature to signal the user, that he has some open issues to take care of within an app. For example Auth uses this feature to show the specific number of open group request to the current user.
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
Here is how to stay consistent with the Auth design philosophy for using this feature:
|
||||
1. Use it to display open items that the current user can close by himself only. Do not use it for items, that the user has no control over.
|
||||
2. If there are currently no open items, do not show a badge at all.
|
||||
```
|
||||
|
||||
To use it set count the `render()` function of your subclass in accordance to the current user. Here is an example:
|
||||
|
||||
```Python
|
||||
def render(self, request):
|
||||
# ...
|
||||
self.count = calculate_count_for_user(request.user)
|
||||
# ...
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
If you cannot get the menu item to look the way you wish, you are free to subclass and override the default render function and the template used.
|
||||
|
Loading…
x
Reference in New Issue
Block a user