This commit is contained in:
Ariel Rin 2021-12-28 21:58:38 +10:00
commit ff610efc84
20 changed files with 255 additions and 106 deletions

View File

@ -76,6 +76,27 @@ test-3.10-core:
reports:
cobertura: coverage.xml
test-3.11-core:
<<: *only-default
image: python:3.11-rc-bullseye
script:
- tox -e py311-core
artifacts:
when: always
reports:
cobertura: coverage.xml
allow_failure: true
test-3.7-all:
<<: *only-default
image: python:3.7-bullseye
script:
- tox -e py37-all
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.8-all:
<<: *only-default
image: python:3.8-bullseye
@ -106,6 +127,17 @@ test-3.10-all:
reports:
cobertura: coverage.xml
test-3.11-all:
<<: *only-default
image: python:3.11-rc-bullseye
script:
- tox -e py311-all
artifacts:
when: always
reports:
cobertura: coverage.xml
allow_failure: true
deploy_production:
stage: deploy
image: python:3.10-bullseye

View File

@ -13,17 +13,18 @@ from allianceauth import __version__
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'swagger.json'
)
"""
Swagger spec operations:
get_alliances_alliance_id
get_alliances_alliance_id_corporations
get_corporations_corporation_id
get_characters_character_id
get_universe_types_type_id
post_character_affiliation
get_universe_factions
"""
# for the love of Bob please add operations you use here. I'm tired of breaking undocumented things.
ESI_OPERATIONS=[
'get_alliances_alliance_id',
'get_alliances_alliance_id_corporations',
'get_corporations_corporation_id',
'get_characters_character_id',
'post_characters_affiliation',
'get_universe_types_type_id',
'get_universe_factions',
'post_universe_names',
]
logger = logging.getLogger(__name__)

File diff suppressed because one or more lines are too long

View File

@ -49,7 +49,6 @@ def run_model_update():
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
#update existing character models if required
# update existing character models
character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
for character_ids_chunk in chunks(character_ids, CHUNK_SIZE):

View File

@ -29,15 +29,18 @@
{% endif %}
</a>
</li>
<li>
<a data-toggle="tab" href="#leave">
{% translate "Leave Requests" %}
{% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>
{% if not auto_leave %}
<li>
<a data-toggle="tab" href="#leave">
{% translate "Leave Requests" %}
{% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>
{% endif %}
</ul>
<div class="panel panel-default panel-tabs-aa">
@ -100,61 +103,63 @@
{% endif %}
</div>
<div id="leave" class="tab-pane">
{% if leaverequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th>{% translate "Group" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for leaverequest in leaverequests %}
{% if not auto_leave %}
<div id="leave" class="tab-pane">
{% if leaverequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<td>
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
{% else %}
{{ leaverequest.user.username }}
{% endif %}
</td>
<td>
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a><br>
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td>{{ leaverequest.group.name }}</td>
<td class="text-right">
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
{% translate "Accept" %}
</a>
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
{% translate "Reject" %}
</a>
</td>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th>{% translate "Group" %}</th>
<th></th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
{% endif %}
</div>
</thead>
<tbody>
{% for leaverequest in leaverequests %}
<tr>
<td>
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
{% else %}
{{ leaverequest.user.username }}
{% endif %}
</td>
<td>
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a><br>
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td>{{ leaverequest.group.name }}</td>
<td class="text-right">
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
{% translate "Accept" %}
</a>
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
{% translate "Reject" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, TestCase, override_settings
from django.urls import reverse
from allianceauth.tests.auth_utils import AuthUtils
@ -6,14 +6,80 @@ from allianceauth.tests.auth_utils import AuthUtils
from .. import views
def response_content_to_str(response) -> str:
return response.content.decode(response.charset)
class TestViews(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = AuthUtils.create_user('Bruce Wayne')
self.user = AuthUtils.create_user('Peter Parker')
self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne')
# set permissions
AuthUtils.add_permission_to_user_by_name(
'auth.group_management', self.user_with_manage_permission
)
def test_groups_view_can_load(self):
request = self.factory.get(reverse('groupmanagement:groups'))
request.user = self.user
response = views.groups_view(request)
self.assertEqual(response.status_code, 200)
def test_management_view_can_load_for_user_with_permissions(self):
"""
Test if user with management permissions can access the management view
:return:
"""
request = self.factory.get(reverse('groupmanagement:management'))
request.user = self.user_with_manage_permission
response = views.group_management(request)
self.assertEqual(response.status_code, 200)
def test_management_view_doesnt_load_for_user_without_permissions(self):
"""
Test if user without management permissions can't access the management view
:return:
"""
request = self.factory.get(reverse('groupmanagement:management'))
request.user = self.user
response = views.group_management(request)
self.assertEqual(response.status_code, 302)
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=False)
def test_leave_requests_tab_visible(self):
"""
Test if the leave requests tab is visible when GROUPMANAGEMENT_AUTO_LEAVE = False
:return:
"""
request = self.factory.get(reverse('groupmanagement:management'))
request.user = self.user_with_manage_permission
response = views.group_management(request)
content = response_content_to_str(response)
self.assertEqual(response.status_code, 200)
self.assertIn('<a data-toggle="tab" href="#leave">', content)
self.assertIn('<div id="leave" class="tab-pane">', content)
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
def test_leave_requests_tab_hidden(self):
"""
Test if the leave requests tab is hidden when GROUPMANAGEMENT_AUTO_LEAVE = True
:return:
"""
request = self.factory.get(reverse('groupmanagement:management'))
request.user = self.user_with_manage_permission
response = views.group_management(request)
content = response_content_to_str(response)
self.assertEqual(response.status_code, 200)
self.assertNotIn('<a data-toggle="tab" href="#leave">', content)
self.assertNotIn('<div id="leave" class="tab-pane">', content)

View File

@ -45,7 +45,11 @@ def group_management(request):
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
request.user, len(acceptrequests), len(leaverequests)))
render_items = {'acceptrequests': acceptrequests, 'leaverequests': leaverequests}
render_items = {
'acceptrequests': acceptrequests,
'leaverequests': leaverequests,
'auto_leave': getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False),
}
return render(request, 'groupmanagement/index.html', context=render_items)

View File

@ -49,19 +49,22 @@ class NotificationManager(models.Manager):
logger.info("Created notification %s", obj)
return obj
def _max_notifications_per_user(self):
"""return the maximum number of notifications allowed per user"""
max_notifications = getattr(settings, 'NOTIFICATIONS_MAX_PER_USER', None)
if (
max_notifications is None
or not isinstance(max_notifications, int)
or max_notifications < 0
):
def _max_notifications_per_user(self) -> int:
"""Maximum number of notifications allowed per user."""
max_notifications = getattr(
settings,
"NOTIFICATIONS_MAX_PER_USER",
self.model.NOTIFICATIONS_MAX_PER_USER_DEFAULT
)
try:
max_notifications = int(max_notifications)
except ValueError:
max_notifications = None
if max_notifications is None or max_notifications < 0:
logger.warning(
'NOTIFICATIONS_MAX_PER_USER setting is invalid. Using default.'
"NOTIFICATIONS_MAX_PER_USER setting is invalid. Using default."
)
max_notifications = self.model.NOTIFICATIONS_MAX_PER_USER_DEFAULT
return max_notifications
def user_unread_count(self, user_pk: int) -> int:

View File

@ -1,5 +1,6 @@
from unittest.mock import patch
from django.conf import settings
from django.contrib.auth.models import User
from django.test import TestCase, override_settings
@ -113,29 +114,53 @@ class TestUserNotify(TestCase):
self.assertSetEqual(result, expected)
@patch("allianceauth.notifications.managers.logger")
@patch(
MODULE_PATH + '.Notification.NOTIFICATIONS_MAX_PER_USER_DEFAULT',
MODULE_PATH + ".Notification.NOTIFICATIONS_MAX_PER_USER_DEFAULT",
NOTIFICATIONS_MAX_PER_USER_DEFAULT
)
class TestMaxNotificationsPerUser(TestCase):
@override_settings(NOTIFICATIONS_MAX_PER_USER=None)
def test_reset_to_default_if_not_defined(self):
@override_settings(NOTIFICATIONS_MAX_PER_USER=42)
def test_should_use_custom_integer_setting(self, mock_logger):
# when
result = Notification.objects._max_notifications_per_user()
expected = NOTIFICATIONS_MAX_PER_USER_DEFAULT
self.assertEqual(result, expected)
# then
self.assertEqual(result, 42)
self.assertFalse(mock_logger.warning.called)
@override_settings(NOTIFICATIONS_MAX_PER_USER='11')
def test_reset_to_default_if_not_int(self):
@override_settings(NOTIFICATIONS_MAX_PER_USER="42")
def test_should_use_custom_string_setting(self, mock_logger):
# when
result = Notification.objects._max_notifications_per_user()
expected = NOTIFICATIONS_MAX_PER_USER_DEFAULT
self.assertEqual(result, expected)
# then
self.assertEqual(result, 42)
self.assertFalse(mock_logger.warning.called)
@override_settings()
def test_should_use_default_if_not_defined(self, mock_logger):
# given
del settings.NOTIFICATIONS_MAX_PER_USER
# when
result = Notification.objects._max_notifications_per_user()
# then
self.assertEqual(result, NOTIFICATIONS_MAX_PER_USER_DEFAULT)
self.assertFalse(mock_logger.warning.called)
@override_settings(NOTIFICATIONS_MAX_PER_USER="abc")
def test_should_reset_to_default_if_not_int(self, mock_logger):
# when
result = Notification.objects._max_notifications_per_user()
# then
self.assertEqual(result, NOTIFICATIONS_MAX_PER_USER_DEFAULT)
self.assertTrue(mock_logger.warning.called)
@override_settings(NOTIFICATIONS_MAX_PER_USER=-1)
def test_reset_to_default_if_lt_zero(self):
def test_should_reset_to_default_if_lt_zero(self, mock_logger):
# when
result = Notification.objects._max_notifications_per_user()
expected = NOTIFICATIONS_MAX_PER_USER_DEFAULT
self.assertEqual(result, expected)
# then
self.assertEqual(result, NOTIFICATIONS_MAX_PER_USER_DEFAULT)
self.assertTrue(mock_logger.warning.called)
@patch('allianceauth.notifications.managers.cache')

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,5 @@
<!-- Start jQuery UI CSS from cdnjs -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" integrity="sha512-aOG0c6nPNzGk+5zjwyJaoRUgCdOrfSDhmMID2u4+OIslr0GjpLKo7Xm0Ao3xmpM4T8AmIouRkqwj1nrdVsLKEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- End jQuery UI CSS from cdnjs -->
{% load static %}
<!-- Start jQuery UI CSS from Alliance Auth -->
<!-- CDNs all contain theme.css, which is not supposed to be in the base CSS, Which is why this is uniquely bundled in not using a CDN -->
<link rel="stylesheet" href="{% static 'js/jquery-ui/1.12.1/css/jquery-ui.min.css' %}" integrity="sha512-7smZe1765O+Mm1UZH46SzaFClbRX7dEs01lB9lqU91oocmugWWfQXVQNVr5tEwktYSqwJMErEfr4GvflXMgTPA==" crossorigin="anonymous" referrerpolicy="no-referrer"/>
<!-- End jQuery UI CSS from aa-gdpr -->

View File

@ -52,7 +52,7 @@ services:
- auth_mysql
grafana:
image: grafana/grafana:8.2
image: grafana/grafana-oss:8.2
restart: always
depends_on:
- auth_mysql

View File

@ -90,11 +90,16 @@ This allows you to more finely control who has access to manage which groups.
### Auto Leave
By default in AA, Both requests and leaves for non-open groups must be approved by a group manager. If you wish to allow users to leave groups without requiring approvals, add the following lines to your `local.py`
By default, in AA both requests and leaves for non-open groups must be approved by a group manager. If you wish to allow users to leave groups without requiring approvals, add the following lines to your `local.py`
```python
## Allows users to freely leave groups without requiring approval.
AUTO_LEAVE = True
GROUPMANAGEMENT_AUTO_LEAVE = True
```
```eval_rst
.. note::
Before you set `GROUPMANAGEMENT_AUTO_LEAVE = True`, make sure there are no pending leave requests, as this option will hide the "Leave Requests" tab.
```
## Settings

View File

@ -1,7 +1,7 @@
[tox]
skipsdist = true
usedevelop = true
envlist = py{38,39,310}-{all,core}
envlist = py{38,39,310,311}-{all,core}
[testenv]
setenv =
@ -12,6 +12,7 @@ basepython =
py38: python3.8
py39: python3.9
py310: python3.10
py311: python3.11
deps=
coverage
install_command = pip install -e ".[testing]" -U {opts} {packages}