mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-09 12:30:15 +02:00
Add option to notify approvers about new group requests
This commit is contained in:
parent
2bd2c09c23
commit
982cac8c43
@ -14,8 +14,8 @@ class GroupManager:
|
||||
|
||||
@classmethod
|
||||
def get_joinable_groups_for_user(
|
||||
cls, user: User, include_hidden = True
|
||||
) -> QuerySet:
|
||||
cls, user: User, include_hidden=True
|
||||
) -> QuerySet[Group]:
|
||||
"""get groups a user could join incl. groups already joined"""
|
||||
groups_qs = cls.get_joinable_groups(user.profile.state)
|
||||
|
||||
@ -28,24 +28,27 @@ class GroupManager:
|
||||
return groups_qs
|
||||
|
||||
@staticmethod
|
||||
def get_joinable_groups(state: State) -> QuerySet:
|
||||
def get_joinable_groups(state: State) -> QuerySet[Group]:
|
||||
"""get groups that can be joined by user with given state"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)\
|
||||
.filter(Q(authgroup__states=state) | Q(authgroup__states=None))
|
||||
|
||||
@staticmethod
|
||||
def get_all_non_internal_groups() -> QuerySet:
|
||||
"""get groups that are not internal"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
return (
|
||||
Group.objects
|
||||
.exclude(authgroup__internal=True)
|
||||
.filter(Q(authgroup__states=state) | Q(authgroup__states=None))
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_group_leaders_groups(user: User):
|
||||
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \
|
||||
Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
|
||||
def get_all_non_internal_groups() -> QuerySet[Group]:
|
||||
"""get groups that are not internal"""
|
||||
return Group.objects.exclude(authgroup__internal=True)
|
||||
|
||||
@staticmethod
|
||||
def get_group_leaders_groups(user: User) -> QuerySet[Group]:
|
||||
return (
|
||||
Group.objects.filter(authgroup__group_leaders=user)
|
||||
| Group.objects.filter(
|
||||
authgroup__group_leader_groups__in=list(user.groups.all())
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def joinable_group(group: Group, state: State) -> bool:
|
||||
@ -57,11 +60,11 @@ class GroupManager:
|
||||
:param state: allianceauth.authentication.State object
|
||||
:return: bool True if its joinable, False otherwise
|
||||
"""
|
||||
if (len(group.authgroup.states.all()) != 0
|
||||
if (
|
||||
len(group.authgroup.states.all()) != 0
|
||||
and state not in group.authgroup.states.all()
|
||||
):
|
||||
return False
|
||||
else:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
@ -78,7 +81,7 @@ class GroupManager:
|
||||
return user.has_perm('auth.group_management')
|
||||
|
||||
@classmethod
|
||||
def can_manage_groups(cls, user:User ) -> bool:
|
||||
def can_manage_groups(cls, user:User) -> bool:
|
||||
"""
|
||||
For use with user_passes_test decorator.
|
||||
Check if the user can manage groups. Either has the
|
||||
@ -88,7 +91,10 @@ class GroupManager:
|
||||
:return: bool True if user can manage groups, False otherwise
|
||||
"""
|
||||
if user.is_authenticated:
|
||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user)
|
||||
return (
|
||||
cls.has_management_permission(user)
|
||||
or cls.get_group_leaders_groups(user)
|
||||
)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
@ -100,19 +106,19 @@ class GroupManager:
|
||||
:return: True if the user can manage the group
|
||||
"""
|
||||
if user.is_authenticated:
|
||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
|
||||
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.all().count()
|
||||
else:
|
||||
return (
|
||||
GroupRequest.objects
|
||||
.filter(group__authgroup__group_leaders__exact=user)
|
||||
.select_related("group__authgroup__group_leaders")
|
||||
.filter(group__in=list(cls.get_group_leaders_groups(user)))
|
||||
.count()
|
||||
)
|
||||
|
@ -0,0 +1,42 @@
|
||||
# Generated by Django 3.2.9 on 2021-11-11 15:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('authentication', '0019_merge_20211026_0919'),
|
||||
('groupmanagement', '0016_remove_grouprequest_status_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='authgroup',
|
||||
name='group_leader_groups',
|
||||
field=models.ManyToManyField(blank=True, help_text='Members of leader groups can process requests for this group. Use the <code>auth.group_management</code> permission to allow a user to manage all groups.<br>', related_name='leads_group_groups', to='auth.Group'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authgroup',
|
||||
name='group_leaders',
|
||||
field=models.ManyToManyField(blank=True, help_text='Group leaders can process requests for this group. Use the <code>auth.group_management</code> permission to allow a user to manage all groups.<br>', related_name='leads_groups', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authgroup',
|
||||
name='open',
|
||||
field=models.BooleanField(default=False, help_text='Group is open and users will be automatically added upon request.<br>If the group is not open users will need their request manually approved.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authgroup',
|
||||
name='public',
|
||||
field=models.BooleanField(default=False, help_text='Group is public. Any registered user is able to join this group, with visibility based on the other options set for this group.<br>Auth will not remove users from this group automatically when they are no longer authenticated.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authgroup',
|
||||
name='states',
|
||||
field=models.ManyToManyField(blank=True, help_text='States listed here will have the ability to join this group provided they have the proper permissions.<br>', related_name='valid_states', to='authentication.State'),
|
||||
),
|
||||
]
|
@ -1,16 +1,26 @@
|
||||
from typing import Set
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.authentication.models import State
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
|
||||
class GroupRequest(models.Model):
|
||||
"""Request from a user for joining or leaving a group."""
|
||||
leave_request = models.BooleanField(default=0)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username + ":" + self.group.name
|
||||
|
||||
@property
|
||||
def main_char(self):
|
||||
"""
|
||||
@ -19,11 +29,22 @@ class GroupRequest(models.Model):
|
||||
"""
|
||||
return self.user.profile.main_character
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username + ":" + self.group.name
|
||||
def notify_leaders(self) -> None:
|
||||
"""Send notification to all group leaders about this request.
|
||||
|
||||
Note: No translations, because language for each leader is unknown
|
||||
"""
|
||||
if not getattr(settings, 'GROUPMANAGEMENT_REQUESTS_NOTIFICATION', False):
|
||||
return
|
||||
keyword = "leave" if self.leave_request else "join"
|
||||
title = f"Group Management: {keyword.title()} request for {self.group.name}"
|
||||
message = f"{self.user} want's to {keyword} {self.group.name}."
|
||||
for appover in self.group.authgroup.group_request_approvers():
|
||||
notify(user=appover, title=title, message=message, level="info")
|
||||
|
||||
|
||||
class RequestLog(models.Model):
|
||||
"""Log entry about who joined and left a group and who approved it."""
|
||||
request_type = models.BooleanField(null=True)
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
request_info = models.CharField(max_length=254)
|
||||
@ -61,11 +82,12 @@ class AuthGroup(models.Model):
|
||||
e.g. group.authgroup.internal
|
||||
|
||||
Logic:
|
||||
Internal - not requestable by users, at all. Covers Corp_, Alliance_, Members etc groups.
|
||||
Groups are internal by default
|
||||
Internal - not requestable by users, at all. Covers Corp_, Alliance_,
|
||||
Members etc groups. Groups are internal by default
|
||||
|
||||
Public - Other options are respected, but any user will be able to become and remain a member, even if they
|
||||
have no API etc entered. Auth will not manage these groups automatically so user removal is up to
|
||||
Public - Other options are respected, but any user will be able to become
|
||||
and remain a member, even if they have no API etc entered.
|
||||
Auth will not manage these groups automatically so user removal is up to
|
||||
group managers/leaders.
|
||||
|
||||
Not Internal and:
|
||||
@ -75,46 +97,88 @@ class AuthGroup(models.Model):
|
||||
Not Open - Users requests must be approved before they are added to the group
|
||||
"""
|
||||
group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True)
|
||||
|
||||
internal = models.BooleanField(default=True,
|
||||
help_text="Internal group, users cannot see, join or request to join this group.<br>"
|
||||
internal = models.BooleanField(
|
||||
default=True,
|
||||
help_text=_(
|
||||
"Internal group, users cannot see, join or request to join this group.<br>"
|
||||
"Used for groups such as Members, Corp_*, Alliance_* etc.<br>"
|
||||
"<b>Overrides Hidden and Open options when selected.</b>")
|
||||
hidden = models.BooleanField(default=True, help_text="Group is hidden from users but can still join with the correct link.")
|
||||
open = models.BooleanField(default=False,
|
||||
help_text="Group is open and users will be automatically added upon request. <br>"
|
||||
"If the group is not open users will need their request manually approved.")
|
||||
public = models.BooleanField(default=False,
|
||||
help_text="Group is public. Any registered user is able to join this group, with "
|
||||
"visibility based on the other options set for this group.<br> Auth will "
|
||||
"not remove users from this group automatically when they are no longer "
|
||||
"authenticated.")
|
||||
# Group leaders have management access to this group
|
||||
group_leaders = models.ManyToManyField(User, related_name='leads_groups', blank=True,
|
||||
help_text="Group leaders can process group requests for this group "
|
||||
"specifically. Use the auth.group_management permission to allow "
|
||||
"a user to manage all groups.")
|
||||
# allow groups to be *group leads*
|
||||
group_leader_groups = models.ManyToManyField(Group, related_name='leads_group_groups', blank=True,
|
||||
help_text="Group leaders can process group requests for this group "
|
||||
"specifically. Use the auth.group_management permission to allow "
|
||||
"a user to manage all groups.")
|
||||
"<b>Overrides Hidden and Open options when selected.</b>"
|
||||
)
|
||||
)
|
||||
hidden = models.BooleanField(
|
||||
default=True,
|
||||
help_text=_(
|
||||
"Group is hidden from users but can still join with the correct link."
|
||||
)
|
||||
)
|
||||
open = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
"Group is open and users will be automatically added upon request.<br>"
|
||||
"If the group is not open users will need their request manually approved."
|
||||
)
|
||||
)
|
||||
public = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
"Group is public. Any registered user is able to join this group, with "
|
||||
"visibility based on the other options set for this group.<br>"
|
||||
"Auth will not remove users from this group automatically when they "
|
||||
"are no longer authenticated."
|
||||
)
|
||||
)
|
||||
group_leaders = models.ManyToManyField(
|
||||
User,
|
||||
related_name='leads_groups',
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Group leaders can process requests for this group. "
|
||||
"Use the <code>auth.group_management</code> permission to allow "
|
||||
"a user to manage all groups.<br>"
|
||||
)
|
||||
)
|
||||
group_leader_groups = models.ManyToManyField(
|
||||
Group,
|
||||
related_name='leads_group_groups',
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Members of leader groups can process requests for this group. "
|
||||
"Use the <code>auth.group_management</code> permission "
|
||||
"to allow a user to manage all groups.<br>")
|
||||
)
|
||||
states = models.ManyToManyField(
|
||||
State,
|
||||
related_name='valid_states',
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"States listed here will have the ability to join this group provided "
|
||||
"they have the proper permissions.<br>"
|
||||
)
|
||||
)
|
||||
description = models.TextField(
|
||||
max_length=512,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Short description <i>(max. 512 characters)</i> "
|
||||
"of the group shown to users."
|
||||
)
|
||||
)
|
||||
|
||||
states = models.ManyToManyField(State, related_name='valid_states', blank=True,
|
||||
help_text="States listed here will have the ability to join this group provided "
|
||||
"they have the proper permissions.")
|
||||
|
||||
description = models.TextField(max_length=512, blank=True, help_text="Short description <i>(max. 512 characters)"
|
||||
"</i> of the group shown to users.")
|
||||
class Meta:
|
||||
permissions = (
|
||||
("request_groups", _("Can request non-public groups")),
|
||||
)
|
||||
default_permissions = tuple()
|
||||
|
||||
def __str__(self):
|
||||
return self.group.name
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("request_groups", "Can request non-public groups"),
|
||||
def group_request_approvers(self) -> Set[User]:
|
||||
"""Return all users who can approve a group request."""
|
||||
return set(
|
||||
self.group_leaders.all()
|
||||
| User.objects.filter(groups__in=list(self.group_leader_groups.all()))
|
||||
)
|
||||
default_permissions = tuple()
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group)
|
||||
|
@ -1,8 +1,5 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
@ -44,9 +41,9 @@ class GroupManagementVisibilityTestCase(TestCase):
|
||||
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()
|
||||
@ -71,70 +68,66 @@ class GroupManagementVisibilityTestCase(TestCase):
|
||||
|
||||
|
||||
class TestGroupManager(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
def setUp(self) -> None:
|
||||
# group 1
|
||||
cls.group_default = Group.objects.create(name='default')
|
||||
cls.group_default.authgroup.description = 'Default Group'
|
||||
cls.group_default.authgroup.internal = False
|
||||
cls.group_default.authgroup.hidden = False
|
||||
cls.group_default.authgroup.save()
|
||||
self.group_default = Group.objects.create(name='default')
|
||||
self.group_default.authgroup.description = 'Default Group'
|
||||
self.group_default.authgroup.internal = False
|
||||
self.group_default.authgroup.hidden = False
|
||||
self.group_default.authgroup.save()
|
||||
|
||||
# group 2
|
||||
cls.group_internal = Group.objects.create(name='internal')
|
||||
cls.group_internal.authgroup.description = 'Internal Group'
|
||||
cls.group_internal.authgroup.internal = True
|
||||
cls.group_internal.authgroup.save()
|
||||
self.group_internal = Group.objects.create(name='internal')
|
||||
self.group_internal.authgroup.description = 'Internal Group'
|
||||
self.group_internal.authgroup.internal = True
|
||||
self.group_internal.authgroup.save()
|
||||
|
||||
# group 3
|
||||
cls.group_hidden = Group.objects.create(name='hidden')
|
||||
cls.group_hidden.authgroup.description = 'Hidden Group'
|
||||
cls.group_hidden.authgroup.internal = False
|
||||
cls.group_hidden.authgroup.hidden = True
|
||||
cls.group_hidden.authgroup.save()
|
||||
self.group_hidden = Group.objects.create(name='hidden')
|
||||
self.group_hidden.authgroup.description = 'Hidden Group'
|
||||
self.group_hidden.authgroup.internal = False
|
||||
self.group_hidden.authgroup.hidden = True
|
||||
self.group_hidden.authgroup.save()
|
||||
|
||||
# group 4
|
||||
cls.group_open = Group.objects.create(name='open')
|
||||
cls.group_open.authgroup.description = 'Open Group'
|
||||
cls.group_open.authgroup.internal = False
|
||||
cls.group_open.authgroup.hidden = False
|
||||
cls.group_open.authgroup.open = True
|
||||
cls.group_open.authgroup.save()
|
||||
self.group_open = Group.objects.create(name='open')
|
||||
self.group_open.authgroup.description = 'Open Group'
|
||||
self.group_open.authgroup.internal = False
|
||||
self.group_open.authgroup.hidden = False
|
||||
self.group_open.authgroup.open = True
|
||||
self.group_open.authgroup.save()
|
||||
|
||||
# group 5
|
||||
cls.group_public_1 = Group.objects.create(name='public 1')
|
||||
cls.group_public_1.authgroup.description = 'Public Group 1'
|
||||
cls.group_public_1.authgroup.internal = False
|
||||
cls.group_public_1.authgroup.hidden = False
|
||||
cls.group_public_1.authgroup.public = True
|
||||
cls.group_public_1.authgroup.save()
|
||||
self.group_public_1 = Group.objects.create(name='public 1')
|
||||
self.group_public_1.authgroup.description = 'Public Group 1'
|
||||
self.group_public_1.authgroup.internal = False
|
||||
self.group_public_1.authgroup.hidden = False
|
||||
self.group_public_1.authgroup.public = True
|
||||
self.group_public_1.authgroup.save()
|
||||
|
||||
# group 6
|
||||
cls.group_public_2 = Group.objects.create(name='public 2')
|
||||
cls.group_public_2.authgroup.description = 'Public Group 2'
|
||||
cls.group_public_2.authgroup.internal = False
|
||||
cls.group_public_2.authgroup.hidden = True
|
||||
cls.group_public_2.authgroup.open = True
|
||||
cls.group_public_2.authgroup.public = True
|
||||
cls.group_public_2.authgroup.save()
|
||||
self.group_public_2 = Group.objects.create(name='public 2')
|
||||
self.group_public_2.authgroup.description = 'Public Group 2'
|
||||
self.group_public_2.authgroup.internal = False
|
||||
self.group_public_2.authgroup.hidden = True
|
||||
self.group_public_2.authgroup.open = True
|
||||
self.group_public_2.authgroup.public = True
|
||||
self.group_public_2.authgroup.save()
|
||||
|
||||
# group 7
|
||||
cls.group_default_member = Group.objects.create(name='default members')
|
||||
cls.group_default_member.authgroup.description = \
|
||||
self.group_default_member = Group.objects.create(name='default members')
|
||||
self.group_default_member.authgroup.description = \
|
||||
'Default Group for members only'
|
||||
cls.group_default_member.authgroup.internal = False
|
||||
cls.group_default_member.authgroup.hidden = False
|
||||
cls.group_default_member.authgroup.open = False
|
||||
cls.group_default_member.authgroup.public = False
|
||||
cls.group_default_member.authgroup.states.add(
|
||||
self.group_default_member.authgroup.internal = False
|
||||
self.group_default_member.authgroup.hidden = False
|
||||
self.group_default_member.authgroup.open = False
|
||||
self.group_default_member.authgroup.public = False
|
||||
self.group_default_member.authgroup.states.add(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
cls.group_default_member.authgroup.save()
|
||||
self.group_default_member.authgroup.save()
|
||||
|
||||
def setUp(self):
|
||||
# user
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_get_joinable_group_member(self):
|
||||
@ -241,7 +234,7 @@ class TestGroupManager(TestCase):
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
self.user = AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
@ -257,7 +250,7 @@ class TestGroupManager(TestCase):
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission_no_hidden(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
self.user = AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(
|
||||
@ -273,7 +266,7 @@ class TestGroupManager(TestCase):
|
||||
|
||||
def test_has_management_permission(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
user = AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.has_management_permission(user))
|
||||
@ -288,7 +281,7 @@ class TestGroupManager(TestCase):
|
||||
|
||||
def test_can_manage_groups_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
user = AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
@ -306,7 +299,7 @@ class TestGroupManager(TestCase):
|
||||
|
||||
def test_can_manage_group_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
user = AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(
|
||||
@ -433,11 +426,21 @@ class TestPendingRequestsCountForUser(TestCase):
|
||||
# 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(
|
||||
user=self.user_requestor, group=self.group_1
|
||||
user_leader_4 = AuthUtils.add_permission_to_user_by_name(
|
||||
"auth.group_management", user_leader_4
|
||||
)
|
||||
GroupRequest.objects.create(user=self.user_requestor, group=self.group_1)
|
||||
self.assertEqual(
|
||||
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
|
||||
)
|
||||
|
||||
def test_single_request_for_members_of_leading_group(self):
|
||||
# given
|
||||
leader_group = Group.objects.create(name="Leaders")
|
||||
self.group_3.authgroup.group_leader_groups.add(leader_group)
|
||||
self.user_leader_1.groups.add(leader_group)
|
||||
GroupRequest.objects.create(user=self.user_requestor, group=self.group_3)
|
||||
# when
|
||||
result = GroupManager.pending_requests_count_for_user(self.user_leader_1)
|
||||
# then
|
||||
self.assertEqual(result, 1)
|
||||
|
@ -1,31 +1,22 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import Group
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
)
|
||||
|
||||
from ..models import GroupRequest, RequestLog
|
||||
|
||||
MODULE_PATH = "allianceauth.groupmanagement.models"
|
||||
|
||||
|
||||
def create_testdata():
|
||||
# clear DB
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
EveCharacter.objects.all().delete()
|
||||
EveCorporationInfo.objects.all().delete()
|
||||
EveAllianceInfo.objects.all().delete()
|
||||
|
||||
# group 1
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
# user 1
|
||||
user_1 = AuthUtils.create_user('Bruce Wayne')
|
||||
AuthUtils.add_main_character_2(
|
||||
@ -37,7 +28,6 @@ def create_testdata():
|
||||
)
|
||||
user_1.groups.add(group)
|
||||
group.authgroup.group_leaders.add(user_1)
|
||||
|
||||
# user 2
|
||||
user_2 = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_main_character_2(
|
||||
@ -45,18 +35,25 @@ def create_testdata():
|
||||
name='Clark Kent',
|
||||
character_id=1002,
|
||||
corp_id=2002,
|
||||
corp_name='Wayne Technologies'
|
||||
corp_name='Wayne Food'
|
||||
)
|
||||
return group, user_1, user_2
|
||||
|
||||
# user 3
|
||||
user_3 = AuthUtils.create_user('Peter Parker')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_2,
|
||||
name='Peter Parker',
|
||||
character_id=1003,
|
||||
corp_id=2002,
|
||||
corp_name='Wayne Food'
|
||||
)
|
||||
return group, user_1, user_2, user_3
|
||||
|
||||
|
||||
class TestGroupRequest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, _ = create_testdata()
|
||||
cls.group, cls.user_1, cls.user_2, cls.user_3 = create_testdata()
|
||||
|
||||
def test_main_char(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
@ -74,13 +71,85 @@ class TestGroupRequest(TestCase):
|
||||
expected = 'Bruce Wayne:Superheros'
|
||||
self.assertEqual(str(group_request), expected)
|
||||
|
||||
@override_settings(GROUPMANAGEMENT_REQUESTS_NOTIFICATION=True)
|
||||
def test_should_notify_leaders_about_join_request(self):
|
||||
# given
|
||||
group_request = GroupRequest.objects.create(
|
||||
user=self.user_2, group=self.group
|
||||
)
|
||||
# when
|
||||
with mock.patch(MODULE_PATH + ".notify") as mock_notify:
|
||||
group_request.notify_leaders()
|
||||
# then
|
||||
self.assertTrue(mock_notify.called)
|
||||
_, kwargs = mock_notify.call_args
|
||||
self.assertEqual(kwargs["user"],self.user_1)
|
||||
|
||||
@override_settings(GROUPMANAGEMENT_REQUESTS_NOTIFICATION=True)
|
||||
def test_should_notify_leaders_about_leave_request(self):
|
||||
# given
|
||||
group_request = GroupRequest.objects.create(
|
||||
user=self.user_2, group=self.group
|
||||
)
|
||||
# when
|
||||
with mock.patch(MODULE_PATH + ".notify") as mock_notify:
|
||||
group_request.notify_leaders()
|
||||
# then
|
||||
self.assertTrue(mock_notify.called)
|
||||
|
||||
@override_settings(GROUPMANAGEMENT_REQUESTS_NOTIFICATION=True)
|
||||
def test_should_handle_notify_leaders_without_leaders(self):
|
||||
# given
|
||||
group = Group.objects.create(name='Dummy')
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
group_request = GroupRequest.objects.create(
|
||||
user=self.user_2, group=group
|
||||
)
|
||||
# when
|
||||
with mock.patch(MODULE_PATH + ".notify") as mock_notify:
|
||||
group_request.notify_leaders()
|
||||
# then
|
||||
self.assertFalse(mock_notify.called)
|
||||
|
||||
@override_settings(GROUPMANAGEMENT_REQUESTS_NOTIFICATION=False)
|
||||
def test_should_not_notify_leaders_if_disabled(self):
|
||||
# given
|
||||
group_request = GroupRequest.objects.create(
|
||||
user=self.user_2, group=self.group
|
||||
)
|
||||
# when
|
||||
with mock.patch(MODULE_PATH + ".notify") as mock_notify:
|
||||
group_request.notify_leaders()
|
||||
# then
|
||||
self.assertFalse(mock_notify.called)
|
||||
|
||||
@override_settings(GROUPMANAGEMENT_REQUESTS_NOTIFICATION=True)
|
||||
def test_should_notify_members_of_leader_groups_about_join_request(self):
|
||||
# given
|
||||
child_group = Group.objects.create(name='Child')
|
||||
child_group.authgroup.internal = False
|
||||
child_group.authgroup.hidden = False
|
||||
child_group.authgroup.save()
|
||||
child_group.authgroup.group_leader_groups.add(self.group)
|
||||
group_request = GroupRequest.objects.create(
|
||||
user=self.user_2, group=child_group
|
||||
)
|
||||
# when
|
||||
with mock.patch(MODULE_PATH + ".notify") as mock_notify:
|
||||
group_request.notify_leaders()
|
||||
# then
|
||||
self.assertTrue(mock_notify.called)
|
||||
_, kwargs = mock_notify.call_args
|
||||
self.assertEqual(kwargs["user"],self.user_1)
|
||||
|
||||
|
||||
class TestRequestLog(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, cls.user_2 = create_testdata()
|
||||
cls.group, cls.user_1, cls.user_2, _ = create_testdata()
|
||||
|
||||
def test_requestor(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
@ -126,7 +195,7 @@ class TestRequestLog(TestCase):
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = True
|
||||
action=True
|
||||
)
|
||||
expected = 'Accept'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
@ -136,7 +205,7 @@ class TestRequestLog(TestCase):
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
action=False
|
||||
)
|
||||
expected = 'Reject'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
@ -146,14 +215,13 @@ class TestRequestLog(TestCase):
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
action=False
|
||||
)
|
||||
expected = self.user_2.profile.main_character
|
||||
self.assertEqual(request_log.req_char(), expected)
|
||||
|
||||
|
||||
class TestAuthGroup(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
@ -163,3 +231,56 @@ class TestAuthGroup(TestCase):
|
||||
|
||||
expected = 'Superheros'
|
||||
self.assertEqual(str(group.authgroup), expected)
|
||||
|
||||
|
||||
class TestAuthGroupRequestApprovers(TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.group, self.user_1, self.user_2, self.user_3 = create_testdata()
|
||||
|
||||
def test_should_return_leaders_of_main_group_only(self):
|
||||
# when
|
||||
leaders = self.group.authgroup.group_request_approvers()
|
||||
# then
|
||||
self.assertSetEqual(leaders, {self.user_1})
|
||||
|
||||
def test_should_return_members_of_leading_groups_only(self):
|
||||
# given
|
||||
parent_group = Group.objects.create(name='Parent')
|
||||
parent_group.authgroup.group_leaders.add(self.user_2)
|
||||
self.user_1.groups.add(parent_group)
|
||||
child_group = Group.objects.create(name='Child')
|
||||
child_group.authgroup.internal = False
|
||||
child_group.authgroup.hidden = False
|
||||
child_group.authgroup.save()
|
||||
child_group.authgroup.group_leader_groups.add(parent_group)
|
||||
# when
|
||||
leaders = child_group.authgroup.group_request_approvers()
|
||||
# then
|
||||
self.assertSetEqual(leaders, {self.user_1})
|
||||
|
||||
def test_should_return_leaders_of_main_group_and_members_of_leading_groups(self):
|
||||
# given
|
||||
parent_group = Group.objects.create(name='Parent')
|
||||
parent_group.authgroup.group_leaders.add(self.user_2)
|
||||
self.user_1.groups.add(parent_group)
|
||||
child_group = Group.objects.create(name='Child')
|
||||
child_group.authgroup.internal = False
|
||||
child_group.authgroup.hidden = False
|
||||
child_group.authgroup.save()
|
||||
child_group.authgroup.group_leaders.add(self.user_3)
|
||||
child_group.authgroup.group_leader_groups.add(self.group)
|
||||
# when
|
||||
leaders = child_group.authgroup.group_request_approvers()
|
||||
# then
|
||||
self.assertSetEqual(leaders, {self.user_1, self.user_3})
|
||||
|
||||
def test_can_handle_group_without_leaders(self):
|
||||
# given
|
||||
child_group = Group.objects.create(name='Child')
|
||||
child_group.authgroup.internal = False
|
||||
child_group.authgroup.hidden = False
|
||||
child_group.authgroup.save()
|
||||
# when
|
||||
leaders = child_group.authgroup.group_request_approvers()
|
||||
# then
|
||||
self.assertSetEqual(leaders, set())
|
||||
|
@ -359,6 +359,7 @@ def group_request_add(request, group_id):
|
||||
grouprequest.leave_request = False
|
||||
grouprequest.save()
|
||||
logger.info(f"Created group request for user {request.user} to group {Group.objects.get(id=group_id)}")
|
||||
grouprequest.notify_leaders()
|
||||
messages.success(request, _('Applied to group %(group)s.') % {"group": group})
|
||||
return redirect("groupmanagement:groups")
|
||||
|
||||
@ -400,5 +401,6 @@ def group_request_leave(request, group_id):
|
||||
grouprequest.leave_request = True
|
||||
grouprequest.save()
|
||||
logger.info(f"Created group leave request for user {request.user} to group {Group.objects.get(id=group_id)}")
|
||||
grouprequest.notify_leaders()
|
||||
messages.success(request, _('Applied to leave group %(group)s.') % {"group": group})
|
||||
return redirect("groupmanagement:groups")
|
||||
|
@ -35,6 +35,19 @@ Group leaders have the same abilities as users with the `group_management` permi
|
||||
|
||||
This allows you to more finely control who has access to manage which groups.
|
||||
|
||||
## Settings
|
||||
|
||||
Here is a list of available settings for Group Management. They can be configured by adding them to your AA settings file (``local.py``).
|
||||
Note that all settings are optional and the app will use the documented default settings if they are not used.
|
||||
|
||||
```eval_rst
|
||||
+---------------------------------------------+---------------------------------------------------------------------------+------------+
|
||||
| Name | Description | Default |
|
||||
+=============================================+===========================================================================+============+
|
||||
| ``GROUPMANAGEMENT_REQUESTS_NOTIFICATION`` | Send Auth notifications to all group leaders for join and leave requests. | ``False`` |
|
||||
+---------------------------------------------+---------------------------------------------------------------------------+------------+
|
||||
```
|
||||
|
||||
## Permissions
|
||||
|
||||
Group Management should be mostly done using group leaders, a series of permissions are included below for thoroughness.
|
||||
|
@ -4,12 +4,12 @@ sphinx_rtd_theme==0.5.0
|
||||
recommonmark==0.6.0
|
||||
|
||||
# Autodoc dependencies
|
||||
django>=3.1.1,<4.0.0
|
||||
django>=3.2,<4.0.0
|
||||
django-celery-beat>=2.0.0
|
||||
django-bootstrap-form
|
||||
django-sortedm2m
|
||||
django-esi>=1.5,<3.0
|
||||
celery>=4.3.0,<5.0.0,!=4.4.4
|
||||
django-esi>=3,<4
|
||||
celery>5,<6
|
||||
celery_once
|
||||
passlib
|
||||
redis>=3.3.1,<4.0.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user