mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b667892698 | ||
|
|
dc11add0e9 | ||
|
|
cb429a0b88 | ||
|
|
b51039cfc0 | ||
|
|
eadd959d95 | ||
|
|
1856e03d88 | ||
|
|
7dcfa622a3 | ||
|
|
64251b9b3c | ||
|
|
6b073dd5fc | ||
|
|
0911fabfb2 | ||
|
|
050d3f5e63 | ||
|
|
bbe3f78ad1 | ||
|
|
8204c18895 | ||
|
|
b91c788897 | ||
|
|
1d20a3029f | ||
|
|
9cfebc9ae3 | ||
|
|
ddabb4539b | ||
|
|
ada35e221b | ||
|
|
6fef9d904e | ||
|
|
67cf2b5904 | ||
|
|
3bebe792f6 | ||
|
|
00b4d89181 | ||
|
|
f729c6b650 | ||
|
|
df95f8c3f3 | ||
|
|
fe36e57d72 | ||
|
|
31197812b6 | ||
|
|
bd3fe01a12 | ||
|
|
39f7f32b7d | ||
|
|
b4522a1277 | ||
|
|
bb6a7e8327 | ||
|
|
9bd42a7579 | ||
|
|
b41430e5a3 | ||
|
|
595353e838 | ||
|
|
f1a21bb856 | ||
|
|
e44c2935f9 | ||
|
|
4d546f948d | ||
|
|
3bab349d7b | ||
|
|
eef6126ef8 | ||
|
|
5c7478fa39 | ||
|
|
64b72d0b06 | ||
|
|
b266a98b25 | ||
|
|
8a27de5df8 | ||
|
|
f9b5310fce | ||
|
|
fdce173969 | ||
|
|
7b9ddf90c1 | ||
|
|
580c8c19de | ||
|
|
55cc77140e | ||
|
|
93c89dd7cc | ||
|
|
c970cbbd2d |
@@ -1,7 +1,7 @@
|
|||||||
# This will make sure the app is always imported when
|
# This will make sure the app is always imported when
|
||||||
# Django starts so that shared_task will use this app.
|
# Django starts so that shared_task will use this app.
|
||||||
|
|
||||||
__version__ = '2.7.2'
|
__version__ = '2.7.5'
|
||||||
__title__ = 'Alliance Auth'
|
__title__ = 'Alliance Auth'
|
||||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||||
NAME = '%s v%s' % (__title__, __version__)
|
NAME = '%s v%s' % (__title__, __version__)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from django.contrib.auth.backends import ModelBackend
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from django.contrib.auth.models import User, Permission
|
||||||
|
|
||||||
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||||
|
|
||||||
|
|
||||||
@@ -11,9 +12,11 @@ logger = logging.getLogger(__name__)
|
|||||||
class StateBackend(ModelBackend):
|
class StateBackend(ModelBackend):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_state_permissions(user_obj):
|
def _get_state_permissions(user_obj):
|
||||||
profile_state_field = UserProfile._meta.get_field('state')
|
"""returns permissions for state of given user object"""
|
||||||
user_state_query = 'state__%s__user' % profile_state_field.related_query_name()
|
if hasattr(user_obj, "profile") and user_obj.profile:
|
||||||
return Permission.objects.filter(**{user_state_query: user_obj})
|
return Permission.objects.filter(state=user_obj.profile.state)
|
||||||
|
else:
|
||||||
|
return Permission.objects.none()
|
||||||
|
|
||||||
def get_state_permissions(self, user_obj, obj=None):
|
def get_state_permissions(self, user_obj, obj=None):
|
||||||
return self._get_permissions(user_obj, obj, 'state')
|
return self._get_permissions(user_obj, obj, 'state')
|
||||||
|
|||||||
@@ -73,11 +73,17 @@ class UserProfile(models.Model):
|
|||||||
if commit:
|
if commit:
|
||||||
logger.info('Updating {} state to {}'.format(self.user, self.state))
|
logger.info('Updating {} state to {}'.format(self.user, self.state))
|
||||||
self.save(update_fields=['state'])
|
self.save(update_fields=['state'])
|
||||||
notify(self.user, _('State Changed'),
|
notify(
|
||||||
_('Your user state has been changed to %(state)s') % ({'state': state}),
|
self.user,
|
||||||
'info')
|
_('State changed to: %s' % state),
|
||||||
|
_('Your user\'s state is now: %(state)s')
|
||||||
|
% ({'state': state}),
|
||||||
|
'info'
|
||||||
|
)
|
||||||
from allianceauth.authentication.signals import state_changed
|
from allianceauth.authentication.signals import state_changed
|
||||||
state_changed.send(sender=self.__class__, user=self.user, state=self.state)
|
state_changed.send(
|
||||||
|
sender=self.__class__, user=self.user, state=self.state
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.user)
|
return str(self.user)
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ def trigger_state_check(state):
|
|||||||
check_states = State.objects.filter(priority__lt=state.priority)
|
check_states = State.objects.filter(priority__lt=state.priority)
|
||||||
for profile in UserProfile.objects.filter(state__in=check_states):
|
for profile in UserProfile.objects.filter(state__in=check_states):
|
||||||
if state.available_to_user(profile.user):
|
if state.available_to_user(profile.user):
|
||||||
profile.state = state
|
profile.assign_state(state)
|
||||||
profile.save(update_fields=['state'])
|
|
||||||
state_changed.send(sender=state.__class__, user=profile.user, state=state)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=State.member_characters.through)
|
@receiver(m2m_changed, sender=State.member_characters.through)
|
||||||
|
|||||||
@@ -14,7 +14,11 @@
|
|||||||
<div class="col-sm-6 text-center">
|
<div class="col-sm-6 text-center">
|
||||||
<div class="panel panel-primary" style="height:100%">
|
<div class="panel panel-primary" style="height:100%">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">{% trans "Main Character" %}</h3>
|
<h3 class="panel-title">
|
||||||
|
{% blocktrans with state=request.user.profile.state %}
|
||||||
|
Main Character (State: {{ state }})
|
||||||
|
{% endblocktrans %}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% if request.user.profile.main_character %}
|
{% if request.user.profile.main_character %}
|
||||||
|
|||||||
149
allianceauth/authentication/tests/test_backend.py
Normal file
149
allianceauth/authentication/tests/test_backend.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
from esi.models import Token
|
||||||
|
|
||||||
|
from ..backends import StateBackend
|
||||||
|
from ..models import CharacterOwnership, UserProfile, OwnershipRecord
|
||||||
|
|
||||||
|
MODULE_PATH = 'allianceauth.authentication'
|
||||||
|
|
||||||
|
PERMISSION_1 = "authentication.add_user"
|
||||||
|
PERMISSION_2 = "authentication.change_user"
|
||||||
|
|
||||||
|
|
||||||
|
class TestStatePermissions(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# permissions
|
||||||
|
self.permission_1 = AuthUtils.get_permission_by_name(PERMISSION_1)
|
||||||
|
self.permission_2 = AuthUtils.get_permission_by_name(PERMISSION_2)
|
||||||
|
|
||||||
|
# group
|
||||||
|
self.group_1 = Group.objects.create(name="Group 1")
|
||||||
|
self.group_2 = Group.objects.create(name="Group 2")
|
||||||
|
|
||||||
|
# state
|
||||||
|
self.state_1 = AuthUtils.get_member_state()
|
||||||
|
self.state_2 = AuthUtils.create_state("Other State", 75)
|
||||||
|
|
||||||
|
# user
|
||||||
|
self.user = AuthUtils.create_user("Bruce Wayne")
|
||||||
|
self.main = AuthUtils.add_main_character_2(self.user, self.user.username, 123)
|
||||||
|
|
||||||
|
def test_user_has_user_permissions(self):
|
||||||
|
self.user.user_permissions.add(self.permission_1)
|
||||||
|
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertTrue(user.has_perm(PERMISSION_1))
|
||||||
|
|
||||||
|
def test_user_has_group_permissions(self):
|
||||||
|
self.group_1.permissions.add(self.permission_1)
|
||||||
|
self.user.groups.add(self.group_1)
|
||||||
|
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertTrue(user.has_perm(PERMISSION_1))
|
||||||
|
|
||||||
|
def test_user_has_state_permissions(self):
|
||||||
|
self.state_1.permissions.add(self.permission_1)
|
||||||
|
self.state_1.member_characters.add(self.main)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
|
||||||
|
self.assertTrue(user.has_perm(PERMISSION_1))
|
||||||
|
|
||||||
|
def test_when_user_changes_state_perms_change_accordingly(self):
|
||||||
|
self.state_1.permissions.add(self.permission_1)
|
||||||
|
self.state_1.member_characters.add(self.main)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertTrue(user.has_perm(PERMISSION_1))
|
||||||
|
|
||||||
|
self.state_2.permissions.add(self.permission_2)
|
||||||
|
self.state_2.member_characters.add(self.main)
|
||||||
|
self.state_1.member_characters.remove(self.main)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertFalse(user.has_perm(PERMISSION_1))
|
||||||
|
self.assertTrue(user.has_perm(PERMISSION_2))
|
||||||
|
|
||||||
|
def test_state_permissions_are_returned_for_current_user_object(self):
|
||||||
|
# verify state permissions are returns for the current user object
|
||||||
|
# and not for it's instance in the database, which might be outdated
|
||||||
|
self.state_1.permissions.add(self.permission_1)
|
||||||
|
self.state_2.permissions.add(self.permission_2)
|
||||||
|
self.state_1.member_characters.add(self.main)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
user.profile.state = self.state_2
|
||||||
|
self.assertFalse(user.has_perm(PERMISSION_1))
|
||||||
|
self.assertTrue(user.has_perm(PERMISSION_2))
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuthenticate(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.main_character = EveCharacter.objects.create(
|
||||||
|
character_id=1,
|
||||||
|
character_name='Main Character',
|
||||||
|
corporation_id=1,
|
||||||
|
corporation_name='Corp',
|
||||||
|
corporation_ticker='CORP',
|
||||||
|
)
|
||||||
|
cls.alt_character = EveCharacter.objects.create(
|
||||||
|
character_id=2,
|
||||||
|
character_name='Alt Character',
|
||||||
|
corporation_id=1,
|
||||||
|
corporation_name='Corp',
|
||||||
|
corporation_ticker='CORP',
|
||||||
|
)
|
||||||
|
cls.unclaimed_character = EveCharacter.objects.create(
|
||||||
|
character_id=3,
|
||||||
|
character_name='Unclaimed Character',
|
||||||
|
corporation_id=1,
|
||||||
|
corporation_name='Corp',
|
||||||
|
corporation_ticker='CORP',
|
||||||
|
)
|
||||||
|
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
|
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
|
||||||
|
AuthUtils.disconnect_signals()
|
||||||
|
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
||||||
|
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
||||||
|
UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character})
|
||||||
|
AuthUtils.connect_signals()
|
||||||
|
|
||||||
|
def test_authenticate_main_character(self):
|
||||||
|
t = Token(character_id=self.main_character.character_id, character_owner_hash='1')
|
||||||
|
user = StateBackend().authenticate(token=t)
|
||||||
|
self.assertEquals(user, self.user)
|
||||||
|
|
||||||
|
def test_authenticate_alt_character(self):
|
||||||
|
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||||
|
user = StateBackend().authenticate(token=t)
|
||||||
|
self.assertEquals(user, self.user)
|
||||||
|
|
||||||
|
def test_authenticate_unclaimed_character(self):
|
||||||
|
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||||
|
user = StateBackend().authenticate(token=t)
|
||||||
|
self.assertNotEqual(user, self.user)
|
||||||
|
self.assertEqual(user.username, 'Unclaimed_Character')
|
||||||
|
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||||
|
|
||||||
|
def test_authenticate_character_record(self):
|
||||||
|
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||||
|
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||||
|
user = StateBackend().authenticate(token=t)
|
||||||
|
self.assertEqual(user, self.old_user)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||||
|
self.assertTrue(user.profile.main_character)
|
||||||
|
|
||||||
|
def test_iterate_username(self):
|
||||||
|
t = Token(character_id=self.unclaimed_character.character_id,
|
||||||
|
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||||
|
username = StateBackend().authenticate(token=t).username
|
||||||
|
t.character_owner_hash = '4'
|
||||||
|
username_1 = StateBackend().authenticate(token=t).username
|
||||||
|
t.character_owner_hash = '5'
|
||||||
|
username_2 = StateBackend().authenticate(token=t).username
|
||||||
|
self.assertNotEqual(username, username_1, username_2)
|
||||||
|
self.assertTrue(username_1.endswith('_1'))
|
||||||
|
self.assertTrue(username_2.endswith('_2'))
|
||||||
35
allianceauth/authentication/tests/test_commands.py
Normal file
35
allianceauth/authentication/tests/test_commands.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
from ..models import CharacterOwnership, UserProfile
|
||||||
|
|
||||||
|
|
||||||
|
class ManagementCommandTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
|
||||||
|
character = UserProfile.objects.get(user=cls.user).main_character
|
||||||
|
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.stdout = StringIO()
|
||||||
|
|
||||||
|
def test_ownership(self):
|
||||||
|
call_command('checkmains', stdout=self.stdout)
|
||||||
|
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||||
|
self.assertNotIn(self.user.username, self.stdout.getvalue())
|
||||||
|
self.assertIn('All main characters', self.stdout.getvalue())
|
||||||
|
|
||||||
|
def test_no_ownership(self):
|
||||||
|
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
|
||||||
|
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||||
|
|
||||||
|
call_command('checkmains', stdout=self.stdout)
|
||||||
|
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
|
||||||
|
self.assertIn(user.username, self.stdout.getvalue())
|
||||||
68
allianceauth/authentication/tests/test_decorators.py
Normal file
68
allianceauth/authentication/tests/test_decorators.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from unittest import mock
|
||||||
|
from urllib import parse
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
from django.http.response import HttpResponse
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
from ..decorators import main_character_required
|
||||||
|
from ..models import CharacterOwnership
|
||||||
|
|
||||||
|
|
||||||
|
MODULE_PATH = 'allianceauth.authentication'
|
||||||
|
|
||||||
|
|
||||||
|
class DecoratorTestCase(TestCase):
|
||||||
|
@staticmethod
|
||||||
|
@main_character_required
|
||||||
|
def dummy_view(*args, **kwargs):
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
|
||||||
|
cls.no_main_user = AuthUtils.create_user(
|
||||||
|
'no_main_user', disconnect_signals=True
|
||||||
|
)
|
||||||
|
main_character = EveCharacter.objects.create(
|
||||||
|
character_id=1,
|
||||||
|
character_name='Main Character',
|
||||||
|
corporation_id=1,
|
||||||
|
corporation_name='Corp',
|
||||||
|
corporation_ticker='CORP',
|
||||||
|
)
|
||||||
|
CharacterOwnership.objects.create(
|
||||||
|
user=cls.main_user, character=main_character, owner_hash='1'
|
||||||
|
)
|
||||||
|
cls.main_user.profile.main_character = main_character
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.request = RequestFactory().get('/test/')
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||||
|
def test_login_redirect(self, m):
|
||||||
|
setattr(self.request, 'user', AnonymousUser())
|
||||||
|
response = self.dummy_view(self.request)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
url = getattr(response, 'url', None)
|
||||||
|
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||||
|
def test_main_character_redirect(self, m):
|
||||||
|
setattr(self.request, 'user', self.no_main_user)
|
||||||
|
response = self.dummy_view(self.request)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
url = getattr(response, 'url', None)
|
||||||
|
self.assertEqual(url, reverse('authentication:dashboard'))
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||||
|
def test_successful_request(self, m):
|
||||||
|
setattr(self.request, 'user', self.main_user)
|
||||||
|
response = self.dummy_view(self.request)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
@@ -1,147 +1,20 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
from io import StringIO
|
|
||||||
from urllib import parse
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.models import AnonymousUser, User
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.http.response import HttpResponse
|
|
||||||
from django.shortcuts import reverse
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
|
|
||||||
|
|
||||||
from allianceauth.authentication.decorators import main_character_required
|
|
||||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
|
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
|
||||||
EveAllianceInfo
|
EveAllianceInfo
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
from esi.errors import IncompleteResponseError
|
from esi.errors import IncompleteResponseError
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
|
||||||
from ..backends import StateBackend
|
from ..models import CharacterOwnership, State, get_guest_state
|
||||||
from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\
|
|
||||||
OwnershipRecord
|
|
||||||
from ..tasks import check_character_ownership
|
from ..tasks import check_character_ownership
|
||||||
|
|
||||||
MODULE_PATH = 'allianceauth.authentication'
|
MODULE_PATH = 'allianceauth.authentication'
|
||||||
|
|
||||||
|
|
||||||
class DecoratorTestCase(TestCase):
|
|
||||||
@staticmethod
|
|
||||||
@main_character_required
|
|
||||||
def dummy_view(*args, **kwargs):
|
|
||||||
return HttpResponse(status=200)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
|
|
||||||
cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True)
|
|
||||||
main_character = EveCharacter.objects.create(
|
|
||||||
character_id=1,
|
|
||||||
character_name='Main Character',
|
|
||||||
corporation_id=1,
|
|
||||||
corporation_name='Corp',
|
|
||||||
corporation_ticker='CORP',
|
|
||||||
)
|
|
||||||
CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1')
|
|
||||||
cls.main_user.profile.main_character = main_character
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.request = RequestFactory().get('/test/')
|
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
|
||||||
def test_login_redirect(self, m):
|
|
||||||
setattr(self.request, 'user', AnonymousUser())
|
|
||||||
response = self.dummy_view(self.request)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
url = getattr(response, 'url', None)
|
|
||||||
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
|
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
|
||||||
def test_main_character_redirect(self, m):
|
|
||||||
setattr(self.request, 'user', self.no_main_user)
|
|
||||||
response = self.dummy_view(self.request)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
url = getattr(response, 'url', None)
|
|
||||||
self.assertEqual(url, reverse('authentication:dashboard'))
|
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
|
||||||
def test_successful_request(self, m):
|
|
||||||
setattr(self.request, 'user', self.main_user)
|
|
||||||
response = self.dummy_view(self.request)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
|
|
||||||
class BackendTestCase(TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.main_character = EveCharacter.objects.create(
|
|
||||||
character_id=1,
|
|
||||||
character_name='Main Character',
|
|
||||||
corporation_id=1,
|
|
||||||
corporation_name='Corp',
|
|
||||||
corporation_ticker='CORP',
|
|
||||||
)
|
|
||||||
cls.alt_character = EveCharacter.objects.create(
|
|
||||||
character_id=2,
|
|
||||||
character_name='Alt Character',
|
|
||||||
corporation_id=1,
|
|
||||||
corporation_name='Corp',
|
|
||||||
corporation_ticker='CORP',
|
|
||||||
)
|
|
||||||
cls.unclaimed_character = EveCharacter.objects.create(
|
|
||||||
character_id=3,
|
|
||||||
character_name='Unclaimed Character',
|
|
||||||
corporation_id=1,
|
|
||||||
corporation_name='Corp',
|
|
||||||
corporation_ticker='CORP',
|
|
||||||
)
|
|
||||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
|
||||||
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
|
|
||||||
AuthUtils.disconnect_signals()
|
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
|
||||||
UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character})
|
|
||||||
AuthUtils.connect_signals()
|
|
||||||
|
|
||||||
def test_authenticate_main_character(self):
|
|
||||||
t = Token(character_id=self.main_character.character_id, character_owner_hash='1')
|
|
||||||
user = StateBackend().authenticate(token=t)
|
|
||||||
self.assertEquals(user, self.user)
|
|
||||||
|
|
||||||
def test_authenticate_alt_character(self):
|
|
||||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
|
||||||
user = StateBackend().authenticate(token=t)
|
|
||||||
self.assertEquals(user, self.user)
|
|
||||||
|
|
||||||
def test_authenticate_unclaimed_character(self):
|
|
||||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
|
||||||
user = StateBackend().authenticate(token=t)
|
|
||||||
self.assertNotEqual(user, self.user)
|
|
||||||
self.assertEqual(user.username, 'Unclaimed_Character')
|
|
||||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
|
||||||
|
|
||||||
def test_authenticate_character_record(self):
|
|
||||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
|
||||||
record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
|
||||||
user = StateBackend().authenticate(token=t)
|
|
||||||
self.assertEqual(user, self.old_user)
|
|
||||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
|
||||||
self.assertTrue(user.profile.main_character)
|
|
||||||
|
|
||||||
def test_iterate_username(self):
|
|
||||||
t = Token(character_id=self.unclaimed_character.character_id,
|
|
||||||
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
|
||||||
username = StateBackend().authenticate(token=t).username
|
|
||||||
t.character_owner_hash = '4'
|
|
||||||
username_1 = StateBackend().authenticate(token=t).username
|
|
||||||
t.character_owner_hash = '5'
|
|
||||||
username_2 = StateBackend().authenticate(token=t).username
|
|
||||||
self.assertNotEqual(username, username_1, username_2)
|
|
||||||
self.assertTrue(username_1.endswith('_1'))
|
|
||||||
self.assertTrue(username_2.endswith('_2'))
|
|
||||||
|
|
||||||
|
|
||||||
class CharacterOwnershipTestCase(TestCase):
|
class CharacterOwnershipTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@@ -378,30 +251,3 @@ class CharacterOwnershipCheckTestCase(TestCase):
|
|||||||
filter.return_value.exists.return_value = False
|
filter.return_value.exists.return_value = False
|
||||||
check_character_ownership(self.ownership)
|
check_character_ownership(self.ownership)
|
||||||
self.assertTrue(filter.return_value.delete.called)
|
self.assertTrue(filter.return_value.delete.called)
|
||||||
|
|
||||||
|
|
||||||
class ManagementCommandTestCase(TestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
|
|
||||||
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
|
|
||||||
character = UserProfile.objects.get(user=cls.user).main_character
|
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.stdout = StringIO()
|
|
||||||
|
|
||||||
def test_ownership(self):
|
|
||||||
call_command('checkmains', stdout=self.stdout)
|
|
||||||
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
|
||||||
self.assertNotIn(self.user.username, self.stdout.getvalue())
|
|
||||||
self.assertIn('All main characters', self.stdout.getvalue())
|
|
||||||
|
|
||||||
def test_no_ownership(self):
|
|
||||||
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
|
|
||||||
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
|
|
||||||
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
|
||||||
|
|
||||||
call_command('checkmains', stdout=self.stdout)
|
|
||||||
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
|
|
||||||
self.assertIn(user.username, self.stdout.getvalue())
|
|
||||||
@@ -83,9 +83,8 @@ class TestStatusOverviewTag(TestCase):
|
|||||||
}
|
}
|
||||||
mock_current_version_info.return_value = version_info
|
mock_current_version_info.return_value = version_info
|
||||||
mock_fetch_celery_queue_length.return_value = 3
|
mock_fetch_celery_queue_length.return_value = 3
|
||||||
|
|
||||||
context = {}
|
result = status_overview()
|
||||||
result = status_overview(context)
|
|
||||||
expected = {
|
expected = {
|
||||||
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
|
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
|
||||||
'latest_major': True,
|
'latest_major': True,
|
||||||
@@ -128,6 +127,13 @@ class TestNotifications(TestCase):
|
|||||||
result = _current_notifications()
|
result = _current_notifications()
|
||||||
self.assertEqual(result['notifications'], list())
|
self.assertEqual(result['notifications'], list())
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.cache')
|
||||||
|
def test_current_notifications_is_none(self, mock_cache):
|
||||||
|
mock_cache.get_or_set.return_value = None
|
||||||
|
|
||||||
|
result = _current_notifications()
|
||||||
|
self.assertEqual(result['notifications'], list())
|
||||||
|
|
||||||
|
|
||||||
class TestCeleryQueueLength(TestCase):
|
class TestCeleryQueueLength(TestCase):
|
||||||
|
|
||||||
@@ -170,6 +176,15 @@ class TestVersionTags(TestCase):
|
|||||||
result = _fetch_tags_from_gitlab()
|
result = _fetch_tags_from_gitlab()
|
||||||
self.assertEqual(result, GITHUB_TAGS)
|
self.assertEqual(result, GITHUB_TAGS)
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||||
|
@patch(MODULE_PATH + '.admin_status.cache')
|
||||||
|
def test_current_version_info_return_no_data(self, mock_cache):
|
||||||
|
mock_cache.get_or_set.return_value = None
|
||||||
|
|
||||||
|
expected = {}
|
||||||
|
result = _current_version_summary()
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
|
||||||
class TestLatestsVersion(TestCase):
|
class TestLatestsVersion(TestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -5,35 +5,96 @@ from .models import EveAllianceInfo
|
|||||||
from .models import EveCharacter
|
from .models import EveCharacter
|
||||||
from .models import EveCorporationInfo
|
from .models import EveCorporationInfo
|
||||||
|
|
||||||
|
from . import providers
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
TASK_PRIORITY = 7
|
TASK_PRIORITY = 7
|
||||||
|
CHUNK_SIZE = 500
|
||||||
|
|
||||||
|
|
||||||
|
def chunks(lst, n):
|
||||||
|
"""Yield successive n-sized chunks from lst."""
|
||||||
|
for i in range(0, len(lst), n):
|
||||||
|
yield lst[i:i + n]
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def update_corp(corp_id):
|
def update_corp(corp_id):
|
||||||
|
"""Update given corporation from ESI"""
|
||||||
EveCorporationInfo.objects.update_corporation(corp_id)
|
EveCorporationInfo.objects.update_corporation(corp_id)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def update_alliance(alliance_id):
|
def update_alliance(alliance_id):
|
||||||
|
"""Update given alliance from ESI"""
|
||||||
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
|
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def update_character(character_id):
|
def update_character(character_id):
|
||||||
|
"""Update given character from ESI"""
|
||||||
EveCharacter.objects.update_character(character_id)
|
EveCharacter.objects.update_character(character_id)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def run_model_update():
|
def run_model_update():
|
||||||
|
"""Update all alliances, corporations and characters from ESI"""
|
||||||
|
|
||||||
# update existing corp models
|
# update existing corp models
|
||||||
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
|
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
|
||||||
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
|
update_corp.apply_async(
|
||||||
|
args=[corp['corporation_id']], priority=TASK_PRIORITY
|
||||||
|
)
|
||||||
|
|
||||||
# update existing alliance models
|
# update existing alliance models
|
||||||
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
|
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
|
||||||
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
|
update_alliance.apply_async(
|
||||||
|
args=[alliance['alliance_id']], priority=TASK_PRIORITY
|
||||||
|
)
|
||||||
|
|
||||||
#update existing character models
|
# update existing character models
|
||||||
for character in EveCharacter.objects.all().values('character_id'):
|
character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
|
||||||
update_character.apply_async(args=[character['character_id']], priority=TASK_PRIORITY)
|
for character_ids_chunk in chunks(character_ids, CHUNK_SIZE):
|
||||||
|
affiliations_raw = providers.provider.client.Character\
|
||||||
|
.post_characters_affiliation(characters=character_ids_chunk).result()
|
||||||
|
character_names = providers.provider.client.Universe\
|
||||||
|
.post_universe_names(ids=character_ids_chunk).result()
|
||||||
|
|
||||||
|
affiliations = {
|
||||||
|
affiliation.get('character_id'): affiliation
|
||||||
|
for affiliation in affiliations_raw
|
||||||
|
}
|
||||||
|
# add character names to affiliations
|
||||||
|
for character in character_names:
|
||||||
|
character_id = character.get('id')
|
||||||
|
if character_id in affiliations:
|
||||||
|
affiliations[character_id]['name'] = character.get('name')
|
||||||
|
|
||||||
|
# fetch current characters
|
||||||
|
characters = EveCharacter.objects.filter(character_id__in=character_ids_chunk)\
|
||||||
|
.values('character_id', 'corporation_id', 'alliance_id', 'character_name')
|
||||||
|
|
||||||
|
for character in characters:
|
||||||
|
character_id = character.get('character_id')
|
||||||
|
if character_id in affiliations:
|
||||||
|
affiliation = affiliations[character_id]
|
||||||
|
|
||||||
|
corp_changed = (
|
||||||
|
character.get('corporation_id') != affiliation.get('corporation_id')
|
||||||
|
)
|
||||||
|
|
||||||
|
alliance_id = character.get('alliance_id')
|
||||||
|
if not alliance_id:
|
||||||
|
alliance_id = None
|
||||||
|
alliance_changed = alliance_id != affiliation.get('alliance_id')
|
||||||
|
|
||||||
|
name_changed = False
|
||||||
|
fetched_name = affiliation.get('name', False)
|
||||||
|
if fetched_name:
|
||||||
|
name_changed = character.get('character_name') != fetched_name
|
||||||
|
|
||||||
|
if corp_changed or alliance_changed or name_changed:
|
||||||
|
update_character.apply_async(
|
||||||
|
args=[character.get('character_id')], priority=TASK_PRIORITY
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
@@ -44,55 +44,202 @@ class TestTasks(TestCase):
|
|||||||
mock_EveCharacter.objects.update_character.call_args[0][0], 42
|
mock_EveCharacter.objects.update_character.call_args[0][0], 42
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch('allianceauth.eveonline.tasks.update_character')
|
|
||||||
@patch('allianceauth.eveonline.tasks.update_alliance')
|
@patch('allianceauth.eveonline.tasks.update_character')
|
||||||
@patch('allianceauth.eveonline.tasks.update_corp')
|
@patch('allianceauth.eveonline.tasks.update_alliance')
|
||||||
def test_run_model_update(
|
@patch('allianceauth.eveonline.tasks.update_corp')
|
||||||
self,
|
@patch('allianceauth.eveonline.providers.provider')
|
||||||
mock_update_corp,
|
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
|
||||||
mock_update_alliance,
|
class TestRunModelUpdate(TestCase):
|
||||||
mock_update_character,
|
|
||||||
):
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
EveCorporationInfo.objects.all().delete()
|
EveCorporationInfo.objects.all().delete()
|
||||||
EveAllianceInfo.objects.all().delete()
|
EveAllianceInfo.objects.all().delete()
|
||||||
EveCharacter.objects.all().delete()
|
EveCharacter.objects.all().delete()
|
||||||
|
|
||||||
EveCorporationInfo.objects.create(
|
EveCorporationInfo.objects.create(
|
||||||
corporation_id=2345,
|
corporation_id=2345,
|
||||||
corporation_name='corp.name',
|
corporation_name='corp.name',
|
||||||
corporation_ticker='corp.ticker',
|
corporation_ticker='c.c.t',
|
||||||
member_count=10,
|
member_count=10,
|
||||||
alliance=None,
|
alliance=None,
|
||||||
)
|
)
|
||||||
EveAllianceInfo.objects.create(
|
EveAllianceInfo.objects.create(
|
||||||
alliance_id=3456,
|
alliance_id=3456,
|
||||||
alliance_name='alliance.name',
|
alliance_name='alliance.name',
|
||||||
alliance_ticker='alliance.ticker',
|
alliance_ticker='a.t',
|
||||||
executor_corp_id='78910',
|
executor_corp_id=5,
|
||||||
|
)
|
||||||
|
EveCharacter.objects.create(
|
||||||
|
character_id=1,
|
||||||
|
character_name='character.name1',
|
||||||
|
corporation_id=2345,
|
||||||
|
corporation_name='character.corp.name',
|
||||||
|
corporation_ticker='c.c.t', # max 5 chars
|
||||||
|
alliance_id=None
|
||||||
)
|
)
|
||||||
EveCharacter.objects.create(
|
EveCharacter.objects.create(
|
||||||
character_id=1234,
|
character_id=2,
|
||||||
character_name='character.name',
|
character_name='character.name2',
|
||||||
corporation_id=2345,
|
corporation_id=9876,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='c.c.t', # max 5 chars
|
corporation_ticker='c.c.t', # max 5 chars
|
||||||
alliance_id=3456,
|
alliance_id=3456,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
EveCharacter.objects.create(
|
||||||
|
character_id=3,
|
||||||
|
character_name='character.name3',
|
||||||
|
corporation_id=9876,
|
||||||
|
corporation_name='character.corp.name',
|
||||||
|
corporation_ticker='c.c.t', # max 5 chars
|
||||||
|
alliance_id=3456,
|
||||||
|
alliance_name='character.alliance.name',
|
||||||
|
)
|
||||||
|
EveCharacter.objects.create(
|
||||||
|
character_id=4,
|
||||||
|
character_name='character.name4',
|
||||||
|
corporation_id=9876,
|
||||||
|
corporation_name='character.corp.name',
|
||||||
|
corporation_ticker='c.c.t', # max 5 chars
|
||||||
|
alliance_id=3456,
|
||||||
|
alliance_name='character.alliance.name',
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
EveCharacter.objects.create(
|
||||||
|
character_id=5,
|
||||||
|
character_name='character.name5',
|
||||||
|
corporation_id=9876,
|
||||||
|
corporation_name='character.corp.name',
|
||||||
|
corporation_ticker='c.c.t', # max 5 chars
|
||||||
|
alliance_id=3456,
|
||||||
|
alliance_name='character.alliance.name',
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.affiliations = [
|
||||||
|
{'character_id': 1, 'corporation_id': 5},
|
||||||
|
{'character_id': 2, 'corporation_id': 9876, 'alliance_id': 3456},
|
||||||
|
{'character_id': 3, 'corporation_id': 9876, 'alliance_id': 7456},
|
||||||
|
{'character_id': 4, 'corporation_id': 9876, 'alliance_id': 3456}
|
||||||
|
]
|
||||||
|
self.names = [
|
||||||
|
{'id': 1, 'name': 'character.name1'},
|
||||||
|
{'id': 2, 'name': 'character.name2'},
|
||||||
|
{'id': 3, 'name': 'character.name3'},
|
||||||
|
{'id': 4, 'name': 'character.name4_new'}
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_normal_run(
|
||||||
|
self,
|
||||||
|
mock_provider,
|
||||||
|
mock_update_corp,
|
||||||
|
mock_update_alliance,
|
||||||
|
mock_update_character,
|
||||||
|
):
|
||||||
|
def get_affiliations(characters: list):
|
||||||
|
response = [x for x in self.affiliations if x['character_id'] in characters]
|
||||||
|
mock_operator = Mock(**{'result.return_value': response})
|
||||||
|
return mock_operator
|
||||||
|
|
||||||
|
def get_names(ids: list):
|
||||||
|
response = [x for x in self.names if x['id'] in ids]
|
||||||
|
mock_operator = Mock(**{'result.return_value': response})
|
||||||
|
return mock_operator
|
||||||
|
|
||||||
|
mock_provider.client.Character.post_characters_affiliation.side_effect \
|
||||||
|
= get_affiliations
|
||||||
|
|
||||||
|
mock_provider.client.Universe.post_universe_names.side_effect = get_names
|
||||||
|
|
||||||
run_model_update()
|
run_model_update()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
mock_provider.client.Character.post_characters_affiliation.call_count, 2
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
mock_provider.client.Universe.post_universe_names.call_count, 2
|
||||||
|
)
|
||||||
|
|
||||||
|
# character 1 has changed corp
|
||||||
|
# character 2 no change
|
||||||
|
# character 3 has changed alliance
|
||||||
|
# character 4 has changed name
|
||||||
self.assertEqual(mock_update_corp.apply_async.call_count, 1)
|
self.assertEqual(mock_update_corp.apply_async.call_count, 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
|
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
|
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456
|
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456
|
||||||
)
|
)
|
||||||
|
characters_updated = {
|
||||||
|
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
|
||||||
|
}
|
||||||
|
excepted = {1, 3, 4}
|
||||||
|
self.assertSetEqual(characters_updated, excepted)
|
||||||
|
|
||||||
|
def test_ignore_character_not_in_affiliations(
|
||||||
|
self,
|
||||||
|
mock_provider,
|
||||||
|
mock_update_corp,
|
||||||
|
mock_update_alliance,
|
||||||
|
mock_update_character,
|
||||||
|
):
|
||||||
|
def get_affiliations(characters: list):
|
||||||
|
response = [x for x in self.affiliations if x['character_id'] in characters]
|
||||||
|
mock_operator = Mock(**{'result.return_value': response})
|
||||||
|
return mock_operator
|
||||||
|
|
||||||
|
def get_names(ids: list):
|
||||||
|
response = [x for x in self.names if x['id'] in ids]
|
||||||
|
mock_operator = Mock(**{'result.return_value': response})
|
||||||
|
return mock_operator
|
||||||
|
|
||||||
|
del self.affiliations[0]
|
||||||
|
|
||||||
|
mock_provider.client.Character.post_characters_affiliation.side_effect \
|
||||||
|
= get_affiliations
|
||||||
|
|
||||||
|
mock_provider.client.Universe.post_universe_names.side_effect = get_names
|
||||||
|
|
||||||
self.assertEqual(mock_update_character.apply_async.call_count, 1)
|
run_model_update()
|
||||||
self.assertEqual(
|
characters_updated = {
|
||||||
int(mock_update_character.apply_async.call_args[1]['args'][0]), 1234
|
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
|
||||||
)
|
}
|
||||||
|
excepted = {3, 4}
|
||||||
|
self.assertSetEqual(characters_updated, excepted)
|
||||||
|
|
||||||
|
def test_ignore_character_not_in_names(
|
||||||
|
self,
|
||||||
|
mock_provider,
|
||||||
|
mock_update_corp,
|
||||||
|
mock_update_alliance,
|
||||||
|
mock_update_character,
|
||||||
|
):
|
||||||
|
def get_affiliations(characters: list):
|
||||||
|
response = [x for x in self.affiliations if x['character_id'] in characters]
|
||||||
|
mock_operator = Mock(**{'result.return_value': response})
|
||||||
|
return mock_operator
|
||||||
|
|
||||||
|
def get_names(ids: list):
|
||||||
|
response = [x for x in self.names if x['id'] in ids]
|
||||||
|
mock_operator = Mock(**{'result.return_value': response})
|
||||||
|
return mock_operator
|
||||||
|
|
||||||
|
del self.names[3]
|
||||||
|
|
||||||
|
mock_provider.client.Character.post_characters_affiliation.side_effect \
|
||||||
|
= get_affiliations
|
||||||
|
|
||||||
|
mock_provider.client.Universe.post_universe_names.side_effect = get_names
|
||||||
|
|
||||||
|
run_model_update()
|
||||||
|
characters_updated = {
|
||||||
|
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
|
||||||
|
}
|
||||||
|
excepted = {1, 3}
|
||||||
|
self.assertSetEqual(characters_updated, excepted)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ entry.date|date:"Y-M-d H:i" }}</td>
|
<td class="text-center">{{ entry.date|date:"Y-M-d, H:i" }}</td>
|
||||||
<td class="text-center">{{ entry.requestor }}</td>
|
<td class="text-center">{{ entry.requestor }}</td>
|
||||||
<td class="text-center">{{ entry.req_char }}</td>
|
<td class="text-center">{{ entry.req_char }}</td>
|
||||||
<td class="text-center">{{ entry.req_char.corporation_name }}</td>
|
<td class="text-center">{{ entry.req_char.corporation_name }}</td>
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js.html' %}
|
||||||
|
{% include 'bundles/moment-js.html' %}
|
||||||
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -74,7 +75,26 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
|
|
||||||
|
$.fn.dataTable.moment = function ( format, locale ) {
|
||||||
|
var types = $.fn.dataTable.ext.type;
|
||||||
|
|
||||||
|
// Add type detection
|
||||||
|
types.detect.unshift( function ( d ) {
|
||||||
|
return moment( d, format, locale, true ).isValid() ?
|
||||||
|
'moment-'+format :
|
||||||
|
null;
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Add sorting method - use an integer for the sorting
|
||||||
|
types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
|
||||||
|
return moment( d, format, locale, true ).unix();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
|
$.fn.dataTable.moment( 'YYYY-MMM-D, HH:mm' );
|
||||||
|
|
||||||
$('#log-entries').DataTable({
|
$('#log-entries').DataTable({
|
||||||
order: [[ 0, 'desc' ], [ 1, 'asc' ] ],
|
order: [[ 0, 'desc' ], [ 1, 'asc' ] ],
|
||||||
filterDropDown:
|
filterDropDown:
|
||||||
|
|||||||
Binary file not shown.
@@ -13,7 +13,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
"POT-Creation-Date: 2020-07-29 04:56+0000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||||
"Last-Translator: Rounon Dax <rounon.dax@terra-nanotech.de>, 2020\n"
|
"Last-Translator: Rounon Dax <rounon.dax@terra-nanotech.de>, 2020\n"
|
||||||
"Language-Team: German (https://www.transifex.com/alliance-auth/teams/107430/de/)\n"
|
"Language-Team: German (https://www.transifex.com/alliance-auth/teams/107430/de/)\n"
|
||||||
@@ -32,55 +32,55 @@ msgstr ""
|
|||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "E-Mail"
|
msgstr "E-Mail"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:76
|
#: allianceauth/authentication/models.py:78
|
||||||
msgid "State Changed"
|
|
||||||
msgstr "Status geändert"
|
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:77
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your user state has been changed to %(state)s"
|
msgid "State changed to: %s"
|
||||||
msgstr "Dein Nutzerstatus hat sich geändert zu %(state)s"
|
msgstr "Status geändert zu %s"
|
||||||
|
|
||||||
|
#: allianceauth/authentication/models.py:79
|
||||||
|
#, python-format
|
||||||
|
msgid "Your user's state is now: %(state)s"
|
||||||
|
msgstr "Dein Nutzerstatus ist nun %(state)s"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
#: allianceauth/templates/allianceauth/side-menu.html:12
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Dashboard"
|
msgstr "Dashboard"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:17
|
#: allianceauth/authentication/templates/authentication/dashboard.html:18
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
#, python-format
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:16
|
msgid ""
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
"\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
" Main Character (State: %(state)s)\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
" "
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
msgstr ""
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
"\n"
|
||||||
msgid "Main Character"
|
"Hauptcharakter (Status: %(state)s)"
|
||||||
msgstr "Hauptcharakter"
|
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:77
|
#: allianceauth/authentication/templates/authentication/dashboard.html:81
|
||||||
msgid "No main character set."
|
msgid "No main character set."
|
||||||
msgstr "Kein Hauptcharakter gesetzt."
|
msgstr "Kein Hauptcharakter gesetzt."
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:84
|
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
||||||
msgid "Add Character"
|
msgid "Add Character"
|
||||||
msgstr "Charakter hinzufügen"
|
msgstr "Charakter hinzufügen"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
#: allianceauth/authentication/templates/authentication/dashboard.html:92
|
||||||
msgid "Change Main"
|
msgid "Change Main"
|
||||||
msgstr "Hauptcharakter ändern"
|
msgstr "Hauptcharakter ändern"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
#: allianceauth/authentication/templates/authentication/dashboard.html:101
|
||||||
msgid "Group Memberships"
|
msgid "Group Memberships"
|
||||||
msgstr "Gruppen"
|
msgstr "Gruppen"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
#: allianceauth/authentication/templates/authentication/dashboard.html:121
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
||||||
msgid "Characters"
|
msgid "Characters"
|
||||||
msgstr "Charaktere"
|
msgstr "Charaktere"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
#: allianceauth/authentication/templates/authentication/dashboard.html:129
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
||||||
@@ -89,13 +89,13 @@ msgstr "Charaktere"
|
|||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:126
|
#: allianceauth/authentication/templates/authentication/dashboard.html:130
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
||||||
msgid "Corp"
|
msgid "Corp"
|
||||||
msgstr "Corp"
|
msgstr "Corp"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:127
|
#: allianceauth/authentication/templates/authentication/dashboard.html:131
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
||||||
msgid "Alliance"
|
msgid "Alliance"
|
||||||
@@ -236,8 +236,8 @@ msgstr "Letzes Update:"
|
|||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
|
||||||
msgid "Character"
|
msgid "Character"
|
||||||
msgstr "Charakter"
|
msgstr "Charakter"
|
||||||
|
|
||||||
@@ -259,6 +259,16 @@ msgstr "Corporation"
|
|||||||
msgid "Killboard"
|
msgid "Killboard"
|
||||||
msgstr "Killboard"
|
msgstr "Killboard"
|
||||||
|
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
||||||
|
#: allianceauth/corputils/templates/corputils/search.html:16
|
||||||
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
||||||
|
msgid "Main Character"
|
||||||
|
msgstr "Hauptcharakter"
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:17
|
#: allianceauth/corputils/templates/corputils/search.html:17
|
||||||
msgid "Main Corporation"
|
msgid "Main Corporation"
|
||||||
@@ -594,8 +604,8 @@ msgid "Portrait"
|
|||||||
msgstr "Portrait"
|
msgstr "Portrait"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "Organization"
|
msgstr "Organization"
|
||||||
@@ -614,7 +624,7 @@ msgstr "Gruppenmitgliedschaft"
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
#: allianceauth/templates/allianceauth/side-menu.html:17
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr "Gruppen"
|
msgstr "Gruppen"
|
||||||
|
|
||||||
@@ -658,8 +668,8 @@ msgid "Audit Members"
|
|||||||
msgstr "Mitglieder Protokoll"
|
msgstr "Mitglieder Protokoll"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||||
msgid "Copy Direrct Join Link"
|
msgid "Copy Direct Join Link"
|
||||||
msgstr ""
|
msgstr "Direktlink kopieren"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||||
msgid "No groups to list."
|
msgid "No groups to list."
|
||||||
@@ -690,37 +700,37 @@ msgstr "Keine Gruppen verfügbar"
|
|||||||
msgid "Groups Management"
|
msgid "Groups Management"
|
||||||
msgstr "Gruppenverwaltung"
|
msgstr "Gruppenverwaltung"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
|
||||||
msgid "Join Requests"
|
msgid "Join Requests"
|
||||||
msgstr "Beitrittsgesuche"
|
msgstr "Beitrittsgesuche"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33
|
||||||
msgid "Leave Requests"
|
msgid "Leave Requests"
|
||||||
msgstr "Austrittsgesuche"
|
msgstr "Austrittsgesuche"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
||||||
#: allianceauth/services/modules/openfire/forms.py:6
|
#: allianceauth/services/modules/openfire/forms.py:6
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr "Gruppen"
|
msgstr "Gruppen"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144
|
||||||
msgid "Accept"
|
msgid "Accept"
|
||||||
msgstr "Akzeptieren"
|
msgstr "Akzeptieren"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "Ablehnen"
|
msgstr "Ablehnen"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
||||||
msgid "No group add requests."
|
msgid "No group add requests."
|
||||||
msgstr "Keine Gruppenbeitrittsanfragen."
|
msgstr "Keine Gruppenbeitrittsanfragen."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156
|
||||||
msgid "No group leave requests."
|
msgid "No group leave requests."
|
||||||
msgstr "Keine Gruppenaustrittsanfragen"
|
msgstr "Keine Gruppenaustrittsanfragen"
|
||||||
|
|
||||||
@@ -729,7 +739,7 @@ msgid "Toggle navigation"
|
|||||||
msgstr "Toggle Navigation"
|
msgstr "Toggle Navigation"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:23
|
#: allianceauth/templates/allianceauth/side-menu.html:25
|
||||||
msgid "Group Management"
|
msgid "Group Management"
|
||||||
msgstr "Gruppenverwaltung"
|
msgstr "Gruppenverwaltung"
|
||||||
|
|
||||||
@@ -741,26 +751,26 @@ msgstr "Gruppenanfragen"
|
|||||||
msgid "Group Membership"
|
msgid "Group Membership"
|
||||||
msgstr "Gruppenmitgliedschaft"
|
msgstr "Gruppenmitgliedschaft"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:166
|
#: allianceauth/groupmanagement/views.py:162
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Removed user %(user)s from group %(group)s."
|
msgid "Removed user %(user)s from group %(group)s."
|
||||||
msgstr "Benutzer %(user)s von Gruppe %(group)s entfernt."
|
msgstr "Benutzer %(user)s von Gruppe %(group)s entfernt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:168
|
#: allianceauth/groupmanagement/views.py:164
|
||||||
msgid "User does not exist in that group"
|
msgid "User does not exist in that group"
|
||||||
msgstr "Benutzer existiert nicht in dieser Gruppe"
|
msgstr "Benutzer existiert nicht in dieser Gruppe"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:171
|
#: allianceauth/groupmanagement/views.py:167
|
||||||
msgid "Group does not exist"
|
msgid "Group does not exist"
|
||||||
msgstr "Gruppe existiert nicht"
|
msgstr "Gruppe existiert nicht"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:198
|
#: allianceauth/groupmanagement/views.py:194
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||||
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s zugestimmt."
|
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s zugestimmt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:205
|
#: allianceauth/groupmanagement/views.py:201
|
||||||
#: allianceauth/groupmanagement/views.py:238
|
#: allianceauth/groupmanagement/views.py:234
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
@@ -769,18 +779,18 @@ msgstr ""
|
|||||||
"Bei der Bearbeitung des Beitrittsgesuchs von %(mainchar)s zur Gruppe "
|
"Bei der Bearbeitung des Beitrittsgesuchs von %(mainchar)s zur Gruppe "
|
||||||
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:231
|
#: allianceauth/groupmanagement/views.py:227
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||||
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s abgelehnt."
|
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s abgelehnt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:267
|
#: allianceauth/groupmanagement/views.py:263
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s akzeptiert."
|
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s akzeptiert."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:273
|
#: allianceauth/groupmanagement/views.py:269
|
||||||
#: allianceauth/groupmanagement/views.py:307
|
#: allianceauth/groupmanagement/views.py:303
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
@@ -789,26 +799,26 @@ msgstr ""
|
|||||||
"Bei der Bearbeitung des Austrittsgesuchs von %(mainchar)s für Gruppe "
|
"Bei der Bearbeitung des Austrittsgesuchs von %(mainchar)s für Gruppe "
|
||||||
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:300
|
#: allianceauth/groupmanagement/views.py:296
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s abgelehnt."
|
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s abgelehnt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:346
|
#: allianceauth/groupmanagement/views.py:342
|
||||||
#: allianceauth/groupmanagement/views.py:358
|
#: allianceauth/groupmanagement/views.py:354
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr "Du kannst dieser Gruppe nicht beitreten"
|
msgstr "Du kannst dieser Gruppe nicht beitreten"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:352
|
#: allianceauth/groupmanagement/views.py:348
|
||||||
msgid "You are already a member of that group."
|
msgid "You are already a member of that group."
|
||||||
msgstr "Du bist bereits Mitglied dieser Gruppe."
|
msgstr "Du bist bereits Mitglied dieser Gruppe."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:367
|
#: allianceauth/groupmanagement/views.py:363
|
||||||
msgid "You already have a pending application for that group."
|
msgid "You already have a pending application for that group."
|
||||||
msgstr "Du hast Dich bereits für diese Gruppe beworben."
|
msgstr "Du hast Dich bereits für diese Gruppe beworben."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:370
|
#: allianceauth/groupmanagement/views.py:366
|
||||||
#: allianceauth/groupmanagement/views.py:408
|
#: allianceauth/groupmanagement/views.py:404
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||||
@@ -820,24 +830,24 @@ msgstr "Du hast Dich bereits für diese Gruppe beworben."
|
|||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Beantragt"
|
msgstr "Beantragt"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:376
|
#: allianceauth/groupmanagement/views.py:372
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to group %(group)s."
|
msgid "Applied to group %(group)s."
|
||||||
msgstr "Beitritt zur Gruppe %(group)s beantragt."
|
msgstr "Beitritt zur Gruppe %(group)s beantragt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:387
|
#: allianceauth/groupmanagement/views.py:383
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr "Du kannst diese Gruppe nicht verlassen"
|
msgstr "Du kannst diese Gruppe nicht verlassen"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:392
|
#: allianceauth/groupmanagement/views.py:388
|
||||||
msgid "You are not a member of that group"
|
msgid "You are not a member of that group"
|
||||||
msgstr "Du bist kein Mitglied dieser Gruppe"
|
msgstr "Du bist kein Mitglied dieser Gruppe"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:401
|
#: allianceauth/groupmanagement/views.py:397
|
||||||
msgid "You already have a pending leave request for that group."
|
msgid "You already have a pending leave request for that group."
|
||||||
msgstr "Du hast bereits ein ausstehendes Austrittsgesuch für diese Gruppe."
|
msgstr "Du hast bereits ein ausstehendes Austrittsgesuch für diese Gruppe."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:414
|
#: allianceauth/groupmanagement/views.py:410
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to leave group %(group)s."
|
msgid "Applied to leave group %(group)s."
|
||||||
msgstr "Austrittsgesuch für Gruppe %(group)s gesendet."
|
msgstr "Austrittsgesuch für Gruppe %(group)s gesendet."
|
||||||
@@ -1300,23 +1310,54 @@ msgstr "Passwort"
|
|||||||
msgid "Password must be at least 8 characters long."
|
msgid "Password must be at least 8 characters long."
|
||||||
msgstr "Passwort muss mindestens 8 Zeichen lang sein"
|
msgstr "Passwort muss mindestens 8 Zeichen lang sein"
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
|
#: allianceauth/services/modules/discord/models.py:224
|
||||||
|
msgid "Discord Account Disabled"
|
||||||
|
msgstr "Discord Konto deaktiviert"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/models.py:226
|
||||||
|
msgid ""
|
||||||
|
"Your Discord account was disabeled automatically by Auth. If you think this "
|
||||||
|
"was a mistake, please contact an admin."
|
||||||
|
msgstr ""
|
||||||
|
"Dein Discord Konto wurde automatisch durch Auth deaktiviert. Wenn Du glaubst"
|
||||||
|
" dies war ein Fehler, kontaktiere bitte einen Administrator."
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
|
||||||
|
msgid "Join the Discord server"
|
||||||
|
msgstr "Discord Server beitreten"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
|
||||||
|
msgid "Leave- and rejoin the Discord Server (Reset)"
|
||||||
|
msgstr "Discord Server verlassen und wieder beitreten (Zurücksetzen)"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
|
||||||
|
msgid "Leave the Discord server"
|
||||||
|
msgstr "Discord Server verlassen"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
|
||||||
msgid "Link Discord Server"
|
msgid "Link Discord Server"
|
||||||
msgstr "Verbinde Discord Server"
|
msgstr "Verbinde Discord Server"
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:26
|
#: allianceauth/services/modules/discord/views.py:30
|
||||||
msgid "Deactivated Discord account."
|
msgid "Deactivated Discord account."
|
||||||
msgstr "Discord Konto deaktiviert."
|
msgstr "Discord Konto deaktiviert."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:29
|
#: allianceauth/services/modules/discord/views.py:36
|
||||||
#: allianceauth/services/modules/discord/views.py:41
|
#: allianceauth/services/modules/discord/views.py:59
|
||||||
#: allianceauth/services/modules/discord/views.py:65
|
|
||||||
msgid "An error occurred while processing your Discord account."
|
msgid "An error occurred while processing your Discord account."
|
||||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Discord Kontos."
|
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Discord Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:62
|
#: allianceauth/services/modules/discord/views.py:102
|
||||||
msgid "Activated Discord account."
|
msgid "Your Discord account has been successfully activated."
|
||||||
msgstr "Discord Konto aktiviert."
|
msgstr "Dein Discord Konto wurde erfolgreich aktiviert."
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/views.py:108
|
||||||
|
msgid ""
|
||||||
|
"An error occurred while trying to activate your Discord account. Please try "
|
||||||
|
"again."
|
||||||
|
msgstr ""
|
||||||
|
"Es gab einen Fehler während der Aktivierung Deines Discord Kontos. Bitte "
|
||||||
|
"versuche es noch einmal."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:37
|
#: allianceauth/services/modules/discourse/views.py:37
|
||||||
msgid "You are not authorized to access Discourse."
|
msgid "You are not authorized to access Discourse."
|
||||||
@@ -1884,32 +1925,30 @@ msgid "Current"
|
|||||||
msgstr "Aktuell"
|
msgstr "Aktuell"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
||||||
msgid "Latest Major"
|
msgid "Latest Stable"
|
||||||
msgstr "Aktuellste Hauptversion"
|
msgstr "Aktuellste stabile Version"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
|
|
||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr "Update verfügbar"
|
msgstr "Update verfügbar"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
|
||||||
msgid "Latest Minor"
|
msgid "Latest Pre-Release"
|
||||||
msgstr "Aktuellste Unterversion"
|
msgstr "Aktuellste Testversion"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||||
msgid "Latest Patch"
|
msgid "Pre-Release available"
|
||||||
msgstr "Aktuellste Patchversion"
|
msgstr "Testversion verfügbar"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
|
||||||
msgid "Task Queue"
|
msgid "Task Queue"
|
||||||
msgstr "Warteschlange"
|
msgstr "Warteschlange"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
|
||||||
msgid "Error retrieving task queue length"
|
msgid "Error retrieving task queue length"
|
||||||
msgstr "Fehler beim Ermitteln der Warteschlange."
|
msgstr "Fehler beim Ermitteln der Warteschlange."
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(tasks)s task"
|
msgid "%(tasks)s task"
|
||||||
msgid_plural "%(tasks)s tasks"
|
msgid_plural "%(tasks)s tasks"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
"POT-Creation-Date: 2020-07-29 04:56+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -26,55 +26,53 @@ msgstr ""
|
|||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:76
|
#: allianceauth/authentication/models.py:78
|
||||||
msgid "State Changed"
|
#, python-format
|
||||||
|
msgid "State changed to: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:77
|
#: allianceauth/authentication/models.py:79
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your user state has been changed to %(state)s"
|
msgid "Your user's state is now: %(state)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
#: allianceauth/templates/allianceauth/side-menu.html:12
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:17
|
#: allianceauth/authentication/templates/authentication/dashboard.html:18
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
#, python-format
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:16
|
msgid ""
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
"\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
" Main Character (State: %(state)s)\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
" "
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
|
||||||
msgid "Main Character"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:77
|
#: allianceauth/authentication/templates/authentication/dashboard.html:81
|
||||||
msgid "No main character set."
|
msgid "No main character set."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:84
|
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
||||||
msgid "Add Character"
|
msgid "Add Character"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
#: allianceauth/authentication/templates/authentication/dashboard.html:92
|
||||||
msgid "Change Main"
|
msgid "Change Main"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
#: allianceauth/authentication/templates/authentication/dashboard.html:101
|
||||||
msgid "Group Memberships"
|
msgid "Group Memberships"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
#: allianceauth/authentication/templates/authentication/dashboard.html:121
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
||||||
msgid "Characters"
|
msgid "Characters"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
#: allianceauth/authentication/templates/authentication/dashboard.html:129
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
||||||
@@ -83,13 +81,13 @@ msgstr ""
|
|||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:126
|
#: allianceauth/authentication/templates/authentication/dashboard.html:130
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
||||||
msgid "Corp"
|
msgid "Corp"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:127
|
#: allianceauth/authentication/templates/authentication/dashboard.html:131
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
||||||
msgid "Alliance"
|
msgid "Alliance"
|
||||||
@@ -221,8 +219,8 @@ msgstr ""
|
|||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
|
||||||
msgid "Character"
|
msgid "Character"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -244,6 +242,16 @@ msgstr ""
|
|||||||
msgid "Killboard"
|
msgid "Killboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
||||||
|
#: allianceauth/corputils/templates/corputils/search.html:16
|
||||||
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
||||||
|
msgid "Main Character"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:17
|
#: allianceauth/corputils/templates/corputils/search.html:17
|
||||||
msgid "Main Corporation"
|
msgid "Main Corporation"
|
||||||
@@ -579,8 +587,8 @@ msgid "Portrait"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -599,7 +607,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
#: allianceauth/templates/allianceauth/side-menu.html:17
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -643,7 +651,7 @@ msgid "Audit Members"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||||
msgid "Copy Direrct Join Link"
|
msgid "Copy Direct Join Link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||||
@@ -675,37 +683,37 @@ msgstr ""
|
|||||||
msgid "Groups Management"
|
msgid "Groups Management"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
|
||||||
msgid "Join Requests"
|
msgid "Join Requests"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33
|
||||||
msgid "Leave Requests"
|
msgid "Leave Requests"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
||||||
#: allianceauth/services/modules/openfire/forms.py:6
|
#: allianceauth/services/modules/openfire/forms.py:6
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144
|
||||||
msgid "Accept"
|
msgid "Accept"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
||||||
msgid "No group add requests."
|
msgid "No group add requests."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156
|
||||||
msgid "No group leave requests."
|
msgid "No group leave requests."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -714,7 +722,7 @@ msgid "Toggle navigation"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:23
|
#: allianceauth/templates/allianceauth/side-menu.html:25
|
||||||
msgid "Group Management"
|
msgid "Group Management"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -726,70 +734,70 @@ msgstr ""
|
|||||||
msgid "Group Membership"
|
msgid "Group Membership"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:166
|
#: allianceauth/groupmanagement/views.py:162
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Removed user %(user)s from group %(group)s."
|
msgid "Removed user %(user)s from group %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:168
|
#: allianceauth/groupmanagement/views.py:164
|
||||||
msgid "User does not exist in that group"
|
msgid "User does not exist in that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:171
|
#: allianceauth/groupmanagement/views.py:167
|
||||||
msgid "Group does not exist"
|
msgid "Group does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:198
|
#: allianceauth/groupmanagement/views.py:194
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:205
|
#: allianceauth/groupmanagement/views.py:201
|
||||||
#: allianceauth/groupmanagement/views.py:238
|
#: allianceauth/groupmanagement/views.py:234
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
"%(mainchar)s to %(group)s."
|
"%(mainchar)s to %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:231
|
#: allianceauth/groupmanagement/views.py:227
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:267
|
#: allianceauth/groupmanagement/views.py:263
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:273
|
#: allianceauth/groupmanagement/views.py:269
|
||||||
#: allianceauth/groupmanagement/views.py:307
|
#: allianceauth/groupmanagement/views.py:303
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
"%(mainchar)s to leave %(group)s."
|
"%(mainchar)s to leave %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:300
|
#: allianceauth/groupmanagement/views.py:296
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:346
|
#: allianceauth/groupmanagement/views.py:342
|
||||||
#: allianceauth/groupmanagement/views.py:358
|
#: allianceauth/groupmanagement/views.py:354
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:352
|
#: allianceauth/groupmanagement/views.py:348
|
||||||
msgid "You are already a member of that group."
|
msgid "You are already a member of that group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:367
|
#: allianceauth/groupmanagement/views.py:363
|
||||||
msgid "You already have a pending application for that group."
|
msgid "You already have a pending application for that group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:370
|
#: allianceauth/groupmanagement/views.py:366
|
||||||
#: allianceauth/groupmanagement/views.py:408
|
#: allianceauth/groupmanagement/views.py:404
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||||
@@ -801,24 +809,24 @@ msgstr ""
|
|||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:376
|
#: allianceauth/groupmanagement/views.py:372
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to group %(group)s."
|
msgid "Applied to group %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:387
|
#: allianceauth/groupmanagement/views.py:383
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:392
|
#: allianceauth/groupmanagement/views.py:388
|
||||||
msgid "You are not a member of that group"
|
msgid "You are not a member of that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:401
|
#: allianceauth/groupmanagement/views.py:397
|
||||||
msgid "You already have a pending leave request for that group."
|
msgid "You already have a pending leave request for that group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:414
|
#: allianceauth/groupmanagement/views.py:410
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to leave group %(group)s."
|
msgid "Applied to leave group %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1281,22 +1289,49 @@ msgstr ""
|
|||||||
msgid "Password must be at least 8 characters long."
|
msgid "Password must be at least 8 characters long."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
|
#: allianceauth/services/modules/discord/models.py:224
|
||||||
|
msgid "Discord Account Disabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/models.py:226
|
||||||
|
msgid ""
|
||||||
|
"Your Discord account was disabeled automatically by Auth. If you think this "
|
||||||
|
"was a mistake, please contact an admin."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
|
||||||
|
msgid "Join the Discord server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
|
||||||
|
msgid "Leave- and rejoin the Discord Server (Reset)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
|
||||||
|
msgid "Leave the Discord server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
|
||||||
msgid "Link Discord Server"
|
msgid "Link Discord Server"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:26
|
#: allianceauth/services/modules/discord/views.py:30
|
||||||
msgid "Deactivated Discord account."
|
msgid "Deactivated Discord account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:29
|
#: allianceauth/services/modules/discord/views.py:36
|
||||||
#: allianceauth/services/modules/discord/views.py:41
|
#: allianceauth/services/modules/discord/views.py:59
|
||||||
#: allianceauth/services/modules/discord/views.py:65
|
|
||||||
msgid "An error occurred while processing your Discord account."
|
msgid "An error occurred while processing your Discord account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:62
|
#: allianceauth/services/modules/discord/views.py:102
|
||||||
msgid "Activated Discord account."
|
msgid "Your Discord account has been successfully activated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/views.py:108
|
||||||
|
msgid ""
|
||||||
|
"An error occurred while trying to activate your Discord account. Please try "
|
||||||
|
"again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:37
|
#: allianceauth/services/modules/discourse/views.py:37
|
||||||
@@ -1848,32 +1883,30 @@ msgid "Current"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
||||||
msgid "Latest Major"
|
msgid "Latest Stable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
|
|
||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
|
||||||
msgid "Latest Minor"
|
msgid "Latest Pre-Release"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||||
msgid "Latest Patch"
|
msgid "Pre-Release available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
|
||||||
msgid "Task Queue"
|
msgid "Task Queue"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
|
||||||
msgid "Error retrieving task queue length"
|
msgid "Error retrieving task queue length"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(tasks)s task"
|
msgid "%(tasks)s task"
|
||||||
msgid_plural "%(tasks)s tasks"
|
msgid_plural "%(tasks)s tasks"
|
||||||
|
|||||||
Binary file not shown.
@@ -11,7 +11,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
"POT-Creation-Date: 2020-07-29 04:56+0000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||||
"Last-Translator: Alexander Gess <de.alex.gess@gmail.com>, 2020\n"
|
"Last-Translator: Alexander Gess <de.alex.gess@gmail.com>, 2020\n"
|
||||||
"Language-Team: Russian (https://www.transifex.com/alliance-auth/teams/107430/ru/)\n"
|
"Language-Team: Russian (https://www.transifex.com/alliance-auth/teams/107430/ru/)\n"
|
||||||
@@ -29,55 +29,56 @@ msgstr "Необходимо указать основного персонаж
|
|||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:76
|
#: allianceauth/authentication/models.py:78
|
||||||
msgid "State Changed"
|
|
||||||
msgstr "Состояние заменено. "
|
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:77
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your user state has been changed to %(state)s"
|
msgid "State changed to: %s"
|
||||||
msgstr "Статус вашего пользователя сменен на %(state)s"
|
msgstr "Статус изменен: %s"
|
||||||
|
|
||||||
|
#: allianceauth/authentication/models.py:79
|
||||||
|
#, python-format
|
||||||
|
msgid "Your user's state is now: %(state)s"
|
||||||
|
msgstr "Статус пилота: %(state)s"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
#: allianceauth/templates/allianceauth/side-menu.html:12
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Панель показателей"
|
msgstr "Панель показателей"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:17
|
#: allianceauth/authentication/templates/authentication/dashboard.html:18
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
#, python-format
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:16
|
msgid ""
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
"\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
" Main Character (State: %(state)s)\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
" "
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
msgstr ""
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
"\n"
|
||||||
msgid "Main Character"
|
" Основной персонаж (статус: %(state)s)\n"
|
||||||
msgstr "Основной персонаж"
|
" "
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:77
|
#: allianceauth/authentication/templates/authentication/dashboard.html:81
|
||||||
msgid "No main character set."
|
msgid "No main character set."
|
||||||
msgstr "Основной персонаж не установлен."
|
msgstr "Основной персонаж не установлен."
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:84
|
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
||||||
msgid "Add Character"
|
msgid "Add Character"
|
||||||
msgstr "Добавить Персонажа"
|
msgstr "Добавить Персонажа"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
#: allianceauth/authentication/templates/authentication/dashboard.html:92
|
||||||
msgid "Change Main"
|
msgid "Change Main"
|
||||||
msgstr "Сменить основного персонажа"
|
msgstr "Сменить основного персонажа"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
#: allianceauth/authentication/templates/authentication/dashboard.html:101
|
||||||
msgid "Group Memberships"
|
msgid "Group Memberships"
|
||||||
msgstr "Групповое участие"
|
msgstr "Групповое участие"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
#: allianceauth/authentication/templates/authentication/dashboard.html:121
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
||||||
msgid "Characters"
|
msgid "Characters"
|
||||||
msgstr "Персонажи"
|
msgstr "Персонажи"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
#: allianceauth/authentication/templates/authentication/dashboard.html:129
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
||||||
@@ -86,13 +87,13 @@ msgstr "Персонажи"
|
|||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Имя"
|
msgstr "Имя"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:126
|
#: allianceauth/authentication/templates/authentication/dashboard.html:130
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
||||||
msgid "Corp"
|
msgid "Corp"
|
||||||
msgstr "Корпорация"
|
msgstr "Корпорация"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:127
|
#: allianceauth/authentication/templates/authentication/dashboard.html:131
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
||||||
msgid "Alliance"
|
msgid "Alliance"
|
||||||
@@ -141,6 +142,7 @@ msgid ""
|
|||||||
"Cannot change main character to %(char)s: character owned by a different "
|
"Cannot change main character to %(char)s: character owned by a different "
|
||||||
"account."
|
"account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Нельзя сменить основного персонажа на %(char)s: похоже, что Владелец не Вы. "
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:80
|
#: allianceauth/authentication/views.py:80
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -226,8 +228,8 @@ msgstr "Последнее обновление: "
|
|||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
|
||||||
msgid "Character"
|
msgid "Character"
|
||||||
msgstr "Персонаж"
|
msgstr "Персонаж"
|
||||||
|
|
||||||
@@ -249,6 +251,16 @@ msgstr "Корпорация"
|
|||||||
msgid "Killboard"
|
msgid "Killboard"
|
||||||
msgstr "zKillBoard"
|
msgstr "zKillBoard"
|
||||||
|
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
||||||
|
#: allianceauth/corputils/templates/corputils/search.html:16
|
||||||
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
||||||
|
msgid "Main Character"
|
||||||
|
msgstr "Основной персонаж"
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:17
|
#: allianceauth/corputils/templates/corputils/search.html:17
|
||||||
msgid "Main Corporation"
|
msgid "Main Corporation"
|
||||||
@@ -588,8 +600,8 @@ msgid "Portrait"
|
|||||||
msgstr "Портрет"
|
msgstr "Портрет"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "Корпорация"
|
msgstr "Корпорация"
|
||||||
@@ -608,7 +620,7 @@ msgstr "Участники группы"
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
#: allianceauth/templates/allianceauth/side-menu.html:17
|
||||||
msgid "Groups"
|
msgid "Groups"
|
||||||
msgstr "Группы"
|
msgstr "Группы"
|
||||||
|
|
||||||
@@ -652,8 +664,8 @@ msgid "Audit Members"
|
|||||||
msgstr "Проверить участников"
|
msgstr "Проверить участников"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||||
msgid "Copy Direrct Join Link"
|
msgid "Copy Direct Join Link"
|
||||||
msgstr ""
|
msgstr "Скопировать ссылку подключения"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||||
msgid "No groups to list."
|
msgid "No groups to list."
|
||||||
@@ -684,37 +696,37 @@ msgstr "Нет доступных групп."
|
|||||||
msgid "Groups Management"
|
msgid "Groups Management"
|
||||||
msgstr "Управление Группами"
|
msgstr "Управление Группами"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
|
||||||
msgid "Join Requests"
|
msgid "Join Requests"
|
||||||
msgstr "Запрос на присоединение"
|
msgstr "Запрос на присоединение"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33
|
||||||
msgid "Leave Requests"
|
msgid "Leave Requests"
|
||||||
msgstr "Запрос на Выход"
|
msgstr "Запрос на Выход"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
||||||
#: allianceauth/services/modules/openfire/forms.py:6
|
#: allianceauth/services/modules/openfire/forms.py:6
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr "Группа"
|
msgstr "Группа"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144
|
||||||
msgid "Accept"
|
msgid "Accept"
|
||||||
msgstr "Принять"
|
msgstr "Принять"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "Сбросить"
|
msgstr "Сбросить"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
||||||
msgid "No group add requests."
|
msgid "No group add requests."
|
||||||
msgstr "Нет групповых запросов на вступление"
|
msgstr "Нет групповых запросов на вступление"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156
|
||||||
msgid "No group leave requests."
|
msgid "No group leave requests."
|
||||||
msgstr "Нет групповых запросов на выход"
|
msgstr "Нет групповых запросов на выход"
|
||||||
|
|
||||||
@@ -723,7 +735,7 @@ msgid "Toggle navigation"
|
|||||||
msgstr "Проложить маршрут"
|
msgstr "Проложить маршрут"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:23
|
#: allianceauth/templates/allianceauth/side-menu.html:25
|
||||||
msgid "Group Management"
|
msgid "Group Management"
|
||||||
msgstr "Управление Группой"
|
msgstr "Управление Группой"
|
||||||
|
|
||||||
@@ -735,26 +747,26 @@ msgstr "Групповой запрос"
|
|||||||
msgid "Group Membership"
|
msgid "Group Membership"
|
||||||
msgstr "Групповое участие"
|
msgstr "Групповое участие"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:166
|
#: allianceauth/groupmanagement/views.py:162
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Removed user %(user)s from group %(group)s."
|
msgid "Removed user %(user)s from group %(group)s."
|
||||||
msgstr "Пользователь %(user)s исключен из %(group)s."
|
msgstr "Пользователь %(user)s исключен из %(group)s."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:168
|
#: allianceauth/groupmanagement/views.py:164
|
||||||
msgid "User does not exist in that group"
|
msgid "User does not exist in that group"
|
||||||
msgstr "Пользователь не существует в этой группе."
|
msgstr "Пользователь не существует в этой группе."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:171
|
#: allianceauth/groupmanagement/views.py:167
|
||||||
msgid "Group does not exist"
|
msgid "Group does not exist"
|
||||||
msgstr "Группа не существует."
|
msgstr "Группа не существует."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:198
|
#: allianceauth/groupmanagement/views.py:194
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||||
msgstr "Запрос от %(mainchar)sв %(group)s принят."
|
msgstr "Запрос от %(mainchar)sв %(group)s принят."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:205
|
#: allianceauth/groupmanagement/views.py:201
|
||||||
#: allianceauth/groupmanagement/views.py:238
|
#: allianceauth/groupmanagement/views.py:234
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
@@ -763,44 +775,46 @@ msgstr ""
|
|||||||
"Персонаж %(mainchar)s не может быть добавлен %(group)s, из-за непредвиденной"
|
"Персонаж %(mainchar)s не может быть добавлен %(group)s, из-за непредвиденной"
|
||||||
" ошибки. "
|
" ошибки. "
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:231
|
#: allianceauth/groupmanagement/views.py:227
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||||
msgstr "%(mainchar)s исключен из %(group)s."
|
msgstr "%(mainchar)s исключен из %(group)s."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:267
|
#: allianceauth/groupmanagement/views.py:263
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr "Утвержден выход %(mainchar)s из %(group)s. "
|
msgstr "Утвержден выход %(mainchar)s из %(group)s. "
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:273
|
#: allianceauth/groupmanagement/views.py:269
|
||||||
#: allianceauth/groupmanagement/views.py:307
|
#: allianceauth/groupmanagement/views.py:303
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
"%(mainchar)s to leave %(group)s."
|
"%(mainchar)s to leave %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Возникла ошибка во время обработки %(mainchar)s на выход из группы "
|
||||||
|
"%(group)s. Повторите позже."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:300
|
#: allianceauth/groupmanagement/views.py:296
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr "Прошение об исключении %(mainchar)s из %(group)s – отклонено. "
|
msgstr "Прошение об исключении %(mainchar)s из %(group)s – отклонено. "
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:346
|
#: allianceauth/groupmanagement/views.py:342
|
||||||
#: allianceauth/groupmanagement/views.py:358
|
#: allianceauth/groupmanagement/views.py:354
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr "Вы не можете вступить"
|
msgstr "Вы не можете вступить"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:352
|
#: allianceauth/groupmanagement/views.py:348
|
||||||
msgid "You are already a member of that group."
|
msgid "You are already a member of that group."
|
||||||
msgstr ""
|
msgstr "Вы уже участник этой группы."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:367
|
#: allianceauth/groupmanagement/views.py:363
|
||||||
msgid "You already have a pending application for that group."
|
msgid "You already have a pending application for that group."
|
||||||
msgstr ""
|
msgstr "Вы уже подали заявку на вступление этой группы."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:370
|
#: allianceauth/groupmanagement/views.py:366
|
||||||
#: allianceauth/groupmanagement/views.py:408
|
#: allianceauth/groupmanagement/views.py:404
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||||
@@ -812,24 +826,24 @@ msgstr ""
|
|||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Ожидание"
|
msgstr "Ожидание"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:376
|
#: allianceauth/groupmanagement/views.py:372
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to group %(group)s."
|
msgid "Applied to group %(group)s."
|
||||||
msgstr "Вступить в группу %(group)s."
|
msgstr "Вступить в группу %(group)s."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:387
|
#: allianceauth/groupmanagement/views.py:383
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr "Вы не можете покинуть эту группу"
|
msgstr "Вы не можете покинуть эту группу"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:392
|
#: allianceauth/groupmanagement/views.py:388
|
||||||
msgid "You are not a member of that group"
|
msgid "You are not a member of that group"
|
||||||
msgstr "Вы не участник группыы"
|
msgstr "Вы не участник группыы"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:401
|
#: allianceauth/groupmanagement/views.py:397
|
||||||
msgid "You already have a pending leave request for that group."
|
msgid "You already have a pending leave request for that group."
|
||||||
msgstr ""
|
msgstr "Ваш запрос находится на рассмотрении"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:414
|
#: allianceauth/groupmanagement/views.py:410
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to leave group %(group)s."
|
msgid "Applied to leave group %(group)s."
|
||||||
msgstr "Запрос на выход из группы %(group)s."
|
msgstr "Запрос на выход из группы %(group)s."
|
||||||
@@ -1222,15 +1236,15 @@ msgstr "Состояния"
|
|||||||
|
|
||||||
#: allianceauth/services/abstract.py:72
|
#: allianceauth/services/abstract.py:72
|
||||||
msgid "That service account already exists"
|
msgid "That service account already exists"
|
||||||
msgstr ""
|
msgstr "Этот сервис уже активирован"
|
||||||
|
|
||||||
#: allianceauth/services/abstract.py:104
|
#: allianceauth/services/abstract.py:104
|
||||||
msgid "Successfully set your {} password"
|
msgid "Successfully set your {} password"
|
||||||
msgstr ""
|
msgstr "{} Пароль успешно обновлен."
|
||||||
|
|
||||||
#: allianceauth/services/auth_hooks.py:11
|
#: allianceauth/services/auth_hooks.py:11
|
||||||
msgid "Services"
|
msgid "Services"
|
||||||
msgstr ""
|
msgstr "Сервисные услуги"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:6
|
#: allianceauth/services/forms.py:6
|
||||||
msgid "Name of Fleet:"
|
msgid "Name of Fleet:"
|
||||||
@@ -1292,37 +1306,74 @@ msgstr "Пароль"
|
|||||||
msgid "Password must be at least 8 characters long."
|
msgid "Password must be at least 8 characters long."
|
||||||
msgstr "Пароль должен быть не менее 8 символов."
|
msgstr "Пароль должен быть не менее 8 символов."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
|
#: allianceauth/services/modules/discord/models.py:224
|
||||||
|
msgid "Discord Account Disabled"
|
||||||
|
msgstr "Discord персонаж отключен"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/models.py:226
|
||||||
|
msgid ""
|
||||||
|
"Your Discord account was disabeled automatically by Auth. If you think this "
|
||||||
|
"was a mistake, please contact an admin."
|
||||||
|
msgstr ""
|
||||||
|
"Ваш доступ на сервер Discord был отменен. Если Вы считаете что по ошибке, "
|
||||||
|
"пожалуйста, свяжитесь с СЕО."
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
|
||||||
|
msgid "Join the Discord server"
|
||||||
|
msgstr "Подключиться к серверу Discord"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
|
||||||
|
msgid "Leave- and rejoin the Discord Server (Reset)"
|
||||||
|
msgstr "Переподключиться к серверу Discord. "
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
|
||||||
|
msgid "Leave the Discord server"
|
||||||
|
msgstr "Покинуть Discord сервер"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
|
||||||
msgid "Link Discord Server"
|
msgid "Link Discord Server"
|
||||||
msgstr "Ссылка на сервер Discord"
|
msgstr "Ссылка на сервер Discord"
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:26
|
#: allianceauth/services/modules/discord/views.py:30
|
||||||
msgid "Deactivated Discord account."
|
msgid "Deactivated Discord account."
|
||||||
msgstr ""
|
msgstr "Отменить доступ на Discord сервер."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:29
|
#: allianceauth/services/modules/discord/views.py:36
|
||||||
#: allianceauth/services/modules/discord/views.py:41
|
#: allianceauth/services/modules/discord/views.py:59
|
||||||
#: allianceauth/services/modules/discord/views.py:65
|
|
||||||
msgid "An error occurred while processing your Discord account."
|
msgid "An error occurred while processing your Discord account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Во время обработки Discord аккаунта возникла ошибка. Попробуйте чуточку "
|
||||||
|
"позднее. "
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:62
|
#: allianceauth/services/modules/discord/views.py:102
|
||||||
msgid "Activated Discord account."
|
msgid "Your Discord account has been successfully activated."
|
||||||
|
msgstr "Доступ на сервер Discord успешно получен."
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/views.py:108
|
||||||
|
msgid ""
|
||||||
|
"An error occurred while trying to activate your Discord account. Please try "
|
||||||
|
"again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Во время активации Discord аккаунта возникла ошибка. Попробуйте чуточку "
|
||||||
|
"позднее. "
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:37
|
#: allianceauth/services/modules/discourse/views.py:37
|
||||||
msgid "You are not authorized to access Discourse."
|
msgid "You are not authorized to access Discourse."
|
||||||
msgstr ""
|
msgstr "Вы не авторизованы в Discourse."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:42
|
#: allianceauth/services/modules/discourse/views.py:42
|
||||||
msgid "You must have a main character set to access Discourse."
|
msgid "You must have a main character set to access Discourse."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Для авторизации Discourse, необходимо получить авторизацию Вашим основным "
|
||||||
|
"аккаунтом."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:52
|
#: allianceauth/services/modules/discourse/views.py:52
|
||||||
msgid ""
|
msgid ""
|
||||||
"No SSO payload or signature. Please contact support if this problem "
|
"No SSO payload or signature. Please contact support if this problem "
|
||||||
"persists."
|
"persists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Отсуствует связь SSO. Если ошибка повторяется - свяжитесь с тех. поддержкой."
|
||||||
|
" "
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:62
|
#: allianceauth/services/modules/discourse/views.py:62
|
||||||
#: allianceauth/services/modules/discourse/views.py:70
|
#: allianceauth/services/modules/discourse/views.py:70
|
||||||
@@ -1354,7 +1405,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/services/modules/openfire/auth_hooks.py:26
|
#: allianceauth/services/modules/openfire/auth_hooks.py:26
|
||||||
msgid "Jabber"
|
msgid "Jabber"
|
||||||
msgstr ""
|
msgstr "Jabber"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/auth_hooks.py:78
|
#: allianceauth/services/modules/openfire/auth_hooks.py:78
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
||||||
@@ -1380,50 +1431,50 @@ msgstr "Бродкаст"
|
|||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:35
|
#: allianceauth/services/modules/openfire/views.py:35
|
||||||
msgid "Activated jabber account."
|
msgid "Activated jabber account."
|
||||||
msgstr ""
|
msgstr "Активировать доступ в jabber."
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:44
|
#: allianceauth/services/modules/openfire/views.py:44
|
||||||
#: allianceauth/services/modules/openfire/views.py:57
|
#: allianceauth/services/modules/openfire/views.py:57
|
||||||
#: allianceauth/services/modules/openfire/views.py:78
|
#: allianceauth/services/modules/openfire/views.py:78
|
||||||
#: allianceauth/services/modules/openfire/views.py:151
|
#: allianceauth/services/modules/openfire/views.py:151
|
||||||
msgid "An error occurred while processing your jabber account."
|
msgid "An error occurred while processing your jabber account."
|
||||||
msgstr ""
|
msgstr "Возникла ошибка во время активации jabber'а ."
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:70
|
#: allianceauth/services/modules/openfire/views.py:70
|
||||||
msgid "Reset jabber password."
|
msgid "Reset jabber password."
|
||||||
msgstr ""
|
msgstr "Сбросить jabber пароль."
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:119
|
#: allianceauth/services/modules/openfire/views.py:119
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Sent jabber broadcast to %s"
|
msgid "Sent jabber broadcast to %s"
|
||||||
msgstr ""
|
msgstr "Отправить Бродкаст %s"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:148
|
#: allianceauth/services/modules/openfire/views.py:148
|
||||||
msgid "Set jabber password."
|
msgid "Set jabber password."
|
||||||
msgstr ""
|
msgstr "Установить jabber пароль."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:34
|
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||||
msgid "Activated forum account."
|
msgid "Activated forum account."
|
||||||
msgstr ""
|
msgstr "Допустить на Форум."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:43
|
#: allianceauth/services/modules/phpbb3/views.py:43
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:57
|
#: allianceauth/services/modules/phpbb3/views.py:57
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:80
|
#: allianceauth/services/modules/phpbb3/views.py:80
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:103
|
#: allianceauth/services/modules/phpbb3/views.py:103
|
||||||
msgid "An error occurred while processing your forum account."
|
msgid "An error occurred while processing your forum account."
|
||||||
msgstr ""
|
msgstr "Во время обработки Форумного аккаунта, возникла ошибка."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:54
|
#: allianceauth/services/modules/phpbb3/views.py:54
|
||||||
msgid "Deactivated forum account."
|
msgid "Deactivated forum account."
|
||||||
msgstr ""
|
msgstr "Отменить доступ на Форум. "
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:71
|
#: allianceauth/services/modules/phpbb3/views.py:71
|
||||||
msgid "Reset forum password."
|
msgid "Reset forum password."
|
||||||
msgstr ""
|
msgstr "Сбросить пароль на Форум."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:100
|
#: allianceauth/services/modules/phpbb3/views.py:100
|
||||||
msgid "Set forum password."
|
msgid "Set forum password."
|
||||||
msgstr ""
|
msgstr "Установить пароль на Форум."
|
||||||
|
|
||||||
#: allianceauth/services/modules/smf/views.py:34
|
#: allianceauth/services/modules/smf/views.py:34
|
||||||
msgid "Activated SMF account."
|
msgid "Activated SMF account."
|
||||||
@@ -1473,21 +1524,21 @@ msgstr "Продолжить"
|
|||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:34
|
#: allianceauth/services/modules/teamspeak3/views.py:34
|
||||||
msgid "Activated TeamSpeak3 account."
|
msgid "Activated TeamSpeak3 account."
|
||||||
msgstr ""
|
msgstr "Активировать аккаунт TeamSpeak3."
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:37
|
#: allianceauth/services/modules/teamspeak3/views.py:37
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:74
|
#: allianceauth/services/modules/teamspeak3/views.py:74
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:100
|
#: allianceauth/services/modules/teamspeak3/views.py:100
|
||||||
msgid "An error occurred while processing your TeamSpeak3 account."
|
msgid "An error occurred while processing your TeamSpeak3 account."
|
||||||
msgstr ""
|
msgstr "Во время активации TeamSpeak3 возникла ошибка, попробуйте позже."
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:71
|
#: allianceauth/services/modules/teamspeak3/views.py:71
|
||||||
msgid "Deactivated TeamSpeak3 account."
|
msgid "Deactivated TeamSpeak3 account."
|
||||||
msgstr ""
|
msgstr "Отключить TeamSpeak3 аккаунт."
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:97
|
#: allianceauth/services/modules/teamspeak3/views.py:97
|
||||||
msgid "Reset TeamSpeak3 permission key."
|
msgid "Reset TeamSpeak3 permission key."
|
||||||
msgstr ""
|
msgstr "Сбросить TeamSpeak3 ключ доступа."
|
||||||
|
|
||||||
#: allianceauth/services/modules/xenforo/views.py:30
|
#: allianceauth/services/modules/xenforo/views.py:30
|
||||||
msgid "Activated XenForo account."
|
msgid "Activated XenForo account."
|
||||||
@@ -1866,32 +1917,30 @@ msgid "Current"
|
|||||||
msgstr "Текущий"
|
msgstr "Текущий"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
||||||
msgid "Latest Major"
|
msgid "Latest Stable"
|
||||||
msgstr "Последняя версия"
|
msgstr "Стабильная Версия"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
|
|
||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr "Доступно обновление"
|
msgstr "Доступно обновление"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
|
||||||
msgid "Latest Minor"
|
msgid "Latest Pre-Release"
|
||||||
msgstr "Последняя версия"
|
msgstr "Предрелизная Версия"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||||
msgid "Latest Patch"
|
msgid "Pre-Release available"
|
||||||
msgstr "Последние исправления"
|
msgstr "Предрелизная Версия"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
|
||||||
msgid "Task Queue"
|
msgid "Task Queue"
|
||||||
msgstr "Список задач"
|
msgstr "Список задач"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
|
||||||
msgid "Error retrieving task queue length"
|
msgid "Error retrieving task queue length"
|
||||||
msgstr "Ошибка при получении списка задач. "
|
msgstr "Ошибка при получении списка задач. "
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(tasks)s task"
|
msgid "%(tasks)s task"
|
||||||
msgid_plural "%(tasks)s tasks"
|
msgid_plural "%(tasks)s tasks"
|
||||||
|
|||||||
Binary file not shown.
@@ -6,15 +6,16 @@
|
|||||||
# Translators:
|
# Translators:
|
||||||
# Joel Falknau <ozirascal@gmail.com>, 2020
|
# Joel Falknau <ozirascal@gmail.com>, 2020
|
||||||
# Jesse . <sgeine@hotmail.com>, 2020
|
# Jesse . <sgeine@hotmail.com>, 2020
|
||||||
|
# Aaron BuBu <351793078@qq.com>, 2020
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2020-03-10 01:32+0000\n"
|
"POT-Creation-Date: 2020-07-29 03:24+0000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||||
"Last-Translator: Jesse . <sgeine@hotmail.com>, 2020\n"
|
"Last-Translator: Aaron BuBu <351793078@qq.com>, 2020\n"
|
||||||
"Language-Team: Chinese Simplified (https://www.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n"
|
"Language-Team: Chinese Simplified (https://www.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -30,55 +31,53 @@ msgstr "只有主要角色才能执行这个操作。在下面添加一个"
|
|||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "电子邮箱"
|
msgstr "电子邮箱"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:76
|
#: allianceauth/authentication/models.py:78
|
||||||
msgid "State Changed"
|
|
||||||
msgstr "状态已经更改"
|
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:77
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your user state has been changed to %(state)s"
|
msgid "State changed to: %s"
|
||||||
msgstr "您的用户状态已经更改为%(state)s"
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/models.py:79
|
||||||
|
#, python-format
|
||||||
|
msgid "Your user's state is now: %(state)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
#: allianceauth/authentication/templates/authentication/dashboard.html:8
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
#: allianceauth/templates/allianceauth/side-menu.html:12
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "账户总览"
|
msgstr "账户总览"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:17
|
#: allianceauth/authentication/templates/authentication/dashboard.html:18
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
#, python-format
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:16
|
msgid ""
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
"\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
" Main Character (State: %(state)s)\n"
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
" "
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
msgstr ""
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
|
||||||
msgid "Main Character"
|
|
||||||
msgstr "主要角色"
|
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:77
|
#: allianceauth/authentication/templates/authentication/dashboard.html:81
|
||||||
msgid "No main character set."
|
msgid "No main character set."
|
||||||
msgstr "没有主要角色组"
|
msgstr "没有主要角色组"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:84
|
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
||||||
msgid "Add Character"
|
msgid "Add Character"
|
||||||
msgstr "添加角色"
|
msgstr "添加角色"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:88
|
#: allianceauth/authentication/templates/authentication/dashboard.html:92
|
||||||
msgid "Change Main"
|
msgid "Change Main"
|
||||||
msgstr "修改主要角色"
|
msgstr "修改主要角色"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
#: allianceauth/authentication/templates/authentication/dashboard.html:101
|
||||||
msgid "Group Memberships"
|
msgid "Group Memberships"
|
||||||
msgstr "用户组成员"
|
msgstr "用户组成员"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
#: allianceauth/authentication/templates/authentication/dashboard.html:121
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
|
||||||
msgid "Characters"
|
msgid "Characters"
|
||||||
msgstr "角色"
|
msgstr "角色"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
#: allianceauth/authentication/templates/authentication/dashboard.html:129
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
|
||||||
@@ -87,13 +86,13 @@ msgstr "角色"
|
|||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "角色名"
|
msgstr "角色名"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:126
|
#: allianceauth/authentication/templates/authentication/dashboard.html:130
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
|
||||||
msgid "Corp"
|
msgid "Corp"
|
||||||
msgstr "所在公司"
|
msgstr "所在公司"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:127
|
#: allianceauth/authentication/templates/authentication/dashboard.html:131
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
#: allianceauth/corputils/templates/corputils/corpstats.html:77
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
|
||||||
msgid "Alliance"
|
msgid "Alliance"
|
||||||
@@ -134,40 +133,47 @@ msgstr "您的IT团队"
|
|||||||
msgid "Submit"
|
msgid "Submit"
|
||||||
msgstr "提交"
|
msgstr "提交"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:77
|
#: allianceauth/authentication/views.py:74
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Cannot change main character to %(char)s: character owned by a different "
|
||||||
|
"account."
|
||||||
|
msgstr "不能修改主角色为%(char)s:这个角色被另一个账户所拥有"
|
||||||
|
|
||||||
|
#: allianceauth/authentication/views.py:80
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Changed main character to %(char)s"
|
msgid "Changed main character to %(char)s"
|
||||||
msgstr "修改主要角色为%(char)s"
|
msgstr "修改主要角色为%(char)s"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:86
|
#: allianceauth/authentication/views.py:89
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Added %(name)s to your account."
|
msgid "Added %(name)s to your account."
|
||||||
msgstr "添加%(name)s到您的账户"
|
msgstr "添加%(name)s到您的账户"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:88
|
#: allianceauth/authentication/views.py:91
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||||
msgstr "添加%(name)s到您的账户失败:他们已经在一个账户中了"
|
msgstr "添加%(name)s到您的账户失败:他们已经在一个账户中了"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:127
|
#: allianceauth/authentication/views.py:130
|
||||||
msgid "Unable to authenticate as the selected character."
|
msgid "Unable to authenticate as the selected character."
|
||||||
msgstr "无法作为选定的角色进行身份验证"
|
msgstr "无法作为选定的角色进行身份验证"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:145
|
#: allianceauth/authentication/views.py:148
|
||||||
msgid "Registration token has expired."
|
msgid "Registration token has expired."
|
||||||
msgstr "注册令牌过期。"
|
msgstr "注册令牌过期。"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:197
|
#: allianceauth/authentication/views.py:200
|
||||||
msgid ""
|
msgid ""
|
||||||
"Sent confirmation email. Please follow the link to confirm your email "
|
"Sent confirmation email. Please follow the link to confirm your email "
|
||||||
"address."
|
"address."
|
||||||
msgstr "已经发送了确认邮件。请按照链接确定您的电邮地址"
|
msgstr "已经发送了确认邮件。请按照链接确定您的电邮地址"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:202
|
#: allianceauth/authentication/views.py:205
|
||||||
msgid "Confirmed your email address. Please login to continue."
|
msgid "Confirmed your email address. Please login to continue."
|
||||||
msgstr "已确认您的电邮地址。请登录以继续"
|
msgstr "已确认您的电邮地址。请登录以继续"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:207
|
#: allianceauth/authentication/views.py:210
|
||||||
msgid "Registraion of new accounts it not allowed at this time."
|
msgid "Registraion of new accounts it not allowed at this time."
|
||||||
msgstr "现在不允许注册新账户。"
|
msgstr "现在不允许注册新账户。"
|
||||||
|
|
||||||
@@ -218,8 +224,8 @@ msgstr "最后一次更新"
|
|||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
|
||||||
msgid "Character"
|
msgid "Character"
|
||||||
msgstr "角色"
|
msgstr "角色"
|
||||||
|
|
||||||
@@ -241,6 +247,16 @@ msgstr "公司"
|
|||||||
msgid "Killboard"
|
msgid "Killboard"
|
||||||
msgstr "KB板"
|
msgstr "KB板"
|
||||||
|
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:116
|
||||||
|
#: allianceauth/corputils/templates/corputils/search.html:16
|
||||||
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
|
||||||
|
msgid "Main Character"
|
||||||
|
msgstr "主要角色"
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
#: allianceauth/corputils/templates/corputils/corpstats.html:117
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:17
|
#: allianceauth/corputils/templates/corputils/search.html:17
|
||||||
msgid "Main Corporation"
|
msgid "Main Corporation"
|
||||||
@@ -527,6 +543,12 @@ msgstr "PAP链接已过期"
|
|||||||
msgid "Audit Log"
|
msgid "Audit Log"
|
||||||
msgstr "审计日志"
|
msgstr "审计日志"
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
|
||||||
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "返回"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
|
||||||
msgid "Date/Time"
|
msgid "Date/Time"
|
||||||
msgstr "日期/时间"
|
msgstr "日期/时间"
|
||||||
@@ -568,8 +590,8 @@ msgid "Portrait"
|
|||||||
msgstr "人物头像"
|
msgstr "人物头像"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "组织"
|
msgstr "组织"
|
||||||
@@ -586,6 +608,12 @@ msgstr "用户组里没人呀,你叫我怎么列"
|
|||||||
msgid "Groups Membership"
|
msgid "Groups Membership"
|
||||||
msgstr "用户组成员"
|
msgstr "用户组成员"
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||||
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||||
|
#: allianceauth/templates/allianceauth/side-menu.html:17
|
||||||
|
msgid "Groups"
|
||||||
|
msgstr "群组"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
|
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
@@ -625,7 +653,11 @@ msgstr "查看成员"
|
|||||||
msgid "Audit Members"
|
msgid "Audit Members"
|
||||||
msgstr "编辑成员"
|
msgstr "编辑成员"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||||
|
msgid "Copy Direct Join Link"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||||
msgid "No groups to list."
|
msgid "No groups to list."
|
||||||
msgstr "无可用组"
|
msgstr "无可用组"
|
||||||
|
|
||||||
@@ -654,37 +686,37 @@ msgstr "没有可用用户组"
|
|||||||
msgid "Groups Management"
|
msgid "Groups Management"
|
||||||
msgstr "用户组管理"
|
msgstr "用户组管理"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
|
||||||
msgid "Join Requests"
|
msgid "Join Requests"
|
||||||
msgstr "入组的请求"
|
msgstr "入组的请求"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33
|
||||||
msgid "Leave Requests"
|
msgid "Leave Requests"
|
||||||
msgstr "离组的请求"
|
msgstr "离组的请求"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
||||||
#: allianceauth/services/modules/openfire/forms.py:6
|
#: allianceauth/services/modules/openfire/forms.py:6
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr "用户组"
|
msgstr "用户组"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144
|
||||||
msgid "Accept"
|
msgid "Accept"
|
||||||
msgstr "接受"
|
msgstr "接受"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "拒绝"
|
msgstr "拒绝"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
|
||||||
msgid "No group add requests."
|
msgid "No group add requests."
|
||||||
msgstr "没有加入用户组的请求,小老弟你是不是摇不到人"
|
msgstr "没有加入用户组的请求,小老弟你是不是摇不到人"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156
|
||||||
msgid "No group leave requests."
|
msgid "No group leave requests."
|
||||||
msgstr "没有离开用户组的请求,小老弟你人缘可以啊?"
|
msgstr "没有离开用户组的请求,小老弟你人缘可以啊?"
|
||||||
|
|
||||||
@@ -693,7 +725,7 @@ msgid "Toggle navigation"
|
|||||||
msgstr "打开导航栏"
|
msgstr "打开导航栏"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:23
|
#: allianceauth/templates/allianceauth/side-menu.html:25
|
||||||
msgid "Group Management"
|
msgid "Group Management"
|
||||||
msgstr "用户组管理"
|
msgstr "用户组管理"
|
||||||
|
|
||||||
@@ -705,61 +737,70 @@ msgstr "用户组请求"
|
|||||||
msgid "Group Membership"
|
msgid "Group Membership"
|
||||||
msgstr "用户组成员"
|
msgstr "用户组成员"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:165
|
#: allianceauth/groupmanagement/views.py:162
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Removed user %(user)s from group %(group)s."
|
msgid "Removed user %(user)s from group %(group)s."
|
||||||
msgstr "已将用户%(user)s从用户组%(group)s中移除"
|
msgstr "已将用户%(user)s从用户组%(group)s中移除"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:167
|
#: allianceauth/groupmanagement/views.py:164
|
||||||
msgid "User does not exist in that group"
|
msgid "User does not exist in that group"
|
||||||
msgstr "那个用户组中不存在这个用户"
|
msgstr "那个用户组中不存在这个用户"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:170
|
#: allianceauth/groupmanagement/views.py:167
|
||||||
msgid "Group does not exist"
|
msgid "Group does not exist"
|
||||||
msgstr "用户组不存在"
|
msgstr "用户组不存在"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:197
|
#: allianceauth/groupmanagement/views.py:194
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||||
msgstr "已接受用户%(mainchar)s加入%(group)s的申请"
|
msgstr "已接受用户%(mainchar)s加入%(group)s的申请"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:204
|
#: allianceauth/groupmanagement/views.py:201
|
||||||
#: allianceauth/groupmanagement/views.py:237
|
#: allianceauth/groupmanagement/views.py:234
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occurred while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
"%(mainchar)s to %(group)s."
|
"%(mainchar)s to %(group)s."
|
||||||
msgstr "在处理用户%(mainchar)s加入%(group)s的申请的过程中出现了一个搞不定的错误"
|
msgstr "在处理用户%(mainchar)s加入%(group)s的申请的过程中出现了一个搞不定的错误"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:230
|
#: allianceauth/groupmanagement/views.py:227
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||||
msgstr "%(mainchar)s加入%(group)s的申请已拒绝"
|
msgstr "%(mainchar)s加入%(group)s的申请已拒绝"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:266
|
#: allianceauth/groupmanagement/views.py:263
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr "%(mainchar)s加入%(group)s的申请已通过"
|
msgstr "%(mainchar)s加入%(group)s的申请已通过"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:272
|
#: allianceauth/groupmanagement/views.py:269
|
||||||
#: allianceauth/groupmanagement/views.py:306
|
#: allianceauth/groupmanagement/views.py:303
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"An unhandled error occured while processing the application from "
|
"An unhandled error occurred while processing the application from "
|
||||||
"%(mainchar)s to leave %(group)s."
|
"%(mainchar)s to leave %(group)s."
|
||||||
msgstr "在处理%(mainchar)s离开%(group)s的请求时发生了搞不定的错误"
|
msgstr "在处理%(mainchar)s离开%(group)s的程序时发生了未知的错误"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:299
|
#: allianceauth/groupmanagement/views.py:296
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||||
msgstr "%(mainchar)s离开%(group)s的请求已被拒绝"
|
msgstr "%(mainchar)s离开%(group)s的请求已被拒绝"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:346
|
#: allianceauth/groupmanagement/views.py:342
|
||||||
|
#: allianceauth/groupmanagement/views.py:354
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr "你无法加入那个用户组"
|
msgstr "你无法加入那个用户组"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:370
|
#: allianceauth/groupmanagement/views.py:348
|
||||||
#: allianceauth/groupmanagement/views.py:408
|
msgid "You are already a member of that group."
|
||||||
|
msgstr "你已经是那个群组的一员了。"
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/views.py:363
|
||||||
|
msgid "You already have a pending application for that group."
|
||||||
|
msgstr "你已经有了该组的未决申请"
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/views.py:366
|
||||||
|
#: allianceauth/groupmanagement/views.py:404
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||||
@@ -771,20 +812,24 @@ msgstr "你无法加入那个用户组"
|
|||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "待定"
|
msgstr "待定"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:376
|
#: allianceauth/groupmanagement/views.py:372
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to group %(group)s."
|
msgid "Applied to group %(group)s."
|
||||||
msgstr "修改已经应用到%(group)s啦"
|
msgstr "修改已经应用到%(group)s啦"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:387
|
#: allianceauth/groupmanagement/views.py:383
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr "你无法离开那个用户组"
|
msgstr "你无法离开那个用户组"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:392
|
#: allianceauth/groupmanagement/views.py:388
|
||||||
msgid "You are not a member of that group"
|
msgid "You are not a member of that group"
|
||||||
msgstr "你不是那个用户组的成员"
|
msgstr "你不是那个用户组的成员"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:414
|
#: allianceauth/groupmanagement/views.py:397
|
||||||
|
msgid "You already have a pending leave request for that group."
|
||||||
|
msgstr "你已经有了该组的未决离开请求"
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/views.py:410
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to leave group %(group)s."
|
msgid "Applied to leave group %(group)s."
|
||||||
msgstr "已经离开群组%(group)s"
|
msgstr "已经离开群组%(group)s"
|
||||||
@@ -1130,10 +1175,6 @@ msgstr "对搞事时间节点%(opname)s的修改保存了,朝令夕改你是
|
|||||||
msgid "Permissions Audit"
|
msgid "Permissions Audit"
|
||||||
msgstr "放行记录审计"
|
msgstr "放行记录审计"
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
|
||||||
msgid "Back"
|
|
||||||
msgstr "返回"
|
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
||||||
msgid "User / Character"
|
msgid "User / Character"
|
||||||
msgstr "用户/角色"
|
msgstr "用户/角色"
|
||||||
@@ -1175,15 +1216,22 @@ msgstr "操作类型"
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "用户"
|
msgstr "用户"
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
|
||||||
msgid "Groups"
|
|
||||||
msgstr "群组"
|
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:43
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:43
|
||||||
msgid "States"
|
msgid "States"
|
||||||
msgstr "声望"
|
msgstr "声望"
|
||||||
|
|
||||||
|
#: allianceauth/services/abstract.py:72
|
||||||
|
msgid "That service account already exists"
|
||||||
|
msgstr "该服务账户仍然存在"
|
||||||
|
|
||||||
|
#: allianceauth/services/abstract.py:104
|
||||||
|
msgid "Successfully set your {} password"
|
||||||
|
msgstr "成功修改了你的[]密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/auth_hooks.py:11
|
||||||
|
msgid "Services"
|
||||||
|
msgstr "服务"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:6
|
#: allianceauth/services/forms.py:6
|
||||||
msgid "Name of Fleet:"
|
msgid "Name of Fleet:"
|
||||||
msgstr "舰队名称"
|
msgstr "舰队名称"
|
||||||
@@ -1244,19 +1292,111 @@ msgstr "密码"
|
|||||||
msgid "Password must be at least 8 characters long."
|
msgid "Password must be at least 8 characters long."
|
||||||
msgstr "密码至少要有8个字符啊,你也太不注重安全啦"
|
msgstr "密码至少要有8个字符啊,你也太不注重安全啦"
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
|
#: allianceauth/services/modules/discord/models.py:224
|
||||||
|
msgid "Discord Account Disabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/models.py:226
|
||||||
|
msgid ""
|
||||||
|
"Your Discord account was disabeled automatically by Auth. If you think this "
|
||||||
|
"was a mistake, please contact an admin."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
|
||||||
|
msgid "Join the Discord server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
|
||||||
|
msgid "Leave- and rejoin the Discord Server (Reset)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
|
||||||
|
msgid "Leave the Discord server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
|
||||||
msgid "Link Discord Server"
|
msgid "Link Discord Server"
|
||||||
msgstr "链接到Discord服务器"
|
msgstr "链接到Discord服务器"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/forms.py:7
|
#: allianceauth/services/modules/discord/views.py:30
|
||||||
msgid "Message"
|
msgid "Deactivated Discord account."
|
||||||
msgstr "消息"
|
msgstr "停用Discord账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/views.py:36
|
||||||
|
#: allianceauth/services/modules/discord/views.py:59
|
||||||
|
msgid "An error occurred while processing your Discord account."
|
||||||
|
msgstr "在处理你的Discord账户时出错。"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/views.py:102
|
||||||
|
msgid "Your Discord account has been successfully activated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discord/views.py:108
|
||||||
|
msgid ""
|
||||||
|
"An error occurred while trying to activate your Discord account. Please try "
|
||||||
|
"again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discourse/views.py:37
|
||||||
|
msgid "You are not authorized to access Discourse."
|
||||||
|
msgstr "你无权访问Discourse"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discourse/views.py:42
|
||||||
|
msgid "You must have a main character set to access Discourse."
|
||||||
|
msgstr "你必须得有一个主要角色来访问Discourse"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discourse/views.py:52
|
||||||
|
msgid ""
|
||||||
|
"No SSO payload or signature. Please contact support if this problem "
|
||||||
|
"persists."
|
||||||
|
msgstr "没有在Seat上检测到SSO。如果该问题依然存在,请联系技术支持"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/discourse/views.py:62
|
||||||
|
#: allianceauth/services/modules/discourse/views.py:70
|
||||||
|
msgid "Invalid payload. Please contact support if this problem persists."
|
||||||
|
msgstr "无效的SSO验证。如果该问题依然存在请联系技术支持。"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:31
|
||||||
|
msgid "Activated IPSuite4 account."
|
||||||
|
msgstr "完成激活IPSuite4账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:40
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:62
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:83
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:103
|
||||||
|
msgid "An error occurred while processing your IPSuite4 account."
|
||||||
|
msgstr "在处理你的IPSuite4账户时出错"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:53
|
||||||
|
msgid "Reset IPSuite4 password."
|
||||||
|
msgstr "重置IPSuite4密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:80
|
||||||
|
msgid "Set IPSuite4 password."
|
||||||
|
msgstr "修改IPSuite4密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/ips4/views.py:100
|
||||||
|
msgid "Deactivated IPSuite4 account."
|
||||||
|
msgstr "停用IPSuite4账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/auth_hooks.py:26
|
||||||
|
msgid "Jabber"
|
||||||
|
msgstr "Jabber"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/auth_hooks.py:78
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:11
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:11
|
||||||
msgid "Jabber Broadcast"
|
msgid "Jabber Broadcast"
|
||||||
msgstr "Jabber广播"
|
msgstr "Jabber广播"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/auth_hooks.py:91
|
||||||
|
msgid "Fleet Broadcast Formatter"
|
||||||
|
msgstr "舰队广播设置"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/forms.py:7
|
||||||
|
msgid "Message"
|
||||||
|
msgstr "消息"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:17
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:17
|
||||||
msgid "Broadcast Sent!!"
|
msgid "Broadcast Sent!!"
|
||||||
msgstr "广播出去了!"
|
msgstr "广播出去了!"
|
||||||
@@ -1265,6 +1405,76 @@ msgstr "广播出去了!"
|
|||||||
msgid "Broadcast"
|
msgid "Broadcast"
|
||||||
msgstr "广播"
|
msgstr "广播"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:35
|
||||||
|
msgid "Activated jabber account."
|
||||||
|
msgstr "成功激活jabber账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:44
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:57
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:78
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:151
|
||||||
|
msgid "An error occurred while processing your jabber account."
|
||||||
|
msgstr "在处理你的jabber账户时出错"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:70
|
||||||
|
msgid "Reset jabber password."
|
||||||
|
msgstr "重置jabber密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:119
|
||||||
|
#, python-format
|
||||||
|
msgid "Sent jabber broadcast to %s"
|
||||||
|
msgstr "已经将jabber广播送到了%s"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/openfire/views.py:148
|
||||||
|
msgid "Set jabber password."
|
||||||
|
msgstr "修改jabber密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||||
|
msgid "Activated forum account."
|
||||||
|
msgstr "成功激活论坛账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:43
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:57
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:80
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:103
|
||||||
|
msgid "An error occurred while processing your forum account."
|
||||||
|
msgstr "在处理你的论坛账户时发生了一个错误"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:54
|
||||||
|
msgid "Deactivated forum account."
|
||||||
|
msgstr "停用论坛账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:71
|
||||||
|
msgid "Reset forum password."
|
||||||
|
msgstr "重置论坛密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/phpbb3/views.py:100
|
||||||
|
msgid "Set forum password."
|
||||||
|
msgstr "修改论坛密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/smf/views.py:34
|
||||||
|
msgid "Activated SMF account."
|
||||||
|
msgstr "成功激活SMF论坛账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/smf/views.py:43
|
||||||
|
#: allianceauth/services/modules/smf/views.py:58
|
||||||
|
#: allianceauth/services/modules/smf/views.py:80
|
||||||
|
#: allianceauth/services/modules/smf/views.py:103
|
||||||
|
msgid "An error occurred while processing your SMF account."
|
||||||
|
msgstr "在处理你的SMF论坛账户时发生了一个错误"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/smf/views.py:55
|
||||||
|
msgid "Deactivated SMF account."
|
||||||
|
msgstr "停用SMF论坛账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/smf/views.py:72
|
||||||
|
msgid "Reset SMF password."
|
||||||
|
msgstr "重置SMF密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/smf/views.py:100
|
||||||
|
msgid "Set SMF password."
|
||||||
|
msgstr "修改SMF论坛密码"
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Unable to locate user %s on server"
|
msgid "Unable to locate user %s on server"
|
||||||
@@ -1288,6 +1498,47 @@ msgstr "加入服务器"
|
|||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
msgstr "下一个"
|
msgstr "下一个"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/teamspeak3/views.py:34
|
||||||
|
msgid "Activated TeamSpeak3 account."
|
||||||
|
msgstr "成功激活TeamSpeak3账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/teamspeak3/views.py:37
|
||||||
|
#: allianceauth/services/modules/teamspeak3/views.py:74
|
||||||
|
#: allianceauth/services/modules/teamspeak3/views.py:100
|
||||||
|
msgid "An error occurred while processing your TeamSpeak3 account."
|
||||||
|
msgstr "在处理你的TeamSpeak3账户时发生了错误"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/teamspeak3/views.py:71
|
||||||
|
msgid "Deactivated TeamSpeak3 account."
|
||||||
|
msgstr "停用TeamSpeak3账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/teamspeak3/views.py:97
|
||||||
|
msgid "Reset TeamSpeak3 permission key."
|
||||||
|
msgstr "重置TeamSpeak3授权秘钥"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:30
|
||||||
|
msgid "Activated XenForo account."
|
||||||
|
msgstr "成功激活XenForo账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:40
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:52
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:73
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:94
|
||||||
|
msgid "An error occurred while processing your XenForo account."
|
||||||
|
msgstr "在处理你的XenForo账户时发生了一个错误"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:50
|
||||||
|
msgid "Deactivated XenForo account."
|
||||||
|
msgstr "停用XenForo论坛账户"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:65
|
||||||
|
msgid "Reset XenForo account password."
|
||||||
|
msgstr "重置XenForo密码"
|
||||||
|
|
||||||
|
#: allianceauth/services/modules/xenforo/views.py:91
|
||||||
|
msgid "Changed XenForo password."
|
||||||
|
msgstr "修改XenForo密码"
|
||||||
|
|
||||||
#: allianceauth/services/templates/services/fleetformattertool.html:6
|
#: allianceauth/services/templates/services/fleetformattertool.html:6
|
||||||
msgid "Fleet Formatter Tool"
|
msgid "Fleet Formatter Tool"
|
||||||
msgstr "起队工具"
|
msgstr "起队工具"
|
||||||
@@ -1638,43 +1889,35 @@ msgid "Current"
|
|||||||
msgstr "当前版本"
|
msgstr "当前版本"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
|
||||||
msgid "Latest Major"
|
msgid "Latest Stable"
|
||||||
msgstr "最新主版本号"
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
|
|
||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr "有更新!"
|
msgstr "有更新!"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
|
||||||
msgid "Latest Minor"
|
msgid "Latest Pre-Release"
|
||||||
msgstr "最新小版本号"
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||||
msgid "Latest Patch"
|
msgid "Pre-Release available"
|
||||||
msgstr "最新补丁版本"
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
|
||||||
msgid "Task Queue"
|
msgid "Task Queue"
|
||||||
msgstr "任务队列"
|
msgstr "任务队列"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
|
||||||
msgid "Error retrieving task queue length"
|
msgid "Error retrieving task queue length"
|
||||||
msgstr "获取任务队列长度的时候出错啦!"
|
msgstr "获取任务队列长度的时候出错啦!"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(tasks)s task"
|
msgid "%(tasks)s task"
|
||||||
msgid_plural "%(tasks)s tasks"
|
msgid_plural "%(tasks)s tasks"
|
||||||
msgstr[0] "%(tasks)s个任务"
|
msgstr[0] "%(tasks)s个任务"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/help.html:4
|
|
||||||
#: allianceauth/templates/allianceauth/help.html:9
|
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:34
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "帮助"
|
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
||||||
msgid "Night"
|
msgid "Night"
|
||||||
msgstr "暗色皮肤"
|
msgstr "暗色皮肤"
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ class DiscordService(ServicesHook):
|
|||||||
if self.user_has_account(user):
|
if self.user_has_account(user):
|
||||||
logger.debug('Deleting user %s %s account', user, self.name)
|
logger.debug('Deleting user %s %s account', user, self.name)
|
||||||
tasks.delete_user.apply_async(
|
tasks.delete_user.apply_async(
|
||||||
kwargs={'user_pk': user.pk}, priority=SINGLE_TASK_PRIORITY
|
kwargs={'user_pk': user.pk, 'notify_user': notify_user},
|
||||||
|
priority=SINGLE_TASK_PRIORITY
|
||||||
)
|
)
|
||||||
|
|
||||||
def render_services_ctrl(self, request):
|
def render_services_ctrl(self, request):
|
||||||
@@ -60,13 +61,21 @@ class DiscordService(ServicesHook):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def service_active_for_user(self, user):
|
def service_active_for_user(self, user):
|
||||||
return user.has_perm(self.access_perm)
|
has_perms = user.has_perm(self.access_perm)
|
||||||
|
logger.debug("User %s has service permission: %s", user, has_perms)
|
||||||
|
return has_perms
|
||||||
|
|
||||||
def sync_nickname(self, user):
|
def sync_nickname(self, user):
|
||||||
logger.debug('Syncing %s nickname for user %s', self.name, user)
|
logger.debug('Syncing %s nickname for user %s', self.name, user)
|
||||||
if self.user_has_account(user):
|
if self.user_has_account(user):
|
||||||
tasks.update_nickname.apply_async(
|
tasks.update_nickname.apply_async(
|
||||||
kwargs={'user_pk': user.pk}, priority=SINGLE_TASK_PRIORITY
|
kwargs={
|
||||||
|
'user_pk': user.pk,
|
||||||
|
# since the new nickname is not yet in the DB we need to
|
||||||
|
# provide it manually to the task
|
||||||
|
'nickname': DiscordUser.objects.user_formatted_nick(user)
|
||||||
|
},
|
||||||
|
priority=SINGLE_TASK_PRIORITY
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync_nicknames_bulk(self, users: list):
|
def sync_nicknames_bulk(self, users: list):
|
||||||
@@ -84,10 +93,16 @@ class DiscordService(ServicesHook):
|
|||||||
tasks.update_all_groups.delay()
|
tasks.update_all_groups.delay()
|
||||||
|
|
||||||
def update_groups(self, user):
|
def update_groups(self, user):
|
||||||
logger.debug('Processing %s groups for %s', self.name, user)
|
logger.debug('Processing %s groups for %s', self.name, user)
|
||||||
if self.user_has_account(user):
|
if self.user_has_account(user):
|
||||||
tasks.update_groups.apply_async(
|
tasks.update_groups.apply_async(
|
||||||
kwargs={'user_pk': user.pk}, priority=SINGLE_TASK_PRIORITY
|
kwargs={
|
||||||
|
'user_pk': user.pk,
|
||||||
|
# since state changes may not yet be in the DB we need to
|
||||||
|
# provide the new state name manually to the task
|
||||||
|
'state_name': user.profile.state.name
|
||||||
|
},
|
||||||
|
priority=SINGLE_TASK_PRIORITY
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_groups_bulk(self, users: list):
|
def update_groups_bulk(self, users: list):
|
||||||
|
|||||||
@@ -3,33 +3,38 @@ from ..utils import clean_setting
|
|||||||
|
|
||||||
# Base URL for all API calls. Must end with /.
|
# Base URL for all API calls. Must end with /.
|
||||||
DISCORD_API_BASE_URL = clean_setting(
|
DISCORD_API_BASE_URL = clean_setting(
|
||||||
'DISCORD_API_BASE_URL', 'https://discordapp.com/api/'
|
'DISCORD_API_BASE_URL', 'https://discord.com/api/'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Low level timeout for requests to the Discord API in ms
|
# Low level connecttimeout for requests to the Discord API in seconds
|
||||||
DISCORD_API_TIMEOUT = clean_setting(
|
DISCORD_API_TIMEOUT_CONNECT = clean_setting(
|
||||||
'DISCORD_API_TIMEOUT', 5000
|
'DISCORD_API_TIMEOUT', 5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Low level read timeout for requests to the Discord API in seconds
|
||||||
|
DISCORD_API_TIMEOUT_READ = clean_setting(
|
||||||
|
'DISCORD_API_TIMEOUT', 30
|
||||||
)
|
)
|
||||||
|
|
||||||
# Base authorization URL for Discord Oauth
|
# Base authorization URL for Discord Oauth
|
||||||
DISCORD_OAUTH_BASE_URL = clean_setting(
|
DISCORD_OAUTH_BASE_URL = clean_setting(
|
||||||
'DISCORD_OAUTH_BASE_URL', 'https://discordapp.com/api/oauth2/authorize'
|
'DISCORD_OAUTH_BASE_URL', 'https://discord.com/api/oauth2/authorize'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Base authorization URL for Discord Oauth
|
# Base authorization URL for Discord Oauth
|
||||||
DISCORD_OAUTH_TOKEN_URL = clean_setting(
|
DISCORD_OAUTH_TOKEN_URL = clean_setting(
|
||||||
'DISCORD_OAUTH_TOKEN_URL', 'https://discordapp.com/api/oauth2/token'
|
'DISCORD_OAUTH_TOKEN_URL', 'https://discord.com/api/oauth2/token'
|
||||||
)
|
)
|
||||||
|
|
||||||
# How long the Discord guild names retrieved from the server are
|
# How long the Discord guild names retrieved from the server are
|
||||||
# caches locally in milliseconds.
|
# caches locally in seconds.
|
||||||
DISCORD_GUILD_NAME_CACHE_MAX_AGE = clean_setting(
|
DISCORD_GUILD_NAME_CACHE_MAX_AGE = clean_setting(
|
||||||
'DISCORD_GUILD_NAME_CACHE_MAX_AGE', 3600 * 1 * 1000
|
'DISCORD_GUILD_NAME_CACHE_MAX_AGE', 3600 * 24
|
||||||
)
|
)
|
||||||
|
|
||||||
# How long Discord roles retrieved from the server are caches locally in milliseconds.
|
# How long Discord roles retrieved from the server are caches locally in seconds.
|
||||||
DISCORD_ROLES_CACHE_MAX_AGE = clean_setting(
|
DISCORD_ROLES_CACHE_MAX_AGE = clean_setting(
|
||||||
'DISCORD_ROLES_CACHE_MAX_AGE', 3600 * 1 * 1000
|
'DISCORD_ROLES_CACHE_MAX_AGE', 3600 * 1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Turns off creation of new roles. In case the rate limit for creating roles is
|
# Turns off creation of new roles. In case the rate limit for creating roles is
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ from allianceauth import __title__ as AUTH_TITLE, __url__, __version__
|
|||||||
from .. import __title__
|
from .. import __title__
|
||||||
from .app_settings import (
|
from .app_settings import (
|
||||||
DISCORD_API_BASE_URL,
|
DISCORD_API_BASE_URL,
|
||||||
DISCORD_API_TIMEOUT,
|
DISCORD_API_TIMEOUT_CONNECT,
|
||||||
|
DISCORD_API_TIMEOUT_READ,
|
||||||
DISCORD_DISABLE_ROLE_CREATION,
|
DISCORD_DISABLE_ROLE_CREATION,
|
||||||
DISCORD_GUILD_NAME_CACHE_MAX_AGE,
|
DISCORD_GUILD_NAME_CACHE_MAX_AGE,
|
||||||
DISCORD_OAUTH_BASE_URL,
|
DISCORD_OAUTH_BASE_URL,
|
||||||
@@ -179,12 +180,19 @@ class DiscordClient:
|
|||||||
r = self._api_request(method='get', route=route)
|
r = self._api_request(method='get', route=route)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def guild_name(self, guild_id: int) -> str:
|
def guild_name(self, guild_id: int, use_cache: bool = True) -> str:
|
||||||
"""returns the name of this guild (cached)
|
"""returns the name of this guild (cached)
|
||||||
or an empty string if something went wrong
|
or an empty string if something went wrong
|
||||||
|
|
||||||
|
Params:
|
||||||
|
- guild_id: ID of current guild
|
||||||
|
- use_cache: When set to False will force an API call to get the server name
|
||||||
"""
|
"""
|
||||||
key_name = self._guild_name_cache_key(guild_id)
|
key_name = self._guild_name_cache_key(guild_id)
|
||||||
guild_name = self._redis_decode(self._redis.get(key_name))
|
if use_cache:
|
||||||
|
guild_name = self._redis_decode(self._redis.get(key_name))
|
||||||
|
else:
|
||||||
|
guild_name = None
|
||||||
if not guild_name:
|
if not guild_name:
|
||||||
guild_infos = self.guild_infos(guild_id)
|
guild_infos = self.guild_infos(guild_id)
|
||||||
if 'name' in guild_infos:
|
if 'name' in guild_infos:
|
||||||
@@ -192,7 +200,7 @@ class DiscordClient:
|
|||||||
self._redis.set(
|
self._redis.set(
|
||||||
name=key_name,
|
name=key_name,
|
||||||
value=guild_name,
|
value=guild_name,
|
||||||
px=DISCORD_GUILD_NAME_CACHE_MAX_AGE
|
ex=DISCORD_GUILD_NAME_CACHE_MAX_AGE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
guild_name = ''
|
guild_name = ''
|
||||||
@@ -229,7 +237,7 @@ class DiscordClient:
|
|||||||
self._redis.set(
|
self._redis.set(
|
||||||
name=cache_key,
|
name=cache_key,
|
||||||
value=json.dumps(roles),
|
value=json.dumps(roles),
|
||||||
px=DISCORD_ROLES_CACHE_MAX_AGE
|
ex=DISCORD_ROLES_CACHE_MAX_AGE
|
||||||
)
|
)
|
||||||
return roles
|
return roles
|
||||||
|
|
||||||
@@ -273,6 +281,11 @@ class DiscordClient:
|
|||||||
gen_key = cls._generate_hash(f'{guild_id}')
|
gen_key = cls._generate_hash(f'{guild_id}')
|
||||||
return f'{cls._KEYPREFIX_GUILD_ROLES}__{gen_key}'
|
return f'{cls._KEYPREFIX_GUILD_ROLES}__{gen_key}'
|
||||||
|
|
||||||
|
def match_role_from_name(self, guild_id: int, role_name: str) -> dict:
|
||||||
|
"""returns Discord role matching the given name or an empty dict"""
|
||||||
|
guild_roles = DiscordRoles(self.guild_roles(guild_id))
|
||||||
|
return guild_roles.role_by_name(role_name)
|
||||||
|
|
||||||
def match_or_create_roles_from_names(self, guild_id: int, role_names: list) -> list:
|
def match_or_create_roles_from_names(self, guild_id: int, role_names: list) -> list:
|
||||||
"""returns Discord roles matching the given names
|
"""returns Discord roles matching the given names
|
||||||
|
|
||||||
@@ -280,6 +293,7 @@ class DiscordClient:
|
|||||||
|
|
||||||
Will try to match with existing roles names
|
Will try to match with existing roles names
|
||||||
Non-existing roles will be created, then created flag will be True
|
Non-existing roles will be created, then created flag will be True
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
- guild_id: ID of guild
|
- guild_id: ID of guild
|
||||||
- role_names: list of name strings each defining a role
|
- role_names: list of name strings each defining a role
|
||||||
@@ -310,6 +324,7 @@ class DiscordClient:
|
|||||||
|
|
||||||
Will try to match with existing roles names
|
Will try to match with existing roles names
|
||||||
Non-existing roles will be created, then created flag will be True
|
Non-existing roles will be created, then created flag will be True
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
- guild_id: ID of guild
|
- guild_id: ID of guild
|
||||||
- role_name: strings defining name of a role
|
- role_name: strings defining name of a role
|
||||||
@@ -540,7 +555,7 @@ class DiscordClient:
|
|||||||
args = {
|
args = {
|
||||||
'url': url,
|
'url': url,
|
||||||
'headers': headers,
|
'headers': headers,
|
||||||
'timeout': DISCORD_API_TIMEOUT / 1000
|
'timeout': (DISCORD_API_TIMEOUT_CONNECT, DISCORD_API_TIMEOUT_READ)
|
||||||
}
|
}
|
||||||
if data:
|
if data:
|
||||||
args['json'] = data
|
args['json'] = data
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ logger = set_logger_to_file(
|
|||||||
)
|
)
|
||||||
|
|
||||||
MODULE_PATH = 'allianceauth.services.modules.discord.discord_client.client'
|
MODULE_PATH = 'allianceauth.services.modules.discord.discord_client.client'
|
||||||
API_BASE_URL = 'https://discordapp.com/api/'
|
API_BASE_URL = 'https://discord.com/api/'
|
||||||
|
|
||||||
TEST_RETRY_AFTER = 3000
|
TEST_RETRY_AFTER = 3000
|
||||||
|
|
||||||
@@ -280,6 +280,8 @@ class TestGuildGetName(TestCase):
|
|||||||
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
||||||
result = client.guild_name(TEST_GUILD_ID)
|
result = client.guild_name(TEST_GUILD_ID)
|
||||||
self.assertEqual(result, guild_name)
|
self.assertEqual(result, guild_name)
|
||||||
|
self.assertTrue(my_mock_redis.get.called)
|
||||||
|
self.assertFalse(my_mock_redis.set.called)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.DiscordClient.guild_infos')
|
@patch(MODULE_PATH + '.DiscordClient.guild_infos')
|
||||||
def test_fetches_from_server_if_not_found_in_cache_and_stores_in_cache(
|
def test_fetches_from_server_if_not_found_in_cache_and_stores_in_cache(
|
||||||
@@ -291,6 +293,20 @@ class TestGuildGetName(TestCase):
|
|||||||
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
||||||
result = client.guild_name(TEST_GUILD_ID)
|
result = client.guild_name(TEST_GUILD_ID)
|
||||||
self.assertEqual(result, guild_name)
|
self.assertEqual(result, guild_name)
|
||||||
|
self.assertTrue(my_mock_redis.get.called)
|
||||||
|
self.assertTrue(my_mock_redis.set.called)
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.DiscordClient.guild_infos')
|
||||||
|
def test_fetches_from_server_if_asked_to_ignore_cache_and_stores_in_cache(
|
||||||
|
self, mock_guild_get_infos
|
||||||
|
):
|
||||||
|
guild_name = 'Omega'
|
||||||
|
my_mock_redis = MagicMock(**{'get.return_value': False})
|
||||||
|
mock_guild_get_infos.return_value = {'id': TEST_GUILD_ID, 'name': guild_name}
|
||||||
|
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
||||||
|
result = client.guild_name(TEST_GUILD_ID, use_cache=False)
|
||||||
|
self.assertFalse(my_mock_redis.get.called)
|
||||||
|
self.assertEqual(result, guild_name)
|
||||||
self.assertTrue(my_mock_redis.set.called)
|
self.assertTrue(my_mock_redis.set.called)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.DiscordClient.guild_infos')
|
@patch(MODULE_PATH + '.DiscordClient.guild_infos')
|
||||||
@@ -302,6 +318,7 @@ class TestGuildGetName(TestCase):
|
|||||||
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
||||||
result = client.guild_name(TEST_GUILD_ID)
|
result = client.guild_name(TEST_GUILD_ID)
|
||||||
self.assertEqual(result, '')
|
self.assertEqual(result, '')
|
||||||
|
self.assertTrue(my_mock_redis.get.called)
|
||||||
self.assertFalse(my_mock_redis.set.called)
|
self.assertFalse(my_mock_redis.set.called)
|
||||||
|
|
||||||
|
|
||||||
@@ -844,9 +861,45 @@ class TestGuildMemberRemoveRole(TestCase):
|
|||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMatchGuildRolesToName(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.url = f'{API_BASE_URL}guilds/{TEST_GUILD_ID}/roles'
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_return_role_if_known(self, requests_mocker):
|
||||||
|
my_mock_redis = MagicMock(**{
|
||||||
|
'get.return_value': None,
|
||||||
|
'pttl.return_value': -1,
|
||||||
|
})
|
||||||
|
requests_mocker.get(
|
||||||
|
url=self.url,
|
||||||
|
request_headers=DEFAULT_REQUEST_HEADERS,
|
||||||
|
json=ALL_ROLES
|
||||||
|
)
|
||||||
|
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
||||||
|
result = client.match_role_from_name(TEST_GUILD_ID, "alpha")
|
||||||
|
self.assertDictEqual(result, ROLE_ALPHA)
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_return_empty_dict_if_not_known(self, requests_mocker):
|
||||||
|
my_mock_redis = MagicMock(**{
|
||||||
|
'get.return_value': None,
|
||||||
|
'pttl.return_value': -1,
|
||||||
|
})
|
||||||
|
requests_mocker.get(
|
||||||
|
url=self.url,
|
||||||
|
request_headers=DEFAULT_REQUEST_HEADERS,
|
||||||
|
json=ALL_ROLES
|
||||||
|
)
|
||||||
|
client = DiscordClient2(TEST_BOT_TOKEN, my_mock_redis)
|
||||||
|
result = client.match_role_from_name(TEST_GUILD_ID, "unknown")
|
||||||
|
self.assertDictEqual(result, dict())
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.DiscordClient.create_guild_role')
|
@patch(MODULE_PATH + '.DiscordClient.create_guild_role')
|
||||||
@patch(MODULE_PATH + '.DiscordClient.guild_roles')
|
@patch(MODULE_PATH + '.DiscordClient.guild_roles')
|
||||||
class TestMatchGuildRolesToName(TestCase):
|
class TestMatchOrCreateGuildRolesToName(TestCase):
|
||||||
|
|
||||||
def test_return_role_if_known(
|
def test_return_role_if_known(
|
||||||
self, mock_guild_get_roles, mock_guild_create_role,
|
self, mock_guild_get_roles, mock_guild_create_role,
|
||||||
@@ -896,7 +949,7 @@ class TestMatchGuildRolesToName(TestCase):
|
|||||||
|
|
||||||
@patch(MODULE_PATH + '.DiscordClient.create_guild_role')
|
@patch(MODULE_PATH + '.DiscordClient.create_guild_role')
|
||||||
@patch(MODULE_PATH + '.DiscordClient.guild_roles')
|
@patch(MODULE_PATH + '.DiscordClient.guild_roles')
|
||||||
class TestMatchGuildRolesToNames(TestCase):
|
class TestMatchOrCreateGuildRolesToNames(TestCase):
|
||||||
|
|
||||||
def test_return_roles_if_known(
|
def test_return_roles_if_known(
|
||||||
self, mock_guild_get_roles, mock_guild_create_role,
|
self, mock_guild_get_roles, mock_guild_create_role,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from urllib.parse import urlencode
|
|||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User, Group
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
@@ -19,7 +19,8 @@ from .app_settings import (
|
|||||||
DISCORD_GUILD_ID,
|
DISCORD_GUILD_ID,
|
||||||
DISCORD_SYNC_NAMES
|
DISCORD_SYNC_NAMES
|
||||||
)
|
)
|
||||||
from .discord_client import DiscordClient, DiscordApiBackoff
|
from .discord_client import DiscordClient
|
||||||
|
from .discord_client.exceptions import DiscordClientException, DiscordApiBackoff
|
||||||
from .discord_client.helpers import match_or_create_roles_from_names
|
from .discord_client.helpers import match_or_create_roles_from_names
|
||||||
from .utils import LoggerAddTag
|
from .utils import LoggerAddTag
|
||||||
|
|
||||||
@@ -127,9 +128,17 @@ class DiscordUserManager(models.Manager):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def user_group_names(user: User) -> list:
|
def user_group_names(user: User, state_name: str = None) -> list:
|
||||||
"""returns list of group names plus state the given user is a member of"""
|
"""returns list of group names plus state the given user is a member of"""
|
||||||
return [group.name for group in user.groups.all()] + [user.profile.state.name]
|
if not state_name:
|
||||||
|
state_name = user.profile.state.name
|
||||||
|
group_names = (
|
||||||
|
[group.name for group in user.groups.all()] + [state_name]
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
"Group names for roles updates of user %s are: %s", user, group_names
|
||||||
|
)
|
||||||
|
return group_names
|
||||||
|
|
||||||
def user_has_account(self, user: User) -> bool:
|
def user_has_account(self, user: User) -> bool:
|
||||||
"""Returns True if the user has an Discord account, else False
|
"""Returns True if the user has an Discord account, else False
|
||||||
@@ -141,7 +150,7 @@ class DiscordUserManager(models.Manager):
|
|||||||
return self.filter(user=user).select_related('user').exists()
|
return self.filter(user=user).select_related('user').exists()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_bot_add_url(cls):
|
def generate_bot_add_url(cls) -> str:
|
||||||
params = urlencode({
|
params = urlencode({
|
||||||
'client_id': DISCORD_APP_ID,
|
'client_id': DISCORD_APP_ID,
|
||||||
'scope': 'bot',
|
'scope': 'bot',
|
||||||
@@ -151,7 +160,7 @@ class DiscordUserManager(models.Manager):
|
|||||||
return f'{DiscordClient.OAUTH_BASE_URL}?{params}'
|
return f'{DiscordClient.OAUTH_BASE_URL}?{params}'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_oauth_redirect_url(cls):
|
def generate_oauth_redirect_url(cls) -> str:
|
||||||
oauth = OAuth2Session(
|
oauth = OAuth2Session(
|
||||||
DISCORD_APP_ID, redirect_uri=DISCORD_CALLBACK_URL, scope=cls.SCOPES
|
DISCORD_APP_ID, redirect_uri=DISCORD_CALLBACK_URL, scope=cls.SCOPES
|
||||||
)
|
)
|
||||||
@@ -170,11 +179,38 @@ class DiscordUserManager(models.Manager):
|
|||||||
return token['access_token']
|
return token['access_token']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def server_name(cls):
|
def server_name(cls, use_cache: bool = True) -> str:
|
||||||
"""returns the name of the Discord server"""
|
"""returns the name of the current Discord server
|
||||||
return cls._bot_client().guild_name(DISCORD_GUILD_ID)
|
or an empty string if the name could not be retrieved
|
||||||
|
|
||||||
|
Params:
|
||||||
|
- use_cache: When set False will force an API call to get the server name
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
server_name = cls._bot_client().guild_name(
|
||||||
|
guild_id=DISCORD_GUILD_ID, use_cache=use_cache
|
||||||
|
)
|
||||||
|
except (HTTPError, DiscordClientException):
|
||||||
|
server_name = ""
|
||||||
|
except Exception:
|
||||||
|
logger.warning(
|
||||||
|
"Unexpected error when trying to retrieve the server name from Discord",
|
||||||
|
exc_info=True
|
||||||
|
)
|
||||||
|
server_name = ""
|
||||||
|
|
||||||
|
return server_name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def group_to_role(cls, group: Group) -> dict:
|
||||||
|
"""returns the Discord role matching the given Django group by name
|
||||||
|
or an empty dict() if no matching role exist
|
||||||
|
"""
|
||||||
|
return cls._bot_client().match_role_from_name(
|
||||||
|
guild_id=DISCORD_GUILD_ID, role_name=group.name
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _bot_client(is_rate_limited: bool = True):
|
def _bot_client(is_rate_limited: bool = True) -> DiscordClient:
|
||||||
"""returns a bot client for access to the Discord API"""
|
"""returns a bot client for access to the Discord API"""
|
||||||
return DiscordClient(DISCORD_BOT_TOKEN, is_rate_limited=is_rate_limited)
|
return DiscordClient(DISCORD_BOT_TOKEN, is_rate_limited=is_rate_limited)
|
||||||
|
|||||||
@@ -67,21 +67,25 @@ class DiscordUser(models.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'{type(self).__name__}(user=\'{self.user}\', uid={self.uid})'
|
return f'{type(self).__name__}(user=\'{self.user}\', uid={self.uid})'
|
||||||
|
|
||||||
def update_nickname(self) -> bool:
|
def update_nickname(self, nickname: str = None) -> bool:
|
||||||
"""Update nickname with formatted name of main character
|
"""Update nickname with formatted name of main character
|
||||||
|
|
||||||
|
Params:
|
||||||
|
- nickname: optional nickname to be used instead of user's main
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- True on success
|
- True on success
|
||||||
- None if user is no longer a member of the Discord server
|
- None if user is no longer a member of the Discord server
|
||||||
- False on error or raises exception
|
- False on error or raises exception
|
||||||
"""
|
"""
|
||||||
requested_nick = DiscordUser.objects.user_formatted_nick(self.user)
|
if not nickname:
|
||||||
if requested_nick:
|
nickname = DiscordUser.objects.user_formatted_nick(self.user)
|
||||||
|
if nickname:
|
||||||
client = DiscordUser.objects._bot_client()
|
client = DiscordUser.objects._bot_client()
|
||||||
success = client.modify_guild_member(
|
success = client.modify_guild_member(
|
||||||
guild_id=DISCORD_GUILD_ID,
|
guild_id=DISCORD_GUILD_ID,
|
||||||
user_id=self.uid,
|
user_id=self.uid,
|
||||||
nick=requested_nick
|
nick=nickname
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
logger.info('Nickname for %s has been updated', self.user)
|
logger.info('Nickname for %s has been updated', self.user)
|
||||||
@@ -92,10 +96,13 @@ class DiscordUser(models.Model):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_groups(self) -> bool:
|
def update_groups(self, state_name: str = None) -> bool:
|
||||||
"""update groups for a user based on his current group memberships.
|
"""update groups for a user based on his current group memberships.
|
||||||
Will add or remove roles of a user as needed.
|
Will add or remove roles of a user as needed.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
- state_name: optional state name to be used
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- True on success
|
- True on success
|
||||||
- None if user is no longer a member of the Discord server
|
- None if user is no longer a member of the Discord server
|
||||||
@@ -128,7 +135,9 @@ class DiscordUser(models.Model):
|
|||||||
requested_roles = match_or_create_roles_from_names(
|
requested_roles = match_or_create_roles_from_names(
|
||||||
client=client,
|
client=client,
|
||||||
guild_id=DISCORD_GUILD_ID,
|
guild_id=DISCORD_GUILD_ID,
|
||||||
role_names=DiscordUser.objects.user_group_names(self.user)
|
role_names=DiscordUser.objects.user_group_names(
|
||||||
|
user=self.user, state_name=state_name
|
||||||
|
)
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'Requested roles for user %s: %s', self.user, requested_roles.ids()
|
'Requested roles for user %s: %s', self.user, requested_roles.ids()
|
||||||
@@ -144,13 +153,13 @@ class DiscordUser(models.Model):
|
|||||||
role_ids=list(new_roles.ids())
|
role_ids=list(new_roles.ids())
|
||||||
)
|
)
|
||||||
if success:
|
if success:
|
||||||
logger.info('Groups for %s have been updated', self.user)
|
logger.info('Roles for %s have been updated', self.user)
|
||||||
else:
|
else:
|
||||||
logger.warning('Failed to update groups for %s', self.user)
|
logger.warning('Failed to update roles for %s', self.user)
|
||||||
return success
|
return success
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.info('No need to update groups for user %s', self.user)
|
logger.info('No need to update roles for user %s', self.user)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update_username(self) -> bool:
|
def update_username(self) -> bool:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from celery import shared_task, chain
|
from celery import shared_task, chain
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
@@ -26,25 +27,27 @@ BULK_TASK_PRIORITY = 6
|
|||||||
@shared_task(
|
@shared_task(
|
||||||
bind=True, name='discord.update_groups', base=QueueOnce, max_retries=None
|
bind=True, name='discord.update_groups', base=QueueOnce, max_retries=None
|
||||||
)
|
)
|
||||||
def update_groups(self, user_pk: int) -> None:
|
def update_groups(self, user_pk: int, state_name: str = None) -> None:
|
||||||
"""Update roles on Discord for given user according to his current groups
|
"""Update roles on Discord for given user according to his current groups
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
- user_pk: PK of given user
|
- user_pk: PK of given user
|
||||||
"""
|
- state_name: optional state name to be used
|
||||||
_task_perform_user_action(self, user_pk, 'update_groups')
|
"""
|
||||||
|
_task_perform_user_action(self, user_pk, 'update_groups', state_name=state_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(
|
@shared_task(
|
||||||
bind=True, name='discord.update_nickname', base=QueueOnce, max_retries=None
|
bind=True, name='discord.update_nickname', base=QueueOnce, max_retries=None
|
||||||
)
|
)
|
||||||
def update_nickname(self, user_pk: int) -> None:
|
def update_nickname(self, user_pk: int, nickname: str = None) -> None:
|
||||||
"""Set nickname on Discord for given user to his main character name
|
"""Set nickname on Discord for given user to his main character name
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
- user_pk: PK of given user
|
- user_pk: PK of given user
|
||||||
|
- nickname: optional nickname to be used instead of user's main
|
||||||
"""
|
"""
|
||||||
_task_perform_user_action(self, user_pk, 'update_nickname')
|
_task_perform_user_action(self, user_pk, 'update_nickname', nickname=nickname)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(
|
@shared_task(
|
||||||
@@ -75,6 +78,7 @@ def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None
|
|||||||
"""perform a user related action incl. managing all exceptions"""
|
"""perform a user related action incl. managing all exceptions"""
|
||||||
logger.debug("Starting %s for user with pk %s", method, user_pk)
|
logger.debug("Starting %s for user with pk %s", method, user_pk)
|
||||||
user = User.objects.get(pk=user_pk)
|
user = User.objects.get(pk=user_pk)
|
||||||
|
# logger.debug("user %s has state %s", user, user.profile.state)
|
||||||
if DiscordUser.objects.user_has_account(user):
|
if DiscordUser.objects.user_has_account(user):
|
||||||
logger.info("Running %s for user %s", method, user)
|
logger.info("Running %s for user %s", method, user)
|
||||||
try:
|
try:
|
||||||
@@ -91,7 +95,7 @@ def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None
|
|||||||
raise self.retry(countdown=bo.retry_after_seconds)
|
raise self.retry(countdown=bo.retry_after_seconds)
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ValueError(f'{method} not a valid method for DiscordUser: %r')
|
raise ValueError(f'{method} not a valid method for DiscordUser')
|
||||||
|
|
||||||
except (HTTPError, ConnectionError):
|
except (HTTPError, ConnectionError):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -112,7 +116,7 @@ def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error(
|
logger.error(
|
||||||
'%s for %s failed due to unexpected exception',
|
'%s for user %s failed due to unexpected exception',
|
||||||
method,
|
method,
|
||||||
user,
|
user,
|
||||||
exc_info=True
|
exc_info=True
|
||||||
@@ -183,9 +187,58 @@ def _bulk_update_nicknames_for_users(discord_users_qs: QuerySet) -> None:
|
|||||||
chain(update_nicknames_chain).apply_async(priority=BULK_TASK_PRIORITY)
|
chain(update_nicknames_chain).apply_async(priority=BULK_TASK_PRIORITY)
|
||||||
|
|
||||||
|
|
||||||
|
def _task_perform_users_action(self, method: str, **kwargs) -> Any:
|
||||||
|
"""Perform an action that concerns a group of users or the whole server
|
||||||
|
and that hits the API
|
||||||
|
"""
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
result = getattr(DiscordUser.objects, method)(**kwargs)
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
raise ValueError(f'{method} not a valid method for DiscordUser.objects')
|
||||||
|
|
||||||
|
except DiscordApiBackoff as bo:
|
||||||
|
logger.info(
|
||||||
|
"API back off for %s due to %r, retrying in %s seconds",
|
||||||
|
method,
|
||||||
|
bo,
|
||||||
|
bo.retry_after_seconds
|
||||||
|
)
|
||||||
|
raise self.retry(countdown=bo.retry_after_seconds)
|
||||||
|
|
||||||
|
except (HTTPError, ConnectionError):
|
||||||
|
logger.warning(
|
||||||
|
'%s failed, retrying in %d secs',
|
||||||
|
method,
|
||||||
|
DISCORD_TASKS_RETRY_PAUSE,
|
||||||
|
exc_info=True
|
||||||
|
)
|
||||||
|
if self.request.retries < DISCORD_TASKS_MAX_RETRIES:
|
||||||
|
raise self.retry(countdown=DISCORD_TASKS_RETRY_PAUSE)
|
||||||
|
else:
|
||||||
|
logger.error('%s failed after max retries', method, exc_info=True)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
logger.error('%s failed due to unexpected exception', method, exc_info=True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(
|
||||||
|
bind=True, name='discord.update_servername', base=QueueOnce, max_retries=None
|
||||||
|
)
|
||||||
|
def update_servername(self) -> None:
|
||||||
|
"""Updates the Discord server name"""
|
||||||
|
_task_perform_users_action(self, method="server_name", use_cache=False)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(name='discord.update_all_usernames')
|
@shared_task(name='discord.update_all_usernames')
|
||||||
def update_all_usernames() -> None:
|
def update_all_usernames() -> None:
|
||||||
"""Update all usernames for all known users with a Discord account."""
|
"""Update all usernames for all known users with a Discord account.
|
||||||
|
Also updates the server name
|
||||||
|
"""
|
||||||
|
update_servername.delay()
|
||||||
discord_users_qs = DiscordUser.objects.all()
|
discord_users_qs = DiscordUser.objects.all()
|
||||||
_bulk_update_usernames_for_users(discord_users_qs)
|
_bulk_update_usernames_for_users(discord_users_qs)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<div class="text-center" style="padding-top:5px;">
|
<div class="text-center" style="padding-top:5px;">
|
||||||
<a type="button" class="btn btn-default" href="{% url 'discord:add_bot' %}">
|
<a type="button" id="btnLinkDiscordServer" class="btn btn-default" href="{% url 'discord:add_bot' %}">
|
||||||
{% trans "Link Discord Server" %}
|
{% trans "Link Discord Server" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from ..discord_client.tests import ( # noqa
|
|||||||
ROLE_BRAVO,
|
ROLE_BRAVO,
|
||||||
ROLE_CHARLIE,
|
ROLE_CHARLIE,
|
||||||
ROLE_MIKE,
|
ROLE_MIKE,
|
||||||
|
ALL_ROLES,
|
||||||
create_user_info
|
create_user_info
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from unittest.mock import patch
|
|||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
from allianceauth.notifications.models import Notification
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from . import TEST_USER_NAME, TEST_USER_ID, add_permissions_to_members, MODULE_PATH
|
from . import TEST_USER_NAME, TEST_USER_ID, add_permissions_to_members, MODULE_PATH
|
||||||
@@ -30,6 +31,7 @@ class TestDiscordService(TestCase):
|
|||||||
self.service = DiscordService
|
self.service = DiscordService
|
||||||
add_permissions_to_members()
|
add_permissions_to_members()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
Notification.objects.all().delete()
|
||||||
|
|
||||||
def test_service_enabled(self):
|
def test_service_enabled(self):
|
||||||
service = self.service()
|
service = self.service()
|
||||||
@@ -89,16 +91,17 @@ class TestDiscordService(TestCase):
|
|||||||
service = self.service()
|
service = self.service()
|
||||||
service.sync_nicknames_bulk([self.member])
|
service.sync_nicknames_bulk([self.member])
|
||||||
self.assertTrue(mock_update_nicknames_bulk.delay.called)
|
self.assertTrue(mock_update_nicknames_bulk.delay.called)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
|
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
|
||||||
def test_delete_user_is_member(self, mock_DiscordClient):
|
def test_delete_user_is_member(self, mock_DiscordClient):
|
||||||
mock_DiscordClient.return_value.remove_guild_member.return_value = True
|
mock_DiscordClient.return_value.remove_guild_member.return_value = True
|
||||||
|
|
||||||
service = self.service()
|
service = self.service()
|
||||||
service.delete_user(self.member)
|
service.delete_user(self.member, notify_user=True)
|
||||||
|
|
||||||
self.assertTrue(mock_DiscordClient.return_value.remove_guild_member.called)
|
self.assertTrue(mock_DiscordClient.return_value.remove_guild_member.called)
|
||||||
self.assertFalse(DiscordUser.objects.filter(user=self.member).exists())
|
self.assertFalse(DiscordUser.objects.filter(user=self.member).exists())
|
||||||
|
self.assertTrue(Notification.objects.filter(user=self.member).exists())
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
|
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
|
||||||
def test_delete_user_is_not_member(self, mock_DiscordClient):
|
def test_delete_user_is_not_member(self, mock_DiscordClient):
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ import requests_mock
|
|||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.core.cache import caches
|
from django.core.cache import caches
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
from django.test import TransactionTestCase
|
from django.test import TransactionTestCase, TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
from allianceauth.authentication.models import State
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.notifications.models import Notification
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@@ -38,11 +41,14 @@ from . import (
|
|||||||
create_user_info
|
create_user_info
|
||||||
)
|
)
|
||||||
from ..discord_client.app_settings import DISCORD_API_BASE_URL
|
from ..discord_client.app_settings import DISCORD_API_BASE_URL
|
||||||
|
from ..discord_client.exceptions import DiscordApiBackoff
|
||||||
from ..models import DiscordUser
|
from ..models import DiscordUser
|
||||||
|
from .. import tasks
|
||||||
|
|
||||||
logger = logging.getLogger('allianceauth')
|
logger = logging.getLogger('allianceauth')
|
||||||
|
|
||||||
ROLE_MEMBER = create_role(99, 'Member')
|
ROLE_MEMBER = create_role(99, 'Member')
|
||||||
|
ROLE_BLUE = create_role(98, 'Blue')
|
||||||
|
|
||||||
# Putting all requests to Discord into objects so we can compare them better
|
# Putting all requests to Discord into objects so we can compare them better
|
||||||
DiscordRequest = namedtuple('DiscordRequest', ['method', 'url'])
|
DiscordRequest = namedtuple('DiscordRequest', ['method', 'url'])
|
||||||
@@ -87,6 +93,16 @@ def clear_cache():
|
|||||||
logger.info('Cache flushed')
|
logger.info('Cache flushed')
|
||||||
|
|
||||||
|
|
||||||
|
def reset_testdata():
|
||||||
|
AuthUtils.disconnect_signals()
|
||||||
|
Group.objects.all().delete()
|
||||||
|
User.objects.all().delete()
|
||||||
|
State.objects.all().delete()
|
||||||
|
EveCharacter.objects.all().delete()
|
||||||
|
AuthUtils.connect_signals()
|
||||||
|
Notification.objects.all().delete()
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
||||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
@@ -96,16 +112,26 @@ class TestServiceFeatures(TransactionTestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.maxDiff = None
|
cls.maxDiff = None
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
"""All tests: Given a user with member state,
|
||||||
|
service permission and active Discord account
|
||||||
|
"""
|
||||||
clear_cache()
|
clear_cache()
|
||||||
AuthUtils.disconnect_signals()
|
reset_testdata()
|
||||||
Group.objects.all().delete()
|
self.group_charlie = Group.objects.create(name='charlie')
|
||||||
User.objects.all().delete()
|
|
||||||
AuthUtils.connect_signals()
|
# States
|
||||||
self.group_3 = Group.objects.create(name='charlie')
|
self.member_state = AuthUtils.get_member_state()
|
||||||
self.user = AuthUtils.create_member(TEST_USER_NAME)
|
self.guest_state = AuthUtils.get_guest_state()
|
||||||
AuthUtils.add_main_character_2(
|
self.blue_state = AuthUtils.create_state("Blue", 50)
|
||||||
|
permission = AuthUtils.get_permission_by_name('discord.access_discord')
|
||||||
|
self.member_state.permissions.add(permission)
|
||||||
|
self.blue_state.permissions.add(permission)
|
||||||
|
|
||||||
|
# Test user
|
||||||
|
self.user = AuthUtils.create_user(TEST_USER_NAME)
|
||||||
|
self.main = AuthUtils.add_main_character_2(
|
||||||
self.user,
|
self.user,
|
||||||
TEST_MAIN_NAME,
|
TEST_MAIN_NAME,
|
||||||
TEST_MAIN_ID,
|
TEST_MAIN_ID,
|
||||||
@@ -113,60 +139,55 @@ class TestServiceFeatures(TransactionTestCase):
|
|||||||
corp_name='test_corp',
|
corp_name='test_corp',
|
||||||
corp_ticker='TEST',
|
corp_ticker='TEST',
|
||||||
disconnect_signals=True
|
disconnect_signals=True
|
||||||
)
|
)
|
||||||
self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
self.member_state.member_characters.add(self.main)
|
||||||
add_permissions_to_members()
|
|
||||||
|
|
||||||
def test_name_of_main_changes(self, requests_mocker):
|
# verify user is a member and has an account
|
||||||
# modify_guild_member()
|
self.user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertEqual(self.user.profile.state, self.member_state)
|
||||||
|
|
||||||
|
self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
|
def test_when_name_of_main_changes_then_discord_nick_is_updated(
|
||||||
|
self, requests_mocker
|
||||||
|
):
|
||||||
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
# changing nick to trigger signals
|
# changing nick to trigger signals
|
||||||
new_nick = f'Testnick {uuid1().hex}'[:32]
|
new_nick = f'Testnick {uuid1().hex}'[:32]
|
||||||
self.user.profile.main_character.character_name = new_nick
|
self.user.profile.main_character.character_name = new_nick
|
||||||
self.user.profile.main_character.save()
|
self.user.profile.main_character.save()
|
||||||
|
|
||||||
# Need to have called modify_guild_member two times only
|
# verify Discord nick was updates
|
||||||
# Once for sync nickname
|
nick_updated = False
|
||||||
# Once for change of main character
|
|
||||||
requests_made = list()
|
|
||||||
for r in requests_mocker.request_history:
|
for r in requests_mocker.request_history:
|
||||||
requests_made.append(DiscordRequest(r.method, r.url))
|
my_request = DiscordRequest(r.method, r.url)
|
||||||
|
if my_request == modify_guild_member_request and "nick" in r.json():
|
||||||
|
nick_updated = True
|
||||||
|
self.assertEqual(r.json()["nick"], new_nick)
|
||||||
|
|
||||||
|
self.assertTrue(nick_updated)
|
||||||
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
expected = [modify_guild_member_request, modify_guild_member_request]
|
def test_when_name_of_main_changes_and_user_deleted_then_account_is_deleted(
|
||||||
self.assertListEqual(requests_made, expected)
|
self, requests_mocker
|
||||||
|
):
|
||||||
def test_name_of_main_changes_but_user_deleted(self, requests_mocker):
|
|
||||||
# modify_guild_member()
|
|
||||||
requests_mocker.patch(
|
requests_mocker.patch(
|
||||||
modify_guild_member_request.url, status_code=404, json={'code': 10007}
|
modify_guild_member_request.url, status_code=404, json={'code': 10007}
|
||||||
)
|
)
|
||||||
# remove_guild_member()
|
|
||||||
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
|
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
# changing nick to trigger signals
|
# changing nick to trigger signals
|
||||||
new_nick = f'Testnick {uuid1().hex}'[:32]
|
new_nick = f'Testnick {uuid1().hex}'[:32]
|
||||||
self.user.profile.main_character.character_name = new_nick
|
self.user.profile.main_character.character_name = new_nick
|
||||||
self.user.profile.main_character.save()
|
self.user.profile.main_character.save()
|
||||||
|
|
||||||
|
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
# Need to have called modify_guild_member two times only
|
def test_when_name_of_main_changes_and_and_rate_limited_then_dont_call_api(
|
||||||
# Once for sync nickname
|
|
||||||
# Once for change of main character
|
|
||||||
requests_made = list()
|
|
||||||
for r in requests_mocker.request_history:
|
|
||||||
requests_made.append(DiscordRequest(r.method, r.url))
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
modify_guild_member_request,
|
|
||||||
remove_guild_member_request,
|
|
||||||
]
|
|
||||||
self.assertListEqual(requests_made, expected)
|
|
||||||
# self.assertFalse(DiscordUser.objects.user_has_account(self.user))
|
|
||||||
|
|
||||||
def test_name_of_main_changes_but_user_rate_limited(
|
|
||||||
self, requests_mocker
|
self, requests_mocker
|
||||||
):
|
):
|
||||||
# modify_guild_member()
|
|
||||||
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
# exhausting rate limit
|
# exhausting rate limit
|
||||||
@@ -183,98 +204,232 @@ class TestServiceFeatures(TransactionTestCase):
|
|||||||
self.user.profile.main_character.save()
|
self.user.profile.main_character.save()
|
||||||
|
|
||||||
# should not have called the API
|
# should not have called the API
|
||||||
requests_made = list()
|
requests_made = [
|
||||||
for r in requests_mocker.request_history:
|
DiscordRequest(r.method, r.url) for r in requests_mocker.request_history
|
||||||
requests_made.append(DiscordRequest(r.method, r.url))
|
]
|
||||||
|
|
||||||
|
self.assertListEqual(requests_made, list())
|
||||||
|
|
||||||
expected = list()
|
def test_when_member_is_demoted_to_guest_then_his_account_is_deleted(
|
||||||
self.assertListEqual(requests_made, expected)
|
self, requests_mocker
|
||||||
|
):
|
||||||
def test_user_demoted_to_guest(self, requests_mocker):
|
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
||||||
# remove_guild_member()
|
|
||||||
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
|
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
|
||||||
self.user.groups.clear()
|
|
||||||
|
# our user is a member and has an account
|
||||||
requests_made = list()
|
self.assertTrue(self.user.has_perm('discord.access_discord'))
|
||||||
for r in requests_mocker.request_history:
|
|
||||||
requests_made.append(DiscordRequest(r.method, r.url))
|
# now we demote him to guest
|
||||||
|
self.member_state.member_characters.remove(self.main)
|
||||||
|
|
||||||
# compare the list of made requests with expected
|
# verify user is now guest
|
||||||
expected = [remove_guild_member_request]
|
self.user = User.objects.get(pk=self.user.pk)
|
||||||
self.assertListEqual(requests_made, expected)
|
self.assertEqual(self.user.profile.state, AuthUtils.get_guest_state())
|
||||||
|
|
||||||
|
# verify user has no longer access to Discord and no account
|
||||||
|
self.assertFalse(self.user.has_perm('discord.access_discord'))
|
||||||
|
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
|
# verify account was actually deleted from Discord server
|
||||||
|
requests_made = [
|
||||||
|
DiscordRequest(r.method, r.url) for r in requests_mocker.request_history
|
||||||
|
]
|
||||||
|
self.assertIn(remove_guild_member_request, requests_made)
|
||||||
|
|
||||||
|
# verify user has been notified
|
||||||
|
self.assertTrue(Notification.objects.filter(user=self.user).exists())
|
||||||
|
|
||||||
|
def test_when_member_changes_to_blue_state_then_roles_are_updated_accordingly(
|
||||||
|
self, requests_mocker
|
||||||
|
):
|
||||||
|
# request mocks
|
||||||
|
requests_mocker.get(
|
||||||
|
guild_member_request.url,
|
||||||
|
json={'user': create_user_info(), 'roles': ['3', '13', '99']}
|
||||||
|
)
|
||||||
|
requests_mocker.get(
|
||||||
|
guild_roles_request.url,
|
||||||
|
json=[
|
||||||
|
ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER, ROLE_BLUE
|
||||||
|
]
|
||||||
|
)
|
||||||
|
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
|
||||||
|
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
|
AuthUtils.disconnect_signals()
|
||||||
|
self.user.groups.add(self.group_charlie)
|
||||||
|
AuthUtils.connect_signals()
|
||||||
|
|
||||||
def test_adding_group_to_user_role_exists(self, requests_mocker):
|
# demote user to blue state
|
||||||
# guild_member()
|
self.blue_state.member_characters.add(self.main)
|
||||||
|
self.member_state.member_characters.remove(self.main)
|
||||||
|
|
||||||
|
# verify roles for user where updated
|
||||||
|
roles_updated = False
|
||||||
|
for r in requests_mocker.request_history:
|
||||||
|
my_request = DiscordRequest(r.method, r.url)
|
||||||
|
if my_request == modify_guild_member_request and "roles" in r.json():
|
||||||
|
roles_updated = True
|
||||||
|
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 98})
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertTrue(roles_updated)
|
||||||
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
|
def test_when_group_added_to_member_and_role_known_then_his_roles_are_updated(
|
||||||
|
self, requests_mocker
|
||||||
|
):
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_member_request.url,
|
guild_member_request.url,
|
||||||
json={
|
json={
|
||||||
'user': create_user_info(),
|
'user': create_user_info(),
|
||||||
'roles': ['1', '13', '99']
|
'roles': ['13', '99']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# guild_roles()
|
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_roles_request.url,
|
guild_roles_request.url,
|
||||||
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER]
|
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER]
|
||||||
)
|
)
|
||||||
# create_guild_role()
|
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
|
||||||
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
|
|
||||||
# modify_guild_member()
|
|
||||||
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
# adding new group to trigger signals
|
# adding new group to trigger signals
|
||||||
self.user.groups.add(self.group_3)
|
self.user.groups.add(self.group_charlie)
|
||||||
self.user.refresh_from_db()
|
|
||||||
|
# verify roles for user where updated
|
||||||
# compare the list of made requests with expected
|
roles_updated = False
|
||||||
requests_made = list()
|
|
||||||
for r in requests_mocker.request_history:
|
for r in requests_mocker.request_history:
|
||||||
requests_made.append(DiscordRequest(r.method, r.url))
|
my_request = DiscordRequest(r.method, r.url)
|
||||||
|
if my_request == modify_guild_member_request and "roles" in r.json():
|
||||||
|
roles_updated = True
|
||||||
|
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 99})
|
||||||
|
break
|
||||||
|
|
||||||
|
self.assertTrue(roles_updated)
|
||||||
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
expected = [
|
def test_when_group_added_to_member_and_role_unknown_then_his_roles_are_updated(
|
||||||
guild_member_request,
|
self, requests_mocker
|
||||||
guild_roles_request,
|
):
|
||||||
modify_guild_member_request
|
|
||||||
]
|
|
||||||
self.assertListEqual(requests_made, expected)
|
|
||||||
|
|
||||||
def test_adding_group_to_user_role_does_not_exist(self, requests_mocker):
|
|
||||||
# guild_member()
|
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_member_request.url,
|
guild_member_request.url,
|
||||||
json={
|
json={
|
||||||
'user': {'id': str(TEST_USER_ID), 'username': TEST_MAIN_NAME},
|
'user': {'id': str(TEST_USER_ID), 'username': TEST_MAIN_NAME},
|
||||||
'roles': ['1', '13', '99']
|
'roles': ['13', '99']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# guild_roles()
|
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_roles_request.url,
|
guild_roles_request.url,
|
||||||
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
|
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
|
||||||
)
|
)
|
||||||
# create_guild_role()
|
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
|
||||||
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
|
|
||||||
# modify_guild_member()
|
|
||||||
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
# adding new group to trigger signals
|
# adding new group to trigger signals
|
||||||
self.user.groups.add(self.group_3)
|
self.user.groups.add(self.group_charlie)
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
|
|
||||||
# compare the list of made requests with expected
|
# verify roles for user where updated
|
||||||
requests_made = list()
|
roles_updated = False
|
||||||
for r in requests_mocker.request_history:
|
for r in requests_mocker.request_history:
|
||||||
requests_made.append(DiscordRequest(r.method, r.url))
|
my_request = DiscordRequest(r.method, r.url)
|
||||||
|
if my_request == modify_guild_member_request and "roles" in r.json():
|
||||||
expected = [
|
roles_updated = True
|
||||||
guild_member_request,
|
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 99})
|
||||||
guild_roles_request,
|
break
|
||||||
create_guild_role_request,
|
|
||||||
modify_guild_member_request
|
self.assertTrue(roles_updated)
|
||||||
]
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
self.assertListEqual(requests_made, expected)
|
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||||
|
@patch(MODULE_PATH + '.managers.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
||||||
|
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
class StateTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
clear_cache()
|
||||||
|
reset_testdata()
|
||||||
|
|
||||||
|
self.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(
|
||||||
|
self.user,
|
||||||
|
'Perm Test Character', '99',
|
||||||
|
corp_id='100',
|
||||||
|
alliance_id='200',
|
||||||
|
corp_name='Perm Test Corp',
|
||||||
|
alliance_name='Perm Test Alliance'
|
||||||
|
)
|
||||||
|
self.test_character = EveCharacter.objects.get(character_id='99')
|
||||||
|
self.member_state = State.objects.create(
|
||||||
|
name='Test Member',
|
||||||
|
priority=150,
|
||||||
|
)
|
||||||
|
self.access_discord = AuthUtils.get_permission_by_name('discord.access_discord')
|
||||||
|
self.member_state.permissions.add(self.access_discord)
|
||||||
|
self.member_state.member_characters.add(self.test_character)
|
||||||
|
|
||||||
|
def _add_discord_user(self):
|
||||||
|
self.discord_user = DiscordUser.objects.create(
|
||||||
|
user=self.user, uid="12345678910"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _refresh_user(self):
|
||||||
|
self.user = User.objects.get(pk=self.user.pk)
|
||||||
|
|
||||||
|
def test_perm_changes_to_higher_priority_state_creation(self, requests_mocker):
|
||||||
|
mock_url = DiscordRequest(
|
||||||
|
method='DELETE',
|
||||||
|
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/12345678910'
|
||||||
|
)
|
||||||
|
requests_mocker.delete(mock_url.url, status_code=204)
|
||||||
|
self._add_discord_user()
|
||||||
|
self._refresh_user()
|
||||||
|
higher_state = State.objects.create(
|
||||||
|
name='Higher State',
|
||||||
|
priority=200,
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(self.user.discord)
|
||||||
|
higher_state.member_characters.add(self.test_character)
|
||||||
|
self._refresh_user()
|
||||||
|
self.assertEquals(higher_state, self.user.profile.state)
|
||||||
|
with self.assertRaises(DiscordUser.DoesNotExist):
|
||||||
|
self.user.discord
|
||||||
|
higher_state.member_characters.clear()
|
||||||
|
self._refresh_user()
|
||||||
|
self.assertEquals(self.member_state, self.user.profile.state)
|
||||||
|
with self.assertRaises(DiscordUser.DoesNotExist):
|
||||||
|
self.user.discord
|
||||||
|
|
||||||
|
def test_perm_changes_to_lower_priority_state_creation(self, requests_mocker):
|
||||||
|
mock_url = DiscordRequest(
|
||||||
|
method='DELETE',
|
||||||
|
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/12345678910'
|
||||||
|
)
|
||||||
|
requests_mocker.delete(mock_url.url, status_code=204)
|
||||||
|
self._add_discord_user()
|
||||||
|
self._refresh_user()
|
||||||
|
lower_state = State.objects.create(
|
||||||
|
name='Lower State',
|
||||||
|
priority=125,
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(self.user.discord)
|
||||||
|
lower_state.member_characters.add(self.test_character)
|
||||||
|
self._refresh_user()
|
||||||
|
self.assertEquals(self.member_state, self.user.profile.state)
|
||||||
|
self.member_state.member_characters.clear()
|
||||||
|
self._refresh_user()
|
||||||
|
self.assertEquals(lower_state, self.user.profile.state)
|
||||||
|
with self.assertRaises(DiscordUser.DoesNotExist):
|
||||||
|
self.user.discord
|
||||||
|
self.member_state.member_characters.add(self.test_character)
|
||||||
|
self._refresh_user()
|
||||||
|
self.assertEquals(self.member_state, self.user.profile.state)
|
||||||
|
with self.assertRaises(DiscordUser.DoesNotExist):
|
||||||
|
self.user.discord
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.managers.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
@patch(MODULE_PATH + '.managers.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
||||||
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
@@ -282,6 +437,7 @@ class TestUserFeatures(WebTest):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
clear_cache()
|
clear_cache()
|
||||||
|
reset_testdata()
|
||||||
self.member = AuthUtils.create_member(TEST_USER_NAME)
|
self.member = AuthUtils.create_member(TEST_USER_NAME)
|
||||||
AuthUtils.add_main_character_2(
|
AuthUtils.add_main_character_2(
|
||||||
self.member,
|
self.member,
|
||||||
@@ -290,25 +446,26 @@ class TestUserFeatures(WebTest):
|
|||||||
disconnect_signals=True
|
disconnect_signals=True
|
||||||
)
|
)
|
||||||
add_permissions_to_members()
|
add_permissions_to_members()
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.views.messages')
|
@patch(MODULE_PATH + '.views.messages')
|
||||||
@patch(MODULE_PATH + '.managers.OAuth2Session')
|
@patch(MODULE_PATH + '.managers.OAuth2Session')
|
||||||
def test_user_activation_normal(
|
def test_user_activation_normal(
|
||||||
self, requests_mocker, mock_OAuth2Session, mock_messages
|
self, requests_mocker, mock_OAuth2Session, mock_messages
|
||||||
):
|
):
|
||||||
# user_get_current()
|
# setup
|
||||||
|
requests_mocker.get(
|
||||||
|
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
|
||||||
|
)
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
user_get_current_request.url,
|
user_get_current_request.url,
|
||||||
json=create_user_info(
|
json=create_user_info(
|
||||||
TEST_USER_ID, TEST_USER_NAME, TEST_USER_DISCRIMINATOR
|
TEST_USER_ID, TEST_USER_NAME, TEST_USER_DISCRIMINATOR
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# guild_roles()
|
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_roles_request.url,
|
guild_roles_request.url,
|
||||||
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
|
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
|
||||||
)
|
)
|
||||||
# add_guild_member()
|
|
||||||
requests_mocker.put(add_guild_member_request.url, status_code=201)
|
requests_mocker.put(add_guild_member_request.url, status_code=201)
|
||||||
|
|
||||||
authentication_code = 'auth_code'
|
authentication_code = 'auth_code'
|
||||||
@@ -320,8 +477,12 @@ class TestUserFeatures(WebTest):
|
|||||||
# login
|
# login
|
||||||
self.app.set_user(self.member)
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
# click activate on the service page
|
# user opens services page
|
||||||
response = self.app.get(reverse('discord:activate'))
|
services_page = self.app.get(reverse('services:services'))
|
||||||
|
self.assertEqual(services_page.status_code, 200)
|
||||||
|
|
||||||
|
# user clicks Discord service activation link on page
|
||||||
|
response = services_page.click(href=reverse('discord:activate'))
|
||||||
|
|
||||||
# check we got a redirect to Discord OAuth
|
# check we got a redirect to Discord OAuth
|
||||||
self.assertRedirects(
|
self.assertRedirects(
|
||||||
@@ -343,7 +504,10 @@ class TestUserFeatures(WebTest):
|
|||||||
requests_made.append(obj)
|
requests_made.append(obj)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
user_get_current_request, guild_roles_request, add_guild_member_request
|
guild_infos_request,
|
||||||
|
user_get_current_request,
|
||||||
|
guild_roles_request,
|
||||||
|
add_guild_member_request
|
||||||
]
|
]
|
||||||
self.assertListEqual(requests_made, expected)
|
self.assertListEqual(requests_made, expected)
|
||||||
|
|
||||||
@@ -352,19 +516,21 @@ class TestUserFeatures(WebTest):
|
|||||||
def test_user_activation_failed(
|
def test_user_activation_failed(
|
||||||
self, requests_mocker, mock_OAuth2Session, mock_messages
|
self, requests_mocker, mock_OAuth2Session, mock_messages
|
||||||
):
|
):
|
||||||
# user_get_current()
|
# setup
|
||||||
|
requests_mocker.get(
|
||||||
|
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
|
||||||
|
)
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
user_get_current_request.url,
|
user_get_current_request.url,
|
||||||
json=create_user_info(
|
json=create_user_info(
|
||||||
TEST_USER_ID, TEST_USER_NAME, TEST_USER_DISCRIMINATOR
|
TEST_USER_ID, TEST_USER_NAME, TEST_USER_DISCRIMINATOR
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# guild_roles()
|
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_roles_request.url,
|
guild_roles_request.url,
|
||||||
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
|
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
|
||||||
)
|
)
|
||||||
# add_guild_member()
|
|
||||||
mock_exception = HTTPError('error')
|
mock_exception = HTTPError('error')
|
||||||
mock_exception.response = Mock()
|
mock_exception.response = Mock()
|
||||||
mock_exception.response.status_code = 503
|
mock_exception.response.status_code = 503
|
||||||
@@ -378,9 +544,13 @@ class TestUserFeatures(WebTest):
|
|||||||
|
|
||||||
# login
|
# login
|
||||||
self.app.set_user(self.member)
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
|
# user opens services page
|
||||||
|
services_page = self.app.get(reverse('services:services'))
|
||||||
|
self.assertEqual(services_page.status_code, 200)
|
||||||
|
|
||||||
# click activate on the service page
|
# click activate on the service page
|
||||||
response = self.app.get(reverse('discord:activate'))
|
response = services_page.click(href=reverse('discord:activate'))
|
||||||
|
|
||||||
# check we got a redirect to Discord OAuth
|
# check we got a redirect to Discord OAuth
|
||||||
self.assertRedirects(
|
self.assertRedirects(
|
||||||
@@ -402,27 +572,31 @@ class TestUserFeatures(WebTest):
|
|||||||
requests_made.append(obj)
|
requests_made.append(obj)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
user_get_current_request, guild_roles_request, add_guild_member_request
|
guild_infos_request,
|
||||||
|
user_get_current_request,
|
||||||
|
guild_roles_request,
|
||||||
|
add_guild_member_request
|
||||||
]
|
]
|
||||||
self.assertListEqual(requests_made, expected)
|
self.assertListEqual(requests_made, expected)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.views.messages')
|
@patch(MODULE_PATH + '.views.messages')
|
||||||
def test_user_deactivation_normal(self, requests_mocker, mock_messages):
|
def test_user_deactivation_normal(self, requests_mocker, mock_messages):
|
||||||
# guild_infos()
|
# setup
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'})
|
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
|
||||||
|
)
|
||||||
# remove_guild_member()
|
|
||||||
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
|
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
|
||||||
|
|
||||||
# user needs have an account
|
|
||||||
DiscordUser.objects.create(user=self.member, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.member, uid=TEST_USER_ID)
|
||||||
|
|
||||||
# login
|
# login
|
||||||
self.app.set_user(self.member)
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
# click deactivate on the service page
|
# user opens services page
|
||||||
response = self.app.get(reverse('discord:deactivate'))
|
services_page = self.app.get(reverse('services:services'))
|
||||||
|
self.assertEqual(services_page.status_code, 200)
|
||||||
|
|
||||||
|
# click deactivate on the service page
|
||||||
|
response = services_page.click(href=reverse('discord:deactivate'))
|
||||||
|
|
||||||
# check we got a redirect to service page
|
# check we got a redirect to service page
|
||||||
self.assertRedirects(response, expected_url=reverse('services:services'))
|
self.assertRedirects(response, expected_url=reverse('services:services'))
|
||||||
@@ -436,29 +610,31 @@ class TestUserFeatures(WebTest):
|
|||||||
obj = DiscordRequest(r.method, r.url)
|
obj = DiscordRequest(r.method, r.url)
|
||||||
requests_made.append(obj)
|
requests_made.append(obj)
|
||||||
|
|
||||||
expected = [remove_guild_member_request, guild_infos_request]
|
expected = [guild_infos_request, remove_guild_member_request]
|
||||||
self.assertListEqual(requests_made, expected)
|
self.assertListEqual(requests_made, expected)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.views.messages')
|
@patch(MODULE_PATH + '.views.messages')
|
||||||
def test_user_deactivation_fails(self, requests_mocker, mock_messages):
|
def test_user_deactivation_fails(self, requests_mocker, mock_messages):
|
||||||
# guild_infos()
|
# setup
|
||||||
requests_mocker.get(
|
requests_mocker.get(
|
||||||
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'})
|
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
|
||||||
|
)
|
||||||
# remove_guild_member()
|
|
||||||
mock_exception = HTTPError('error')
|
mock_exception = HTTPError('error')
|
||||||
mock_exception.response = Mock()
|
mock_exception.response = Mock()
|
||||||
mock_exception.response.status_code = 503
|
mock_exception.response.status_code = 503
|
||||||
requests_mocker.delete(remove_guild_member_request.url, exc=mock_exception)
|
requests_mocker.delete(remove_guild_member_request.url, exc=mock_exception)
|
||||||
|
|
||||||
# user needs have an account
|
|
||||||
DiscordUser.objects.create(user=self.member, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.member, uid=TEST_USER_ID)
|
||||||
|
|
||||||
# login
|
# login
|
||||||
self.app.set_user(self.member)
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
# click deactivate on the service page
|
# user opens services page
|
||||||
response = self.app.get(reverse('discord:deactivate'))
|
services_page = self.app.get(reverse('services:services'))
|
||||||
|
self.assertEqual(services_page.status_code, 200)
|
||||||
|
|
||||||
|
# click deactivate on the service page
|
||||||
|
response = services_page.click(href=reverse('discord:deactivate'))
|
||||||
|
|
||||||
# check we got a redirect to service page
|
# check we got a redirect to service page
|
||||||
self.assertRedirects(response, expected_url=reverse('services:services'))
|
self.assertRedirects(response, expected_url=reverse('services:services'))
|
||||||
@@ -472,5 +648,60 @@ class TestUserFeatures(WebTest):
|
|||||||
obj = DiscordRequest(r.method, r.url)
|
obj = DiscordRequest(r.method, r.url)
|
||||||
requests_made.append(obj)
|
requests_made.append(obj)
|
||||||
|
|
||||||
expected = [remove_guild_member_request, guild_infos_request]
|
expected = [guild_infos_request, remove_guild_member_request]
|
||||||
self.assertListEqual(requests_made, expected)
|
self.assertListEqual(requests_made, expected)
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.views.messages')
|
||||||
|
def test_user_add_new_server(self, requests_mocker, mock_messages):
|
||||||
|
# setup
|
||||||
|
mock_exception = HTTPError(Mock(**{"response.status_code": 400}))
|
||||||
|
requests_mocker.get(guild_infos_request.url, exc=mock_exception)
|
||||||
|
|
||||||
|
# login
|
||||||
|
self.member.is_superuser = True
|
||||||
|
self.member.is_staff = True
|
||||||
|
self.member.save()
|
||||||
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
|
# click deactivate on the service page
|
||||||
|
response = self.app.get(reverse('services:services'))
|
||||||
|
|
||||||
|
# check we got can see the page and the "link server" button
|
||||||
|
self.assertEqual(response.status_int, 200)
|
||||||
|
self.assertIsNotNone(response.html.find(id='btnLinkDiscordServer'))
|
||||||
|
|
||||||
|
def test_when_server_name_fails_user_can_still_see_service_page(
|
||||||
|
self, requests_mocker
|
||||||
|
):
|
||||||
|
# setup
|
||||||
|
requests_mocker.get(guild_infos_request.url, exc=DiscordApiBackoff(1000))
|
||||||
|
|
||||||
|
# login
|
||||||
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
|
# user opens services page
|
||||||
|
services_page = self.app.get(reverse('services:services'))
|
||||||
|
self.assertEqual(services_page.status_code, 200)
|
||||||
|
|
||||||
|
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||||
|
def test_server_name_is_updated_by_task(
|
||||||
|
self, requests_mocker
|
||||||
|
):
|
||||||
|
# setup
|
||||||
|
requests_mocker.get(
|
||||||
|
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
|
||||||
|
)
|
||||||
|
# run task to update usernames
|
||||||
|
tasks.update_all_usernames()
|
||||||
|
|
||||||
|
# login
|
||||||
|
self.app.set_user(self.member)
|
||||||
|
|
||||||
|
# disable API call to make sure server name is not retrieved from API
|
||||||
|
mock_exception = HTTPError(Mock(**{"response.status_code": 400}))
|
||||||
|
requests_mocker.get(guild_infos_request.url, exc=mock_exception)
|
||||||
|
|
||||||
|
# user opens services page
|
||||||
|
services_page = self.app.get(reverse('services:services'))
|
||||||
|
self.assertEqual(services_page.status_code, 200)
|
||||||
|
self.assertIn("Test Guild", services_page.text)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from . import (
|
|||||||
MODULE_PATH,
|
MODULE_PATH,
|
||||||
ROLE_ALPHA,
|
ROLE_ALPHA,
|
||||||
ROLE_BRAVO,
|
ROLE_BRAVO,
|
||||||
ROLE_CHARLIE
|
ROLE_CHARLIE,
|
||||||
)
|
)
|
||||||
from ..discord_client.tests import create_matched_role
|
from ..discord_client.tests import create_matched_role
|
||||||
from ..app_settings import (
|
from ..app_settings import (
|
||||||
@@ -361,3 +361,61 @@ class TestUserHasAccount(TestCase):
|
|||||||
|
|
||||||
def test_return_false_if_not_called_with_user_object(self):
|
def test_return_false_if_not_called_with_user_object(self):
|
||||||
self.assertFalse(DiscordUser.objects.user_has_account('abc'))
|
self.assertFalse(DiscordUser.objects.user_has_account('abc'))
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
|
||||||
|
@patch(MODULE_PATH + '.managers.logger')
|
||||||
|
class TestServerName(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.user = AuthUtils.create_user(TEST_USER_NAME)
|
||||||
|
|
||||||
|
def test_returns_name_when_api_returns_it(self, mock_logger, mock_DiscordClient):
|
||||||
|
server_name = "El Dorado"
|
||||||
|
mock_DiscordClient.return_value.guild_name.return_value = server_name
|
||||||
|
|
||||||
|
self.assertEqual(DiscordUser.objects.server_name(), server_name)
|
||||||
|
self.assertFalse(mock_logger.warning.called)
|
||||||
|
|
||||||
|
def test_returns_empty_string_when_api_throws_http_error(
|
||||||
|
self, mock_logger, mock_DiscordClient
|
||||||
|
):
|
||||||
|
mock_exception = HTTPError('Test exception')
|
||||||
|
mock_exception.response = Mock(**{"status_code": 440})
|
||||||
|
mock_DiscordClient.return_value.guild_name.side_effect = mock_exception
|
||||||
|
|
||||||
|
self.assertEqual(DiscordUser.objects.server_name(), "")
|
||||||
|
self.assertFalse(mock_logger.warning.called)
|
||||||
|
|
||||||
|
def test_returns_empty_string_when_api_throws_service_error(
|
||||||
|
self, mock_logger, mock_DiscordClient
|
||||||
|
):
|
||||||
|
mock_DiscordClient.return_value.guild_name.side_effect = DiscordApiBackoff(1000)
|
||||||
|
|
||||||
|
self.assertEqual(DiscordUser.objects.server_name(), "")
|
||||||
|
self.assertFalse(mock_logger.warning.called)
|
||||||
|
|
||||||
|
def test_returns_empty_string_when_api_throws_unexpected_error(
|
||||||
|
self, mock_logger, mock_DiscordClient
|
||||||
|
):
|
||||||
|
mock_DiscordClient.return_value.guild_name.side_effect = RuntimeError
|
||||||
|
|
||||||
|
self.assertEqual(DiscordUser.objects.server_name(), "")
|
||||||
|
self.assertTrue(mock_logger.warning.called)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
|
||||||
|
class TestRoleForGroup(TestCase):
|
||||||
|
def test_return_role_if_found(self, mock_DiscordClient):
|
||||||
|
mock_DiscordClient.return_value.match_role_from_name.return_value = ROLE_ALPHA
|
||||||
|
|
||||||
|
group = Group.objects.create(name='alpha')
|
||||||
|
self.assertEqual(DiscordUser.objects.group_to_role(group), ROLE_ALPHA)
|
||||||
|
|
||||||
|
def test_return_empty_dict_if_not_found(self, mock_DiscordClient):
|
||||||
|
mock_DiscordClient.return_value.match_role_from_name.return_value = dict()
|
||||||
|
|
||||||
|
group = Group.objects.create(name='unknown')
|
||||||
|
self.assertEqual(DiscordUser.objects.group_to_role(group), dict())
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ logger = set_logger_to_file(MODULE_PATH, __file__)
|
|||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.DiscordUser.update_groups')
|
@patch(MODULE_PATH + '.DiscordUser.update_groups')
|
||||||
|
@patch(MODULE_PATH + ".logger")
|
||||||
class TestUpdateGroups(TestCase):
|
class TestUpdateGroups(TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -32,16 +33,18 @@ class TestUpdateGroups(TestCase):
|
|||||||
cls.group_1.user_set.add(cls.user)
|
cls.group_1.user_set.add(cls.user)
|
||||||
cls.group_2.user_set.add(cls.user)
|
cls.group_2.user_set.add(cls.user)
|
||||||
|
|
||||||
def test_can_update_groups(self, mock_update_groups):
|
def test_can_update_groups(self, mock_logger, mock_update_groups):
|
||||||
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
tasks.update_groups(self.user.pk)
|
tasks.update_groups(self.user.pk)
|
||||||
self.assertTrue(mock_update_groups.called)
|
self.assertTrue(mock_update_groups.called)
|
||||||
|
|
||||||
def test_no_action_if_user_has_no_discord_account(self, mock_update_groups):
|
def test_no_action_if_user_has_no_discord_account(
|
||||||
|
self, mock_logger, mock_update_groups
|
||||||
|
):
|
||||||
tasks.update_groups(self.user.pk)
|
tasks.update_groups(self.user.pk)
|
||||||
self.assertFalse(mock_update_groups.called)
|
self.assertFalse(mock_update_groups.called)
|
||||||
|
|
||||||
def test_retries_on_api_backoff(self, mock_update_groups):
|
def test_retries_on_api_backoff(self, mock_logger, mock_update_groups):
|
||||||
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
mock_exception = DiscordApiBackoff(999)
|
mock_exception = DiscordApiBackoff(999)
|
||||||
mock_update_groups.side_effect = mock_exception
|
mock_update_groups.side_effect = mock_exception
|
||||||
@@ -49,7 +52,7 @@ class TestUpdateGroups(TestCase):
|
|||||||
with self.assertRaises(Retry):
|
with self.assertRaises(Retry):
|
||||||
tasks.update_groups(self.user.pk)
|
tasks.update_groups(self.user.pk)
|
||||||
|
|
||||||
def test_retry_on_http_error_except_404(self, mock_update_groups):
|
def test_retry_on_http_error_except_404(self, mock_logger, mock_update_groups):
|
||||||
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
mock_exception = HTTPError('error')
|
mock_exception = HTTPError('error')
|
||||||
mock_exception.response = MagicMock()
|
mock_exception.response = MagicMock()
|
||||||
@@ -58,8 +61,12 @@ class TestUpdateGroups(TestCase):
|
|||||||
|
|
||||||
with self.assertRaises(Retry):
|
with self.assertRaises(Retry):
|
||||||
tasks.update_groups(self.user.pk)
|
tasks.update_groups(self.user.pk)
|
||||||
|
|
||||||
|
self.assertTrue(mock_logger.warning.called)
|
||||||
|
|
||||||
def test_retry_on_http_error_404_when_user_not_deleted(self, mock_update_groups):
|
def test_retry_on_http_error_404_when_user_not_deleted(
|
||||||
|
self, mock_logger, mock_update_groups
|
||||||
|
):
|
||||||
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
mock_exception = HTTPError('error')
|
mock_exception = HTTPError('error')
|
||||||
mock_exception.response = MagicMock()
|
mock_exception.response = MagicMock()
|
||||||
@@ -68,26 +75,31 @@ class TestUpdateGroups(TestCase):
|
|||||||
|
|
||||||
with self.assertRaises(Retry):
|
with self.assertRaises(Retry):
|
||||||
tasks.update_groups(self.user.pk)
|
tasks.update_groups(self.user.pk)
|
||||||
|
|
||||||
|
self.assertTrue(mock_logger.warning.called)
|
||||||
|
|
||||||
def test_retry_on_non_http_error(self, mock_update_groups):
|
def test_retry_on_non_http_error(self, mock_logger, mock_update_groups):
|
||||||
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
mock_update_groups.side_effect = ConnectionError
|
mock_update_groups.side_effect = ConnectionError
|
||||||
|
|
||||||
with self.assertRaises(Retry):
|
with self.assertRaises(Retry):
|
||||||
tasks.update_groups(self.user.pk)
|
tasks.update_groups(self.user.pk)
|
||||||
|
|
||||||
|
self.assertTrue(mock_logger.warning.called)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.DISCORD_TASKS_MAX_RETRIES', 3)
|
@patch(MODULE_PATH + '.DISCORD_TASKS_MAX_RETRIES', 3)
|
||||||
def test_log_error_if_retries_exhausted(self, mock_update_groups):
|
def test_log_error_if_retries_exhausted(self, mock_logger, mock_update_groups):
|
||||||
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
mock_task = MagicMock(**{'request.retries': 3})
|
mock_task = MagicMock(**{'request.retries': 3})
|
||||||
mock_update_groups.side_effect = ConnectionError
|
mock_update_groups.side_effect = ConnectionError
|
||||||
update_groups_inner = tasks.update_groups.__wrapped__.__func__
|
update_groups_inner = tasks.update_groups.__wrapped__.__func__
|
||||||
|
|
||||||
update_groups_inner(mock_task, self.user.pk)
|
update_groups_inner(mock_task, self.user.pk)
|
||||||
|
self.assertTrue(mock_logger.error.called)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.delete_user.delay')
|
@patch(MODULE_PATH + '.delete_user.delay')
|
||||||
def test_delete_user_if_user_is_no_longer_member_of_discord_server(
|
def test_delete_user_if_user_is_no_longer_member_of_discord_server(
|
||||||
self, mock_delete_user, mock_update_groups
|
self, mock_delete_user, mock_logger, mock_update_groups
|
||||||
):
|
):
|
||||||
mock_update_groups.return_value = None
|
mock_update_groups.return_value = None
|
||||||
|
|
||||||
@@ -222,6 +234,72 @@ class TestTaskPerformUserAction(TestCase):
|
|||||||
tasks._task_perform_user_action(mock_task, self.user.pk, 'update_groups')
|
tasks._task_perform_user_action(mock_task, self.user.pk, 'update_groups')
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.DiscordUser.objects.server_name')
|
||||||
|
@patch(MODULE_PATH + ".logger")
|
||||||
|
class TestTaskUpdateServername(TestCase):
|
||||||
|
|
||||||
|
def test_normal(self, mock_logger, mock_server_name):
|
||||||
|
tasks.update_servername()
|
||||||
|
self.assertTrue(mock_server_name.called)
|
||||||
|
self.assertFalse(mock_logger.error.called)
|
||||||
|
_, kwargs = mock_server_name.call_args
|
||||||
|
self.assertFalse(kwargs["use_cache"])
|
||||||
|
|
||||||
|
def test_retries_on_api_backoff(self, mock_logger, mock_server_name):
|
||||||
|
mock_server_name.side_effect = DiscordApiBackoff(999)
|
||||||
|
|
||||||
|
with self.assertRaises(Retry):
|
||||||
|
tasks.update_servername()
|
||||||
|
|
||||||
|
self.assertFalse(mock_logger.error.called)
|
||||||
|
|
||||||
|
def test_retry_on_http_error(self, mock_logger, mock_server_name):
|
||||||
|
mock_exception = HTTPError(MagicMock(**{"response.status_code": 500}))
|
||||||
|
mock_server_name.side_effect = mock_exception
|
||||||
|
|
||||||
|
with self.assertRaises(Retry):
|
||||||
|
tasks.update_servername()
|
||||||
|
|
||||||
|
self.assertTrue(mock_logger.warning.called)
|
||||||
|
|
||||||
|
def test_retry_on_connection_error(self, mock_logger, mock_server_name):
|
||||||
|
mock_server_name.side_effect = ConnectionError
|
||||||
|
|
||||||
|
with self.assertRaises(Retry):
|
||||||
|
tasks.update_servername()
|
||||||
|
|
||||||
|
self.assertTrue(mock_logger.warning.called)
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.DISCORD_TASKS_MAX_RETRIES', 3)
|
||||||
|
def test_log_error_if_retries_exhausted(self, mock_logger, mock_server_name):
|
||||||
|
mock_task = MagicMock(**{'request.retries': 3})
|
||||||
|
mock_server_name.side_effect = ConnectionError
|
||||||
|
update_groups_inner = tasks.update_servername.__wrapped__.__func__
|
||||||
|
|
||||||
|
update_groups_inner(mock_task)
|
||||||
|
self.assertTrue(mock_logger.error.called)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.DiscordUser.objects.server_name')
|
||||||
|
class TestTaskPerformUsersAction(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
def test_raise_value_error_on_unknown_method(self, mock_server_name):
|
||||||
|
mock_task = MagicMock(**{'request.retries': 0})
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
tasks._task_perform_users_action(mock_task, 'invalid_method')
|
||||||
|
|
||||||
|
def test_catch_and_log_unexpected_exceptions(self, mock_server_name):
|
||||||
|
mock_server_name.side_effect = RuntimeError
|
||||||
|
mock_task = MagicMock(**{'request.retries': 0})
|
||||||
|
|
||||||
|
tasks._task_perform_users_action(mock_task, 'server_name')
|
||||||
|
|
||||||
|
|
||||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||||
class TestBulkTasks(TestCase):
|
class TestBulkTasks(TestCase):
|
||||||
|
|
||||||
@@ -299,15 +377,19 @@ class TestBulkTasks(TestCase):
|
|||||||
|
|
||||||
self.assertSetEqual(set(current_pks), set(expected_pks))
|
self.assertSetEqual(set(current_pks), set(expected_pks))
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.update_username.si')
|
@patch(MODULE_PATH + '.update_username')
|
||||||
def test_can_update_all_usernames(self, mock_update_username):
|
@patch(MODULE_PATH + '.update_servername')
|
||||||
|
def test_can_update_all_usernames(
|
||||||
|
self, mock_update_servername, mock_update_username
|
||||||
|
):
|
||||||
du_1 = DiscordUser.objects.create(user=self.user_1, uid=123)
|
du_1 = DiscordUser.objects.create(user=self.user_1, uid=123)
|
||||||
du_2 = DiscordUser.objects.create(user=self.user_2, uid=456)
|
du_2 = DiscordUser.objects.create(user=self.user_2, uid=456)
|
||||||
du_3 = DiscordUser.objects.create(user=self.user_3, uid=789)
|
du_3 = DiscordUser.objects.create(user=self.user_3, uid=789)
|
||||||
|
|
||||||
tasks.update_all_usernames()
|
tasks.update_all_usernames()
|
||||||
self.assertEqual(mock_update_username.call_count, 3)
|
self.assertTrue(mock_update_servername.delay.called)
|
||||||
current_pks = [args[0][0] for args in mock_update_username.call_args_list]
|
self.assertEqual(mock_update_username.si.call_count, 3)
|
||||||
|
current_pks = [args[0][0] for args in mock_update_username.si.call_args_list]
|
||||||
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
|
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
|
||||||
self.assertSetEqual(set(current_pks), set(expected_pks))
|
self.assertSetEqual(set(current_pks), set(expected_pks))
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ class ExampleService(ServicesHook):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
urls = self.Urls()
|
urls = self.Urls()
|
||||||
urls.auth_activate = 'auth_example_activate'
|
# urls.auth_activate = 'auth_example_activate'
|
||||||
urls.auth_deactivate = 'auth_example_deactivate'
|
# urls.auth_deactivate = 'auth_example_deactivate'
|
||||||
urls.auth_reset_password = 'auth_example_reset_password'
|
# urls.auth_reset_password = 'auth_example_reset_password'
|
||||||
urls.auth_set_password = 'auth_example_set_password'
|
# urls.auth_set_password = 'auth_example_set_password'
|
||||||
return render_to_string(self.service_ctrl_template, {
|
return render_to_string(self.service_ctrl_template, {
|
||||||
'service_name': self.title,
|
'service_name': self.title,
|
||||||
'urls': urls,
|
'urls': urls,
|
||||||
|
|||||||
@@ -123,17 +123,12 @@ def m2m_changed_state_permissions(sender, instance, action, pk_set, *args, **kwa
|
|||||||
logger.debug("Permission change for state {} was not service permission, ignoring".format(instance))
|
logger.debug("Permission change for state {} was not service permission, ignoring".format(instance))
|
||||||
|
|
||||||
|
|
||||||
@receiver(state_changed, sender=UserProfile)
|
@receiver(state_changed)
|
||||||
def check_service_accounts_state_changed(sender, user, state, **kwargs):
|
def check_service_accounts_state_changed(sender, user, state, **kwargs):
|
||||||
logger.debug("Received state_changed from %s to state %s" % (user, state))
|
logger.debug("Received state_changed from %s to state %s" % (user, state))
|
||||||
service_perms = [svc.access_perm for svc in ServicesHook.get_services()]
|
for svc in ServicesHook.get_services():
|
||||||
state_perms = ["{}.{}".format(perm.natural_key()[1], perm.natural_key()[0]) for perm in state.permissions.all()]
|
svc.validate_user(user)
|
||||||
for perm in service_perms:
|
svc.update_groups(user)
|
||||||
if perm not in state_perms:
|
|
||||||
for svc in ServicesHook.get_services():
|
|
||||||
if svc.access_perm == perm:
|
|
||||||
logger.debug("User %s new state %s does not have service %s permission. Checking account." % (user, state, svc))
|
|
||||||
svc.validate_user(user)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=User)
|
@receiver(pre_delete, sender=User)
|
||||||
@@ -159,24 +154,37 @@ def disable_services_on_inactive(sender, instance, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=UserProfile)
|
@receiver(pre_save, sender=UserProfile)
|
||||||
def process_main_character_change(sender, instance, *args, **kwargs):
|
def process_main_character_change(sender, instance, *args, **kwargs):
|
||||||
|
if not instance.pk:
|
||||||
if not instance.pk: # ignore
|
# ignore new model being created
|
||||||
# new model being created
|
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
logger.debug(
|
||||||
|
"Received pre_save from %s for process_main_character_change", instance
|
||||||
|
)
|
||||||
old_instance = UserProfile.objects.get(pk=instance.pk)
|
old_instance = UserProfile.objects.get(pk=instance.pk)
|
||||||
if old_instance.main_character and not instance.main_character: # lost main char disable services
|
if old_instance.main_character and not instance.main_character:
|
||||||
logger.info("Disabling services due to loss of main character for user {0}".format(instance.user))
|
logger.info(
|
||||||
|
"Disabling services due to loss of main character for user %s",
|
||||||
|
instance.user
|
||||||
|
)
|
||||||
disable_user(instance.user)
|
disable_user(instance.user)
|
||||||
elif old_instance.main_character is not instance.main_character: # swapping/changing main character
|
elif old_instance.main_character != instance.main_character:
|
||||||
logger.info("Updating Names due to change of main character for user {0}".format(instance.user))
|
logger.info(
|
||||||
|
"Updating Names due to change of main character for user %s",
|
||||||
|
instance.user
|
||||||
|
)
|
||||||
for svc in ServicesHook.get_services():
|
for svc in ServicesHook.get_services():
|
||||||
try:
|
try:
|
||||||
svc.validate_user(instance.user)
|
svc.validate_user(instance.user)
|
||||||
svc.sync_nickname(instance.user)
|
svc.sync_nickname(instance.user)
|
||||||
except:
|
except:
|
||||||
logger.exception('Exception running sync_nickname for services module %s on user %s' % (svc, instance))
|
logger.exception(
|
||||||
|
"Exception running sync_nickname for services module %s "
|
||||||
|
"on user %s",
|
||||||
|
svc,
|
||||||
|
instance
|
||||||
|
)
|
||||||
|
|
||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
@@ -184,8 +192,12 @@ def process_main_character_change(sender, instance, *args, **kwargs):
|
|||||||
|
|
||||||
@receiver(pre_save, sender=EveCharacter)
|
@receiver(pre_save, sender=EveCharacter)
|
||||||
def process_main_character_update(sender, instance, *args, **kwargs):
|
def process_main_character_update(sender, instance, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
if instance.userprofile:
|
if instance.userprofile:
|
||||||
|
logger.debug(
|
||||||
|
"Received pre_save from %s for process_main_character_update",
|
||||||
|
instance
|
||||||
|
)
|
||||||
old_instance = EveCharacter.objects.get(pk=instance.pk)
|
old_instance = EveCharacter.objects.get(pk=instance.pk)
|
||||||
if not instance.character_name == old_instance.character_name or \
|
if not instance.character_name == old_instance.character_name or \
|
||||||
not instance.corporation_name == old_instance.corporation_name or \
|
not instance.corporation_name == old_instance.corporation_name or \
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from django.contrib.auth.models import User
|
|||||||
from .hooks import ServicesHook
|
from .hooks import ServicesHook
|
||||||
from celery_once import QueueOnce as BaseTask, AlreadyQueued
|
from celery_once import QueueOnce as BaseTask, AlreadyQueued
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from time import time
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -22,14 +21,9 @@ class DjangoBackend:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def raise_or_lock(key, timeout):
|
def raise_or_lock(key, timeout):
|
||||||
now = int(time())
|
acquired = cache.add(key=key, value="lock", timeout=timeout)
|
||||||
result = cache.get(key)
|
if not acquired:
|
||||||
if result:
|
raise AlreadyQueued(int(cache.ttl(key)))
|
||||||
remaining = int(result) - now
|
|
||||||
if remaining > 0:
|
|
||||||
raise AlreadyQueued(remaining)
|
|
||||||
else:
|
|
||||||
cache.set(key, now + timeout, timeout)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_lock(key):
|
def clear_lock(key):
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
|
from copy import deepcopy
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import Group, Permission
|
from django.contrib.auth.models import Group, Permission
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
|
||||||
from allianceauth.authentication.models import State
|
from allianceauth.authentication.models import State
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
|
||||||
class ServicesSignalsTestCase(TestCase):
|
class ServicesSignalsTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.member = AuthUtils.create_user('auth_member', disconnect_signals=True)
|
self.member = AuthUtils.create_user('auth_member', disconnect_signals=True)
|
||||||
AuthUtils.add_main_character(self.member, 'Test', '1', '2', 'Test Corp', 'TEST')
|
AuthUtils.add_main_character_2(
|
||||||
|
self.member, 'Test', 1, 2, 'Test Corp', 'TEST'
|
||||||
|
)
|
||||||
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
||||||
|
|
||||||
@mock.patch('allianceauth.services.signals.transaction')
|
@mock.patch('allianceauth.services.signals.transaction')
|
||||||
@@ -46,7 +51,6 @@ class ServicesSignalsTestCase(TestCase):
|
|||||||
|
|
||||||
@mock.patch('allianceauth.services.signals.disable_user')
|
@mock.patch('allianceauth.services.signals.disable_user')
|
||||||
def test_pre_delete_user(self, disable_user):
|
def test_pre_delete_user(self, disable_user):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Test that disable_member is called when a user is deleted
|
Test that disable_member is called when a user is deleted
|
||||||
"""
|
"""
|
||||||
@@ -126,7 +130,9 @@ class ServicesSignalsTestCase(TestCase):
|
|||||||
transaction.on_commit = lambda fn: fn()
|
transaction.on_commit = lambda fn: fn()
|
||||||
|
|
||||||
ct = ContentType.objects.get(app_label='auth', model='permission')
|
ct = ContentType.objects.get(app_label='auth', model='permission')
|
||||||
perm = Permission.objects.create(name="Test perm", codename="access_testsvc", content_type=ct)
|
perm = Permission.objects.create(
|
||||||
|
name="Test perm", codename="access_testsvc", content_type=ct
|
||||||
|
)
|
||||||
self.member.user_permissions.add(perm)
|
self.member.user_permissions.add(perm)
|
||||||
|
|
||||||
# Act, should trigger m2m change
|
# Act, should trigger m2m change
|
||||||
@@ -159,7 +165,9 @@ class ServicesSignalsTestCase(TestCase):
|
|||||||
AuthUtils.connect_signals()
|
AuthUtils.connect_signals()
|
||||||
|
|
||||||
ct = ContentType.objects.get(app_label='auth', model='permission')
|
ct = ContentType.objects.get(app_label='auth', model='permission')
|
||||||
perm = Permission.objects.create(name="Test perm", codename="access_testsvc", content_type=ct)
|
perm = Permission.objects.create(
|
||||||
|
name="Test perm", codename="access_testsvc", content_type=ct
|
||||||
|
)
|
||||||
test_state.permissions.add(perm)
|
test_state.permissions.add(perm)
|
||||||
|
|
||||||
# Act, should trigger m2m change
|
# Act, should trigger m2m change
|
||||||
@@ -173,12 +181,12 @@ class ServicesSignalsTestCase(TestCase):
|
|||||||
self.assertEqual(self.member, args[0])
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
@mock.patch('allianceauth.services.signals.ServicesHook')
|
@mock.patch('allianceauth.services.signals.ServicesHook')
|
||||||
def test_state_changed_services_valudation(self, services_hook):
|
def test_state_changed_services_validation_and_groups_update(self, services_hook):
|
||||||
"""
|
"""Test a user changing state has service accounts validated and groups updated
|
||||||
Test a user changing state has service accounts validated
|
|
||||||
"""
|
"""
|
||||||
svc = mock.Mock()
|
svc = mock.Mock()
|
||||||
svc.validate_user.return_value = None
|
svc.validate_user.return_value = None
|
||||||
|
svc.update_groups.return_value = None
|
||||||
svc.access_perm = 'auth.access_testsvc'
|
svc.access_perm = 'auth.access_testsvc'
|
||||||
|
|
||||||
services_hook.get_services.return_value = [svc]
|
services_hook.get_services.return_value = [svc]
|
||||||
@@ -190,6 +198,65 @@ class ServicesSignalsTestCase(TestCase):
|
|||||||
# Assert
|
# Assert
|
||||||
self.assertTrue(services_hook.get_services.called)
|
self.assertTrue(services_hook.get_services.called)
|
||||||
|
|
||||||
self.assertTrue(svc.validate_user.called)
|
self.assertTrue(svc.validate_user.called)
|
||||||
args, kwargs = svc.validate_user.call_args
|
args, kwargs = svc.validate_user.call_args
|
||||||
self.assertEqual(self.member, args[0])
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
|
self.assertTrue(svc.update_groups.called)
|
||||||
|
args, kwargs = svc.update_groups.call_args
|
||||||
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('allianceauth.services.signals.ServicesHook')
|
||||||
|
def test_state_changed_services_validation_and_groups_update_1(self, services_hook):
|
||||||
|
"""Test a user changing main has service accounts validated and sync updated
|
||||||
|
"""
|
||||||
|
svc = mock.Mock()
|
||||||
|
svc.validate_user.return_value = None
|
||||||
|
svc.sync_nickname.return_value = None
|
||||||
|
svc.access_perm = 'auth.access_testsvc'
|
||||||
|
|
||||||
|
services_hook.get_services.return_value = [svc]
|
||||||
|
|
||||||
|
new_main = EveCharacter.objects.create(
|
||||||
|
character_id=123,
|
||||||
|
character_name="Alter Ego",
|
||||||
|
corporation_id=987,
|
||||||
|
corporation_name="ABC"
|
||||||
|
)
|
||||||
|
self.member.profile.main_character = new_main
|
||||||
|
self.member.profile.save()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertTrue(services_hook.get_services.called)
|
||||||
|
|
||||||
|
self.assertTrue(svc.validate_user.called)
|
||||||
|
args, kwargs = svc.validate_user.call_args
|
||||||
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
|
self.assertTrue(svc.sync_nickname.called)
|
||||||
|
args, kwargs = svc.sync_nickname.call_args
|
||||||
|
self.assertEqual(self.member, args[0])
|
||||||
|
|
||||||
|
@mock.patch('allianceauth.services.signals.ServicesHook')
|
||||||
|
def test_state_changed_services_validation_and_groups_update_2(self, services_hook):
|
||||||
|
"""Test a user changing main has service does not have accounts validated
|
||||||
|
and sync updated if the new main is equal to the old main
|
||||||
|
"""
|
||||||
|
svc = mock.Mock()
|
||||||
|
svc.validate_user.return_value = None
|
||||||
|
svc.sync_nickname.return_value = None
|
||||||
|
svc.access_perm = 'auth.access_testsvc'
|
||||||
|
|
||||||
|
services_hook.get_services.return_value = [svc]
|
||||||
|
|
||||||
|
# this creates a clone of the Django object
|
||||||
|
new_main = deepcopy(self.member.profile.main_character)
|
||||||
|
self.assertIsNot(new_main, self.member.profile.main_character)
|
||||||
|
self.member.profile.main_character = new_main
|
||||||
|
self.member.profile.save()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertFalse(services_hook.get_services.called)
|
||||||
|
self.assertFalse(svc.validate_user.called)
|
||||||
|
self.assertFalse(svc.sync_nickname.called)
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from celery_once import AlreadyQueued
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from allianceauth.services.tasks import validate_services
|
from allianceauth.services.tasks import validate_services
|
||||||
|
|
||||||
|
from ..tasks import DjangoBackend
|
||||||
|
|
||||||
|
|
||||||
class ServicesTasksTestCase(TestCase):
|
class ServicesTasksTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -24,3 +28,46 @@ class ServicesTasksTestCase(TestCase):
|
|||||||
self.assertTrue(svc.validate_user.called)
|
self.assertTrue(svc.validate_user.called)
|
||||||
args, kwargs = svc.validate_user.call_args
|
args, kwargs = svc.validate_user.call_args
|
||||||
self.assertEqual(self.member, args[0]) # Assert correct user is passed to service hook function
|
self.assertEqual(self.member, args[0]) # Assert correct user is passed to service hook function
|
||||||
|
|
||||||
|
|
||||||
|
class TestDjangoBackend(TestCase):
|
||||||
|
|
||||||
|
TEST_KEY = "my-django-backend-test-key"
|
||||||
|
TIMEOUT = 1800
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
cache.delete(self.TEST_KEY)
|
||||||
|
self.backend = DjangoBackend(dict())
|
||||||
|
|
||||||
|
def test_can_get_lock(self):
|
||||||
|
"""
|
||||||
|
when lock can be acquired
|
||||||
|
then set it with timetout
|
||||||
|
"""
|
||||||
|
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||||
|
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||||
|
self.assertAlmostEqual(cache.ttl(self.TEST_KEY), self.TIMEOUT, delta=2)
|
||||||
|
|
||||||
|
def test_when_cant_get_lock_raise_exception(self):
|
||||||
|
"""
|
||||||
|
when lock can bot be acquired
|
||||||
|
then raise AlreadyQueued exception with countdown
|
||||||
|
"""
|
||||||
|
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||||
|
except Exception as ex:
|
||||||
|
self.assertIsInstance(ex, AlreadyQueued)
|
||||||
|
self.assertAlmostEqual(ex.countdown, self.TIMEOUT, delta=2)
|
||||||
|
|
||||||
|
def test_can_clear_lock(self):
|
||||||
|
"""
|
||||||
|
when a lock exists
|
||||||
|
then can get a new lock after clearing it
|
||||||
|
"""
|
||||||
|
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||||
|
|
||||||
|
self.backend.clear_lock(self.TEST_KEY)
|
||||||
|
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||||
|
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
<!-- Font Awesome Bundle -->
|
<!-- Font Awesome Bundle -->
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" rel="stylesheet" type="text/css">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" rel="stylesheet" type="text/css">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/v4-shims.min.css" rel="stylesheet" type="text/css">
|
|
||||||
<!-- End Font Awesome Bundle -->
|
<!-- End Font Awesome Bundle -->
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ GITLAB_AUTH_ANNOUNCEMENT_ISSUES_URL = (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('allianceauth/admin-status/overview.html', takes_context=True)
|
@register.inclusion_tag('allianceauth/admin-status/overview.html')
|
||||||
def status_overview(context):
|
def status_overview() -> dict:
|
||||||
response = {
|
response = {
|
||||||
'notifications': list(),
|
'notifications': list(),
|
||||||
'current_version': __version__,
|
'current_version': __version__,
|
||||||
@@ -46,7 +46,7 @@ def status_overview(context):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _fetch_celery_queue_length():
|
def _fetch_celery_queue_length() -> int:
|
||||||
try:
|
try:
|
||||||
app = app_or_default(None)
|
app = app_or_default(None)
|
||||||
with app.connection_or_acquire() as conn:
|
with app.connection_or_acquire() as conn:
|
||||||
@@ -69,11 +69,15 @@ def _current_notifications() -> dict:
|
|||||||
'gitlab_notification_issues',
|
'gitlab_notification_issues',
|
||||||
_fetch_notification_issues_from_gitlab,
|
_fetch_notification_issues_from_gitlab,
|
||||||
NOTIFICATION_CACHE_TIME
|
NOTIFICATION_CACHE_TIME
|
||||||
)
|
)
|
||||||
top_notifications = notifications[:5]
|
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
logger.exception('Error while getting gitlab notifications')
|
logger.exception('Error while getting gitlab notifications')
|
||||||
top_notifications = []
|
top_notifications = []
|
||||||
|
else:
|
||||||
|
if notifications:
|
||||||
|
top_notifications = notifications[:5]
|
||||||
|
else:
|
||||||
|
top_notifications = []
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
'notifications': top_notifications,
|
'notifications': top_notifications,
|
||||||
@@ -95,8 +99,15 @@ def _current_version_summary() -> dict:
|
|||||||
logger.exception('Error while getting gitlab release tags')
|
logger.exception('Error while getting gitlab release tags')
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
latest_major_version, latest_minor_version, latest_patch_version, latest_beta_version = \
|
if not tags:
|
||||||
_latests_versions(tags)
|
return {}
|
||||||
|
|
||||||
|
(
|
||||||
|
latest_major_version,
|
||||||
|
latest_minor_version,
|
||||||
|
latest_patch_version,
|
||||||
|
latest_beta_version
|
||||||
|
) = _latests_versions(tags)
|
||||||
current_version = Pep440Version(__version__)
|
current_version = Pep440Version(__version__)
|
||||||
|
|
||||||
has_latest_major = \
|
has_latest_major = \
|
||||||
@@ -107,8 +118,8 @@ def _current_version_summary() -> dict:
|
|||||||
current_version >= latest_patch_version if latest_patch_version else False
|
current_version >= latest_patch_version if latest_patch_version else False
|
||||||
has_current_beta = \
|
has_current_beta = \
|
||||||
current_version.base_version <= latest_beta_version.base_version \
|
current_version.base_version <= latest_beta_version.base_version \
|
||||||
and latest_major_version.base_version <= latest_beta_version.base_version \
|
and latest_major_version.base_version <= latest_beta_version.base_version \
|
||||||
if latest_beta_version else False
|
if latest_beta_version else False
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
'latest_major': has_latest_major,
|
'latest_major': has_latest_major,
|
||||||
@@ -146,7 +157,6 @@ def _latests_versions(tags: list) -> tuple:
|
|||||||
else:
|
else:
|
||||||
versions.append(version)
|
versions.append(version)
|
||||||
|
|
||||||
|
|
||||||
latest_version = latest_patch_version = max(versions)
|
latest_version = latest_patch_version = max(versions)
|
||||||
latest_major_version = min([
|
latest_major_version = min([
|
||||||
v for v in versions if v.major == latest_version.major
|
v for v in versions if v.major == latest_version.major
|
||||||
@@ -156,10 +166,15 @@ def _latests_versions(tags: list) -> tuple:
|
|||||||
if v.major == latest_version.major and v.minor == latest_version.minor
|
if v.major == latest_version.major and v.minor == latest_version.minor
|
||||||
])
|
])
|
||||||
latest_beta_version = max(betas)
|
latest_beta_version = max(betas)
|
||||||
return latest_major_version, latest_minor_version, latest_patch_version, latest_beta_version
|
return (
|
||||||
|
latest_major_version,
|
||||||
|
latest_minor_version,
|
||||||
|
latest_patch_version,
|
||||||
|
latest_beta_version
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _fetch_list_from_gitlab(url: str, max_pages: int = MAX_PAGES):
|
def _fetch_list_from_gitlab(url: str, max_pages: int = MAX_PAGES) -> list:
|
||||||
"""returns a list from the GitLab API. Supports pageing"""
|
"""returns a list from the GitLab API. Supports pageing"""
|
||||||
result = list()
|
result = list()
|
||||||
for page in range(1, max_pages + 1):
|
for page in range(1, max_pages + 1):
|
||||||
|
|||||||
BIN
allianceauth_model.png
Normal file
BIN
allianceauth_model.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
45
docs/contributing/index.md
Normal file
45
docs/contributing/index.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Alliance Auth is developed by the community and we are always looking to welcome new contributors. If you are interested in contributing, here are some ideas where to start:
|
||||||
|
|
||||||
|
## Publish a new community app or service
|
||||||
|
|
||||||
|
One great way to contribute is to develop and publish your own community app or service for Alliance Auth. By design Auth only comes with some basic features and therefore heavily relies on the community to provide apps to extend Auth with additional features.
|
||||||
|
|
||||||
|
To publish your app make sure it can be installed from a public repo or PyPI. Once it's ready, you can inform everybody about your new app by posting it to our [list of community apps](/features/community/index.md).
|
||||||
|
|
||||||
|
If you are looking for ideas on what to make, you can check out Auth's [issue list](https://gitlab.com/allianceauth/allianceauth/-/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=enhancement). Many of those issues are feature requests that will probably never make into Auth core, but would be awesome to have as community app or service. You could also ask the other devs on our Discord server for ideas or to help you get a feeling about which new features might be in higher demand than others.
|
||||||
|
|
||||||
|
## Help to maintain an existing community app or service
|
||||||
|
|
||||||
|
There are quite a few great community apps that need help from additional maintainers. Often the initial author has no time anymore to support his app or would just appreciate some support for working on new features or to fix bugs.
|
||||||
|
|
||||||
|
Sometimes original app owners may even be looking to completely hand over their apps to a new owner.
|
||||||
|
|
||||||
|
If you are interested to help maintain an existing community app or service you can just start working on open issues and create merge requests. Or just ask other devs on our Discord.
|
||||||
|
|
||||||
|
## Help with improving Auth documentation
|
||||||
|
|
||||||
|
Auth has an extensive [documentation](https://allianceauth.readthedocs.io/en/latest/), but there are always things to improve and add. If you notice any errors or see something to improve or add please feel free to issue a change for the documentation (via MRs same as code changes).
|
||||||
|
|
||||||
|
## Help with support questions on Discord
|
||||||
|
|
||||||
|
One of the main functions of the Auth Discord server is to help the community with any support question they may have when installing or running an Auth installation.
|
||||||
|
|
||||||
|
Note that you do not need a be part of any official group to become a supporter. Just jump in and help with answering new questions from the community if you know how to help.
|
||||||
|
|
||||||
|
## Help to improve Alliance Auth core
|
||||||
|
|
||||||
|
Alliance Auth has an issue list, which is usually the basis for all maintenance activities for Auth core. That means that bug fixes and new features are primarily delivered based on existing open issues.
|
||||||
|
|
||||||
|
We usually have a long list of open issues and very much welcome every help to fix existing bugs or work on new features for Auth.
|
||||||
|
|
||||||
|
Before starting to code on any topic we'd suggest talking to the other devs on Discord to make sure your issue is not already being worked on. Also, some feature request may be better implemented in a community app. Another aspect, which is best clarified by talking with the other devs.
|
||||||
|
|
||||||
|
If you like to contribute to Auth core, but are unsure where to start, we have a dedicated label for issues that are suitable for beginners: [beginner friendly](https://gitlab.com/allianceauth/allianceauth/-/issues?label_name%5B%5D=beginner+friendly).
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
For more information on how to create community apps or how to setup a developer environment for Auth, please see our official [developer documentation](/development/index.md).
|
||||||
|
|
||||||
|
For getting in touch with other contributors please feel free to join the [Alliance Auth Discord server](https://discord.gg/fjnHAmk).
|
||||||
@@ -4,4 +4,4 @@ Another key feature of **Alliance Auth** is that it can be easily extended. Our
|
|||||||
|
|
||||||
Check out the [Community Creations](https://gitlab.com/allianceauth/community-creations) repo for more details.
|
Check out the [Community Creations](https://gitlab.com/allianceauth/community-creations) repo for more details.
|
||||||
|
|
||||||
Or if you have very specific needs you can of course develop your own plugin- apps and services. Please see the [Development](/development/index.md) chapter details.
|
Or if you have specific needs you can of course always develop your own plugin- apps and services. Please see the [Development](/development/index.md) chapter for details.
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ CELERYBEAT_SCHEDULE['discord.update_all_usernames'] = {
|
|||||||
|
|
||||||
### Creating a Server
|
### Creating a Server
|
||||||
|
|
||||||
Navigate to the [Discord site](https://discordapp.com/) and register an account, or log in if you have one already.
|
Navigate to the [Discord site](https://discord.com/) and register an account, or log in if you have one already.
|
||||||
|
|
||||||
On the left side of the screen you’ll see a circle with a plus sign. This is the button to create a new server. Go ahead and do that, naming it something obvious.
|
On the left side of the screen you’ll see a circle with a plus sign. This is the button to create a new server. Go ahead and do that, naming it something obvious.
|
||||||
|
|
||||||
Now retrieve the server ID [following this procedure.](https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-)
|
Now retrieve the server ID [following this procedure.](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-)
|
||||||
|
|
||||||
Update your auth project's settings file, inputting the server ID as `DISCORD_GUILD_ID`
|
Update your auth project's settings file, inputting the server ID as `DISCORD_GUILD_ID`
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ Update your auth project's settings file, inputting the server ID as `DISCORD_GU
|
|||||||
|
|
||||||
### Registering an Application
|
### Registering an Application
|
||||||
|
|
||||||
Navigate to the [Discord Developers site.](https://discordapp.com/developers/applications/me) Press the plus sign to create a new application.
|
Navigate to the [Discord Developers site.](https://discord.com/developers/applications/me) Press the plus sign to create a new application.
|
||||||
|
|
||||||
Give it a name and description relating to your auth site. Add a redirect to `https://example.com/discord/callback/`, substituting your domain. Press Create Application.
|
Give it a name and description relating to your auth site. Add a redirect to `https://example.com/discord/callback/`, substituting your domain. Press Create Application.
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ Once created, navigate to the services page of your Alliance Auth install as the
|
|||||||
|
|
||||||
This adds a new user to your Discord server with a `BOT` tag, and a new role with the same name as your Discord application. Don't touch either of these. If for some reason the bot loses permissions or is removed from the server, click this button again.
|
This adds a new user to your Discord server with a `BOT` tag, and a new role with the same name as your Discord application. Don't touch either of these. If for some reason the bot loses permissions or is removed from the server, click this button again.
|
||||||
|
|
||||||
To manage roles, this bot role must be at the top of the hierarchy. Edit your Discord server, roles, and click and drag the role with the same name as your application to the top of the list. This role must stay at the top of the list for the bot to work. Finally, the owner of the bot account must enable 2 Factor Authentication (this is required from Discord for kicking and modifying member roles). If you are unsure what 2FA is or how to set it up, refer to [this support page](https://support.discordapp.com/hc/en-us/articles/219576828). It is also recommended to force 2FA on your server (this forces any admins or moderators to have 2fa enabled to perform similar functions on discord).
|
To manage roles, this bot role must be at the top of the hierarchy. Edit your Discord server, roles, and click and drag the role with the same name as your application to the top of the list. This role must stay at the top of the list for the bot to work. Finally, the owner of the bot account must enable 2 Factor Authentication (this is required from Discord for kicking and modifying member roles). If you are unsure what 2FA is or how to set it up, refer to [this support page](https://support.discord.com/hc/en-us/articles/219576828). It is also recommended to force 2FA on your server (this forces any admins or moderators to have 2fa enabled to perform similar functions on discord).
|
||||||
|
|
||||||
Note that the bot will never appear online as it does not participate in chat channels.
|
Note that the bot will never appear online as it does not participate in chat channels.
|
||||||
|
|
||||||
@@ -131,8 +131,8 @@ Name Description
|
|||||||
`DISCORD_BOT_TOKEN` Generated bot token for the Discord Auth app `''`
|
`DISCORD_BOT_TOKEN` Generated bot token for the Discord Auth app `''`
|
||||||
`DISCORD_CALLBACK_URL` Oauth callback URL `''`
|
`DISCORD_CALLBACK_URL` Oauth callback URL `''`
|
||||||
`DISCORD_GUILD_ID` Discord ID of your Discord server `''`
|
`DISCORD_GUILD_ID` Discord ID of your Discord server `''`
|
||||||
`DISCORD_GUILD_NAME_CACHE_MAX_AGE` How long the Discord server name is cached locally in milliseconds `3600000`
|
`DISCORD_GUILD_NAME_CACHE_MAX_AGE` How long the Discord server name is cached locally in seconds `86400`
|
||||||
`DISCORD_ROLES_CACHE_MAX_AGE` How long roles retrieved from the Discord server are cached locally in milliseconds `3600000`
|
`DISCORD_ROLES_CACHE_MAX_AGE` How long roles retrieved from the Discord server are cached locally in seconds `3600`
|
||||||
`DISCORD_SYNC_NAMES` When set to True the nicknames of Discord users will be set to the user's main character name `False`
|
`DISCORD_SYNC_NAMES` When set to True the nicknames of Discord users will be set to the user's main character name `False`
|
||||||
`DISCORD_TASKS_RETRY_PAUSE` Pause in seconds until next retry for tasks after an error occurred `60`
|
`DISCORD_TASKS_RETRY_PAUSE` Pause in seconds until next retry for tasks after an error occurred `60`
|
||||||
`DISCORD_TASKS_MAX_RETRIES` max retries of tasks after an error occurred `3`
|
`DISCORD_TASKS_MAX_RETRIES` max retries of tasks after an error occurred `3`
|
||||||
@@ -144,3 +144,11 @@ Name Description
|
|||||||
### "Unknown Error" on Discord site when activating service
|
### "Unknown Error" on Discord site when activating service
|
||||||
|
|
||||||
This indicates your callback URL doesn't match. Ensure the `DISCORD_CALLBACK_URL` setting exactly matches the URL entered on the Discord developers site. This includes http(s), trailing slash, etc.
|
This indicates your callback URL doesn't match. Ensure the `DISCORD_CALLBACK_URL` setting exactly matches the URL entered on the Discord developers site. This includes http(s), trailing slash, etc.
|
||||||
|
|
||||||
|
### "Add/Remove" Errors in Discord Service
|
||||||
|
|
||||||
|
If you are recieving errors in your Notifications after verifying that your settings are all correct try the following:
|
||||||
|
|
||||||
|
- Ensure that the bot's role in Discord is at the top of the roles list. Each time you add it to your server you will need to do this again.
|
||||||
|
- Make sure that the bot is not trying to modify the Owner of the discord, as it will fail. A holding discord account added with invite link will mitigate this.
|
||||||
|
- Make sure that the bot role on discord has all needed permissions, Admin etc., remembering that these will need to be set every time you add the bot to the Discord server.
|
||||||
|
|||||||
@@ -215,3 +215,72 @@ On a freshly installed mumble server only your superuser has the right to config
|
|||||||
|
|
||||||
- user: `SuperUser`
|
- user: `SuperUser`
|
||||||
- password: *what you defined when configuring your mumble server*
|
- password: *what you defined when configuring your mumble server*
|
||||||
|
|
||||||
|
## Optimizing a Mumble Server
|
||||||
|
|
||||||
|
The needs and available resources will vary between Alliance Auth installations. Consider yours when applying these settings.
|
||||||
|
|
||||||
|
### Bandwidth
|
||||||
|
|
||||||
|
<https://wiki.mumble.info/wiki/Murmur.ini#bandwidth>
|
||||||
|
This is likely the most important setting for scaling a Mumble install, The default maximum Bandwidth is 72000bps Per User. Reducing this value will cause your clients to automatically scale back their bandwidth transmitted, while causing a reduction in voice quality. A value thats still high may cause robotic voices or users with bad connections to drop due entirely due to network load.
|
||||||
|
|
||||||
|
Please tune this value to your individual needs, the below scale may provide a rough starting point.
|
||||||
|
72000 - Superior voice quality - Less than 50 users.
|
||||||
|
54000 - No noticeable reduction in quality - 50+ Users or many channels with active audio.
|
||||||
|
36000 - Mild reduction in quality - 100+ Users
|
||||||
|
30000 - Noticeable reduction in quality but not function - 250+ Users
|
||||||
|
|
||||||
|
### Forcing Opus
|
||||||
|
|
||||||
|
<https://wiki.mumble.info/wiki/Murmur.ini#opusthreshold>
|
||||||
|
A Mumble server by default, will fall back to the older CELT codec as soon as a single user connects with an old client. This will significantly reduce your audio quality and likely place higher load on your server. We _highly_ reccommend setting this to Zero, to force OPUS to be used at all times. Be aware any users with Mumble clients prior to 1.2.4 (From 2013...) Will not hear any audio.
|
||||||
|
|
||||||
|
`opusthreshold=0`
|
||||||
|
|
||||||
|
### AutoBan and Rate Limiting
|
||||||
|
|
||||||
|
<https://wiki.mumble.info/wiki/Murmur.ini#autobanAttempts.2C_autobanTimeframe_and_autobanTime>
|
||||||
|
The AutoBan feature has some sensible settings by default, You may wish to tune these if your users keep locking themselves out by opening two clients by mistake, or if you are receiving unwanted attention
|
||||||
|
|
||||||
|
<https://wiki.mumble.info/wiki/Murmur.ini#messagelimit_and_messageburst>
|
||||||
|
This too, is set to a sensible configuration by default. Take note on upgrading older installs, as this may actually be set too restrictively and will rate-limit your admins accidentally, take note of the configuration in <https://github.com/mumble-voip/mumble/blob/master/scripts/murmur.ini#L156>
|
||||||
|
|
||||||
|
### "Suggest" Options
|
||||||
|
|
||||||
|
There is no way to force your users to update their clients or use Push to Talk, but these options will throw an error into their Mumble Client.
|
||||||
|
|
||||||
|
<https://wiki.mumble.info/wiki/Murmur.ini#Miscellany>
|
||||||
|
|
||||||
|
We suggest using Mumble 1.3.0+ for your server and Clients, you can tune this to the latest Patch version.
|
||||||
|
`suggestVersion=1.3.0`
|
||||||
|
|
||||||
|
If Push to Talk is to your tastes, configure the suggestion as follows
|
||||||
|
`suggestPushToTalk=true`
|
||||||
|
|
||||||
|
## General notes
|
||||||
|
|
||||||
|
### Setting a server password
|
||||||
|
|
||||||
|
With the default configuration your mumble server is public. Meaning that everyone who has the address can at least connect to it and might also be able join all channels that don't have any permissions set (Depending on your ACL configured for the root channel). If you want only registered member being able to join your mumble, you have to set a server password. To do so open your mumble server configuration which is by default located at `/etc/mumble-server.ini`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /etc/mumble-server.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
Now search for `serverpassword=` and set your password here. If there is no such line, simply add it.
|
||||||
|
|
||||||
|
```text
|
||||||
|
serverpassword=YourSuperSecretServerPassword
|
||||||
|
```
|
||||||
|
|
||||||
|
Save the file and restart your mumble server afterwards.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
service mumble-server restart
|
||||||
|
```
|
||||||
|
|
||||||
|
From now on, only registered member can join your mumble server. Now if you still want to allow guests to join you have 2 options.
|
||||||
|
|
||||||
|
- Allow the "Guest" state to activate the Mumble service in your Auth instance
|
||||||
|
- Use [Mumble temporary links](https://github.com/pvyParts/allianceauth-mumble-temp)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ The method which populates these runs every 30 minutes. To populate manually, st
|
|||||||
|
|
||||||
And execute the update:
|
And execute the update:
|
||||||
|
|
||||||
from services.modules.teamspeak3.tasks import Teamspeak3Tasks
|
from allianceauth.services.modules.teamspeak3.tasks import Teamspeak3Tasks
|
||||||
Teamspeak3Tasks.run_ts3_group_update()
|
Teamspeak3Tasks.run_ts3_group_update()
|
||||||
|
|
||||||
Ensure that command does not return an error.
|
Ensure that command does not return an error.
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ Welcome to the official documentation for **Alliance Auth**!
|
|||||||
support/index
|
support/index
|
||||||
customizing/index
|
customizing/index
|
||||||
development/index
|
development/index
|
||||||
|
contributing/index
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user