mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-06 07:06:19 +01:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3874aa6fee | ||
|
|
103e9f3a11 | ||
|
|
d02c25f421 | ||
|
|
228af38a4a | ||
|
|
051a48885c | ||
|
|
6bcdc6052f | ||
|
|
af3527e64f | ||
|
|
17ef3dd07a | ||
|
|
1f165ecd2a | ||
|
|
70d1d450a9 | ||
|
|
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 | ||
|
|
9ea55fa51f | ||
|
|
5775a11b4e | ||
|
|
1a666b6584 | ||
|
|
35407a2108 | ||
|
|
71fb19aa22 | ||
|
|
b7d7f7b8ce | ||
|
|
59b983edcc | ||
|
|
1734d034e1 | ||
|
|
7f7500ff0c | ||
|
|
ce77c24e5c | ||
|
|
5469a591c0 | ||
|
|
a4befc5e59 | ||
|
|
1ee8065592 | ||
|
|
e4e3bd44fc | ||
|
|
c75de07c2e | ||
|
|
e928131809 | ||
|
|
4f802e82a9 | ||
|
|
0c90bd462e | ||
|
|
bbb70c93d9 | ||
|
|
f6e6ba775c | ||
|
|
06646be907 | ||
|
|
1b4c1a4b9e | ||
|
|
ae3f5a0f62 | ||
|
|
3a984e8a4d | ||
|
|
7d711a54bc | ||
|
|
d92d629c25 | ||
|
|
21e630209a | ||
|
|
e3933998ef | ||
|
|
667afe9051 | ||
|
|
26dc2881eb | ||
|
|
250cb33285 | ||
|
|
db51abec1f | ||
|
|
530716d458 | ||
|
|
f3065d79b3 | ||
|
|
bca5f0472e | ||
|
|
8e54c43917 | ||
|
|
946df1d7a0 | ||
|
|
55f00f742c | ||
|
|
97b2cb71b7 | ||
|
|
ba3a5ba53c | ||
|
|
953c09c999 | ||
|
|
b4cc325b07 | ||
|
|
28c1343f3e | ||
|
|
c16fd94c4a | ||
|
|
bb2cc20838 | ||
|
|
7b815fd010 | ||
|
|
18584974df | ||
|
|
15823b7785 | ||
|
|
6c275d4cd2 | ||
|
|
2d64ee5e2a | ||
|
|
3ae5ffa3f6 | ||
|
|
57d9ddc2c6 | ||
|
|
8b84def494 | ||
|
|
546f01ceb2 | ||
|
|
be720d0e0f | ||
|
|
72bed03244 | ||
|
|
38083ed284 | ||
|
|
53f1b94475 | ||
|
|
ed4270a0e3 | ||
|
|
f1d5cc8903 | ||
|
|
80efdec5d9 | ||
|
|
d49687400a | ||
|
|
e6e03b50da | ||
|
|
543fa3cfa9 | ||
|
|
899988c7c2 | ||
|
|
2f48dd449b | ||
|
|
f70fbbdfee | ||
|
|
2b09ca240d | ||
|
|
0626ff84ad | ||
|
|
62ec746ee3 | ||
|
|
d0f12d7d56 | ||
|
|
b806a69604 | ||
|
|
a609d6360b | ||
|
|
dafbfc8644 | ||
|
|
55413eea19 | ||
|
|
5247c181af | ||
|
|
321af5ec87 | ||
|
|
9ccf340b3d | ||
|
|
d7dcacb899 | ||
|
|
8addd483c2 | ||
|
|
4d27e5ac9b | ||
|
|
31290f6e80 | ||
|
|
c31cc4dbee | ||
|
|
cc1f94cf61 | ||
|
|
a9132b8d50 | ||
|
|
7b4a9891aa | ||
|
|
dcaaf38ecc | ||
|
|
653a8aa850 | ||
|
|
274af11385 | ||
|
|
170b246901 | ||
|
|
9ea79ea389 | ||
|
|
b6fdf840ef | ||
|
|
42948386ec | ||
|
|
1ce0dbde0e |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -72,3 +72,8 @@ celerybeat-schedule
|
|||||||
|
|
||||||
#transifex
|
#transifex
|
||||||
.tx/
|
.tx/
|
||||||
|
|
||||||
|
#other
|
||||||
|
.flake8
|
||||||
|
.pylintrc
|
||||||
|
Makefile
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
stages:
|
stages:
|
||||||
- "test"
|
- test
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- apt-get update && apt-get install redis-server -y
|
||||||
|
- redis-server --daemonize yes
|
||||||
|
- redis-cli ping
|
||||||
- python -V
|
- python -V
|
||||||
- pip install wheel tox
|
- pip install wheel tox
|
||||||
|
|
||||||
test-3.5-core:
|
|
||||||
image: python:3.5-buster
|
|
||||||
script:
|
|
||||||
- tox -e py35-core
|
|
||||||
|
|
||||||
test-3.6-core:
|
test-3.6-core:
|
||||||
image: python:3.6-buster
|
image: python:3.6-buster
|
||||||
script:
|
script:
|
||||||
@@ -26,11 +24,6 @@ test-3.8-core:
|
|||||||
script:
|
script:
|
||||||
- tox -e py38-core
|
- tox -e py38-core
|
||||||
|
|
||||||
test-3.5-all:
|
|
||||||
image: python:3.5-buster
|
|
||||||
script:
|
|
||||||
- tox -e py35-all
|
|
||||||
|
|
||||||
test-3.6-all:
|
test-3.6-all:
|
||||||
image: python:3.6-buster
|
image: python:3.6-buster
|
||||||
script:
|
script:
|
||||||
@@ -57,5 +50,5 @@ deploy_production:
|
|||||||
- python setup.py sdist
|
- python setup.py sdist
|
||||||
- twine upload dist/*
|
- twine upload dist/*
|
||||||
|
|
||||||
only:
|
rules:
|
||||||
- tags
|
- if: $CI_COMMIT_TAG
|
||||||
|
|||||||
27
.readthedocs.yml
Normal file
27
.readthedocs.yml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# .readthedocs.yml
|
||||||
|
# Read the Docs configuration file
|
||||||
|
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||||
|
|
||||||
|
# Required
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
|
||||||
|
# Build documentation with MkDocs
|
||||||
|
#mkdocs:
|
||||||
|
# configuration: mkdocs.yml
|
||||||
|
|
||||||
|
# Optionally build your docs in additional formats such as PDF and ePub
|
||||||
|
formats: all
|
||||||
|
|
||||||
|
# Optionally set the version of Python and requirements required to build your docs
|
||||||
|
python:
|
||||||
|
version: 3.7
|
||||||
|
install:
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- testing
|
||||||
|
system_packages: true
|
||||||
@@ -36,7 +36,7 @@ Main features:
|
|||||||
|
|
||||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||||
|
|
||||||
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization
|
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: localization
|
||||||
|
|
||||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# 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.6.3'
|
__version__ = '2.8.0a1'
|
||||||
NAME = 'Alliance Auth v%s' % __version__
|
__title__ = 'Alliance Auth'
|
||||||
|
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||||
|
NAME = '%s v%s' % (__title__, __version__)
|
||||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||||
|
|||||||
@@ -37,8 +37,11 @@ def make_service_hooks_update_groups_action(service):
|
|||||||
:return: fn to update services groups for the selected users
|
:return: fn to update services groups for the selected users
|
||||||
"""
|
"""
|
||||||
def update_service_groups(modeladmin, request, queryset):
|
def update_service_groups(modeladmin, request, queryset):
|
||||||
for user in queryset: # queryset filtering doesn't work here?
|
if hasattr(service, 'update_groups_bulk'):
|
||||||
service.update_groups(user)
|
service.update_groups_bulk(queryset)
|
||||||
|
else:
|
||||||
|
for user in queryset: # queryset filtering doesn't work here?
|
||||||
|
service.update_groups(user)
|
||||||
|
|
||||||
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
|
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
|
||||||
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
|
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
|
||||||
@@ -52,8 +55,11 @@ def make_service_hooks_sync_nickname_action(service):
|
|||||||
:return: fn to sync nickname for the selected users
|
:return: fn to sync nickname for the selected users
|
||||||
"""
|
"""
|
||||||
def sync_nickname(modeladmin, request, queryset):
|
def sync_nickname(modeladmin, request, queryset):
|
||||||
for user in queryset: # queryset filtering doesn't work here?
|
if hasattr(service, 'sync_nicknames_bulk'):
|
||||||
service.sync_nickname(user)
|
service.sync_nicknames_bulk(queryset)
|
||||||
|
else:
|
||||||
|
for user in queryset: # queryset filtering doesn't work here?
|
||||||
|
service.sync_nickname(user)
|
||||||
|
|
||||||
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
|
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
|
||||||
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
|
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -141,19 +145,17 @@
|
|||||||
</table>
|
</table>
|
||||||
<table class="table table-aa visible-xs-block" style="width: 100%">
|
<table class="table table-aa visible-xs-block" style="width: 100%">
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for ownership in request.user.character_ownerships.all %}
|
{% for char in characters %}
|
||||||
{% with ownership.character as char %}
|
<tr>
|
||||||
<tr>
|
<td class="text-center" style="vertical-align: middle">
|
||||||
<td class="text-center" style="vertical-align: middle">
|
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
</td>
|
||||||
</td>
|
<td class="text-center" style="vertical-align: middle; width: 100%">
|
||||||
<td class="text-center" style="vertical-align: middle; width: 100%">
|
<strong>{{ char.character_name }}</strong><br>
|
||||||
<strong>{{ char.character_name }}</strong><br>
|
{{ char.corporation_name }}<br>
|
||||||
{{ char.corporation_name }}<br>
|
{{ char.alliance_name|default:"" }}
|
||||||
{{ char.alliance_name|default:"" }}
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="author" content="">
|
<meta name="author" content="">
|
||||||
|
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||||
|
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
|
||||||
|
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||||
|
|
||||||
{% include 'allianceauth/icons.html' %}
|
{% include 'allianceauth/icons.html' %}
|
||||||
|
|
||||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
@@ -13,6 +13,7 @@ from allianceauth.authentication.models import (
|
|||||||
from allianceauth.eveonline.models import (
|
from allianceauth.eveonline.models import (
|
||||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
)
|
)
|
||||||
|
from allianceauth.services.hooks import ServicesHook
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from ..admin import (
|
from ..admin import (
|
||||||
@@ -28,7 +29,9 @@ from ..admin import (
|
|||||||
user_main_organization,
|
user_main_organization,
|
||||||
user_profile_pic,
|
user_profile_pic,
|
||||||
user_username,
|
user_username,
|
||||||
update_main_character_model
|
update_main_character_model,
|
||||||
|
make_service_hooks_update_groups_action,
|
||||||
|
make_service_hooks_sync_nickname_action
|
||||||
)
|
)
|
||||||
from . import get_admin_change_view_url, get_admin_search_url
|
from . import get_admin_change_view_url, get_admin_search_url
|
||||||
|
|
||||||
@@ -45,136 +48,144 @@ class MockRequest(object):
|
|||||||
def __init__(self, user=None):
|
def __init__(self, user=None):
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
|
class TestCaseWithTestData(TestCase):
|
||||||
|
|
||||||
def create_test_data():
|
@classmethod
|
||||||
# groups
|
def setUpClass(cls):
|
||||||
group_1 = Group.objects.create(
|
super().setUpClass()
|
||||||
name='Group 1'
|
|
||||||
)
|
|
||||||
group_2 = Group.objects.create(
|
|
||||||
name='Group 2'
|
|
||||||
)
|
|
||||||
|
|
||||||
# user 1 - corp and alliance, normal user
|
|
||||||
character_1 = EveCharacter.objects.create(
|
|
||||||
character_id='1001',
|
|
||||||
character_name='Bruce Wayne',
|
|
||||||
corporation_id='2001',
|
|
||||||
corporation_name='Wayne Technologies',
|
|
||||||
corporation_ticker='WT',
|
|
||||||
alliance_id='3001',
|
|
||||||
alliance_name='Wayne Enterprises',
|
|
||||||
alliance_ticker='WE',
|
|
||||||
)
|
|
||||||
character_1a = EveCharacter.objects.create(
|
|
||||||
character_id='1002',
|
|
||||||
character_name='Batman',
|
|
||||||
corporation_id='2001',
|
|
||||||
corporation_name='Wayne Technologies',
|
|
||||||
corporation_ticker='WT',
|
|
||||||
alliance_id='3001',
|
|
||||||
alliance_name='Wayne Enterprises',
|
|
||||||
alliance_ticker='WE',
|
|
||||||
)
|
|
||||||
alliance = EveAllianceInfo.objects.create(
|
|
||||||
alliance_id='3001',
|
|
||||||
alliance_name='Wayne Enterprises',
|
|
||||||
alliance_ticker='WE',
|
|
||||||
executor_corp_id='2001'
|
|
||||||
)
|
|
||||||
EveCorporationInfo.objects.create(
|
|
||||||
corporation_id='2001',
|
|
||||||
corporation_name='Wayne Technologies',
|
|
||||||
corporation_ticker='WT',
|
|
||||||
member_count=42,
|
|
||||||
alliance=alliance
|
|
||||||
)
|
|
||||||
user_1 = User.objects.create_user(
|
|
||||||
character_1.character_name.replace(' ', '_'),
|
|
||||||
'abc@example.com',
|
|
||||||
'password'
|
|
||||||
)
|
|
||||||
CharacterOwnership.objects.create(
|
|
||||||
character=character_1,
|
|
||||||
owner_hash='x1' + character_1.character_name,
|
|
||||||
user=user_1
|
|
||||||
)
|
|
||||||
CharacterOwnership.objects.create(
|
|
||||||
character=character_1a,
|
|
||||||
owner_hash='x1' + character_1a.character_name,
|
|
||||||
user=user_1
|
|
||||||
)
|
|
||||||
user_1.profile.main_character = character_1
|
|
||||||
user_1.profile.save()
|
|
||||||
user_1.groups.add(group_1)
|
|
||||||
|
|
||||||
# user 2 - corp only, staff
|
for MyModel in [
|
||||||
character_2 = EveCharacter.objects.create(
|
EveAllianceInfo, EveCorporationInfo, EveCharacter, Group, User
|
||||||
character_id=1003,
|
]:
|
||||||
character_name='Clark Kent',
|
MyModel.objects.all().delete()
|
||||||
corporation_id=2002,
|
|
||||||
corporation_name='Daily Planet',
|
# groups
|
||||||
corporation_ticker='DP',
|
cls.group_1 = Group.objects.create(
|
||||||
alliance_id=None
|
name='Group 1'
|
||||||
)
|
)
|
||||||
EveCorporationInfo.objects.create(
|
cls.group_2 = Group.objects.create(
|
||||||
corporation_id=2002,
|
name='Group 2'
|
||||||
corporation_name='Daily Plane',
|
)
|
||||||
corporation_ticker='DP',
|
|
||||||
member_count=99,
|
# user 1 - corp and alliance, normal user
|
||||||
alliance=None
|
character_1 = EveCharacter.objects.create(
|
||||||
)
|
character_id=1001,
|
||||||
user_2 = User.objects.create_user(
|
character_name='Bruce Wayne',
|
||||||
character_2.character_name.replace(' ', '_'),
|
corporation_id=2001,
|
||||||
'abc@example.com',
|
corporation_name='Wayne Technologies',
|
||||||
'password'
|
corporation_ticker='WT',
|
||||||
)
|
alliance_id=3001,
|
||||||
CharacterOwnership.objects.create(
|
alliance_name='Wayne Enterprises',
|
||||||
character=character_2,
|
alliance_ticker='WE',
|
||||||
owner_hash='x1' + character_2.character_name,
|
)
|
||||||
user=user_2
|
character_1a = EveCharacter.objects.create(
|
||||||
)
|
character_id=1002,
|
||||||
user_2.profile.main_character = character_2
|
character_name='Batman',
|
||||||
user_2.profile.save()
|
corporation_id=2001,
|
||||||
user_2.groups.add(group_2)
|
corporation_name='Wayne Technologies',
|
||||||
user_2.is_staff = True
|
corporation_ticker='WT',
|
||||||
user_2.save()
|
alliance_id=3001,
|
||||||
|
alliance_name='Wayne Enterprises',
|
||||||
# user 3 - no main, no group, superuser
|
alliance_ticker='WE',
|
||||||
character_3 = EveCharacter.objects.create(
|
)
|
||||||
character_id=1101,
|
alliance = EveAllianceInfo.objects.create(
|
||||||
character_name='Lex Luthor',
|
alliance_id=3001,
|
||||||
corporation_id=2101,
|
alliance_name='Wayne Enterprises',
|
||||||
corporation_name='Lex Corp',
|
alliance_ticker='WE',
|
||||||
corporation_ticker='LC',
|
executor_corp_id=2001
|
||||||
alliance_id=None
|
)
|
||||||
)
|
EveCorporationInfo.objects.create(
|
||||||
EveCorporationInfo.objects.create(
|
corporation_id=2001,
|
||||||
corporation_id=2101,
|
corporation_name='Wayne Technologies',
|
||||||
corporation_name='Lex Corp',
|
corporation_ticker='WT',
|
||||||
corporation_ticker='LC',
|
member_count=42,
|
||||||
member_count=666,
|
alliance=alliance
|
||||||
alliance=None
|
)
|
||||||
)
|
cls.user_1 = User.objects.create_user(
|
||||||
EveAllianceInfo.objects.create(
|
character_1.character_name.replace(' ', '_'),
|
||||||
alliance_id='3101',
|
'abc@example.com',
|
||||||
alliance_name='Lex World Domination',
|
'password'
|
||||||
alliance_ticker='LWD',
|
)
|
||||||
executor_corp_id=''
|
CharacterOwnership.objects.create(
|
||||||
)
|
character=character_1,
|
||||||
user_3 = User.objects.create_user(
|
owner_hash='x1' + character_1.character_name,
|
||||||
character_3.character_name.replace(' ', '_'),
|
user=cls.user_1
|
||||||
'abc@example.com',
|
)
|
||||||
'password'
|
CharacterOwnership.objects.create(
|
||||||
)
|
character=character_1a,
|
||||||
CharacterOwnership.objects.create(
|
owner_hash='x1' + character_1a.character_name,
|
||||||
character=character_3,
|
user=cls.user_1
|
||||||
owner_hash='x1' + character_3.character_name,
|
)
|
||||||
user=user_3
|
cls.user_1.profile.main_character = character_1
|
||||||
)
|
cls.user_1.profile.save()
|
||||||
user_3.is_superuser = True
|
cls.user_1.groups.add(cls.group_1)
|
||||||
user_3.save()
|
|
||||||
return user_1, user_2, user_3, group_1, group_2
|
# user 2 - corp only, staff
|
||||||
|
character_2 = EveCharacter.objects.create(
|
||||||
|
character_id=1003,
|
||||||
|
character_name='Clark Kent',
|
||||||
|
corporation_id=2002,
|
||||||
|
corporation_name='Daily Planet',
|
||||||
|
corporation_ticker='DP',
|
||||||
|
alliance_id=None
|
||||||
|
)
|
||||||
|
EveCorporationInfo.objects.create(
|
||||||
|
corporation_id=2002,
|
||||||
|
corporation_name='Daily Plane',
|
||||||
|
corporation_ticker='DP',
|
||||||
|
member_count=99,
|
||||||
|
alliance=None
|
||||||
|
)
|
||||||
|
cls.user_2 = User.objects.create_user(
|
||||||
|
character_2.character_name.replace(' ', '_'),
|
||||||
|
'abc@example.com',
|
||||||
|
'password'
|
||||||
|
)
|
||||||
|
CharacterOwnership.objects.create(
|
||||||
|
character=character_2,
|
||||||
|
owner_hash='x1' + character_2.character_name,
|
||||||
|
user=cls.user_2
|
||||||
|
)
|
||||||
|
cls.user_2.profile.main_character = character_2
|
||||||
|
cls.user_2.profile.save()
|
||||||
|
cls.user_2.groups.add(cls.group_2)
|
||||||
|
cls.user_2.is_staff = True
|
||||||
|
cls.user_2.save()
|
||||||
|
|
||||||
|
# user 3 - no main, no group, superuser
|
||||||
|
character_3 = EveCharacter.objects.create(
|
||||||
|
character_id=1101,
|
||||||
|
character_name='Lex Luthor',
|
||||||
|
corporation_id=2101,
|
||||||
|
corporation_name='Lex Corp',
|
||||||
|
corporation_ticker='LC',
|
||||||
|
alliance_id=None
|
||||||
|
)
|
||||||
|
EveCorporationInfo.objects.create(
|
||||||
|
corporation_id=2101,
|
||||||
|
corporation_name='Lex Corp',
|
||||||
|
corporation_ticker='LC',
|
||||||
|
member_count=666,
|
||||||
|
alliance=None
|
||||||
|
)
|
||||||
|
EveAllianceInfo.objects.create(
|
||||||
|
alliance_id=3101,
|
||||||
|
alliance_name='Lex World Domination',
|
||||||
|
alliance_ticker='LWD',
|
||||||
|
executor_corp_id=2101
|
||||||
|
)
|
||||||
|
cls.user_3 = User.objects.create_user(
|
||||||
|
character_3.character_name.replace(' ', '_'),
|
||||||
|
'abc@example.com',
|
||||||
|
'password'
|
||||||
|
)
|
||||||
|
CharacterOwnership.objects.create(
|
||||||
|
character=character_3,
|
||||||
|
owner_hash='x1' + character_3.character_name,
|
||||||
|
user=cls.user_3
|
||||||
|
)
|
||||||
|
cls.user_3.is_superuser = True
|
||||||
|
cls.user_3.save()
|
||||||
|
|
||||||
|
|
||||||
def make_generic_search_request(ModelClass: type, search_term: str):
|
def make_generic_search_request(ModelClass: type, search_term: str):
|
||||||
@@ -188,12 +199,7 @@ def make_generic_search_request(ModelClass: type, search_term: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestCharacterOwnershipAdmin(TestCase):
|
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
cls.user_1, _, _, _, _ = create_test_data()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.modeladmin = CharacterOwnershipAdmin(
|
self.modeladmin = CharacterOwnershipAdmin(
|
||||||
@@ -219,12 +225,7 @@ class TestCharacterOwnershipAdmin(TestCase):
|
|||||||
self.assertEqual(response.status_code, expected)
|
self.assertEqual(response.status_code, expected)
|
||||||
|
|
||||||
|
|
||||||
class TestOwnershipRecordAdmin(TestCase):
|
class TestOwnershipRecordAdmin(TestCaseWithTestData):
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
cls.user_1, _, _, _, _ = create_test_data()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.modeladmin = OwnershipRecordAdmin(
|
self.modeladmin = OwnershipRecordAdmin(
|
||||||
@@ -250,13 +251,8 @@ class TestOwnershipRecordAdmin(TestCase):
|
|||||||
self.assertEqual(response.status_code, expected)
|
self.assertEqual(response.status_code, expected)
|
||||||
|
|
||||||
|
|
||||||
class TestStateAdmin(TestCase):
|
class TestStateAdmin(TestCaseWithTestData):
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
create_test_data()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.modeladmin = StateAdmin(
|
self.modeladmin = StateAdmin(
|
||||||
model=User, admin_site=AdminSite()
|
model=User, admin_site=AdminSite()
|
||||||
@@ -283,13 +279,7 @@ class TestStateAdmin(TestCase):
|
|||||||
expected = 200
|
expected = 200
|
||||||
self.assertEqual(response.status_code, expected)
|
self.assertEqual(response.status_code, expected)
|
||||||
|
|
||||||
class TestUserAdmin(TestCase):
|
class TestUserAdmin(TestCaseWithTestData):
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
cls.user_1, cls.user_2, cls.user_3, cls.group_1, cls.group_2 = \
|
|
||||||
create_test_data()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
@@ -520,8 +510,8 @@ class TestUserAdmin(TestCase):
|
|||||||
filters = changelist.get_filters(request)
|
filters = changelist.get_filters(request)
|
||||||
filterspec = filters[0][0]
|
filterspec = filters[0][0]
|
||||||
expected = [
|
expected = [
|
||||||
('2002', 'Daily Planet'),
|
(2002, 'Daily Planet'),
|
||||||
('2001', 'Wayne Technologies'),
|
(2001, 'Wayne Technologies'),
|
||||||
]
|
]
|
||||||
self.assertEqual(filterspec.lookup_choices, expected)
|
self.assertEqual(filterspec.lookup_choices, expected)
|
||||||
|
|
||||||
@@ -550,7 +540,7 @@ class TestUserAdmin(TestCase):
|
|||||||
filters = changelist.get_filters(request)
|
filters = changelist.get_filters(request)
|
||||||
filterspec = filters[0][0]
|
filterspec = filters[0][0]
|
||||||
expected = [
|
expected = [
|
||||||
('3001', 'Wayne Enterprises'),
|
(3001, 'Wayne Enterprises'),
|
||||||
]
|
]
|
||||||
self.assertEqual(filterspec.lookup_choices, expected)
|
self.assertEqual(filterspec.lookup_choices, expected)
|
||||||
|
|
||||||
@@ -578,4 +568,68 @@ class TestUserAdmin(TestCase):
|
|||||||
obj = User.objects.first()
|
obj = User.objects.first()
|
||||||
response = make_generic_search_request(type(obj), obj.username)
|
response = make_generic_search_request(type(obj), obj.username)
|
||||||
expected = 200
|
expected = 200
|
||||||
self.assertEqual(response.status_code, expected)
|
self.assertEqual(response.status_code, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMakeServicesHooksActions(TestCaseWithTestData):
|
||||||
|
|
||||||
|
class MyServicesHookTypeA(ServicesHook):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.name = 'My Service A'
|
||||||
|
|
||||||
|
def update_groups(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sync_nicknames(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MyServicesHookTypeB(ServicesHook):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.name = 'My Service B'
|
||||||
|
|
||||||
|
def update_groups(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_groups_bulk(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sync_nicknames(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sync_nicknames_bulk(self, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_service_has_update_groups_only(self):
|
||||||
|
service = self.MyServicesHookTypeA()
|
||||||
|
mock_service = MagicMock(spec=service)
|
||||||
|
action = make_service_hooks_update_groups_action(mock_service)
|
||||||
|
action(MagicMock(), MagicMock(), [self.user_1])
|
||||||
|
self.assertTrue(mock_service.update_groups.called)
|
||||||
|
|
||||||
|
def test_service_has_update_groups_bulk(self):
|
||||||
|
service = self.MyServicesHookTypeB()
|
||||||
|
mock_service = MagicMock(spec=service)
|
||||||
|
action = make_service_hooks_update_groups_action(mock_service)
|
||||||
|
action(MagicMock(), MagicMock(), [self.user_1])
|
||||||
|
self.assertFalse(mock_service.update_groups.called)
|
||||||
|
self.assertTrue(mock_service.update_groups_bulk.called)
|
||||||
|
|
||||||
|
def test_service_has_sync_nickname_only(self):
|
||||||
|
service = self.MyServicesHookTypeA()
|
||||||
|
mock_service = MagicMock(spec=service)
|
||||||
|
action = make_service_hooks_sync_nickname_action(mock_service)
|
||||||
|
action(MagicMock(), MagicMock(), [self.user_1])
|
||||||
|
self.assertTrue(mock_service.sync_nickname.called)
|
||||||
|
|
||||||
|
def test_service_has_sync_nicknames_bulk(self):
|
||||||
|
service = self.MyServicesHookTypeB()
|
||||||
|
mock_service = MagicMock(spec=service)
|
||||||
|
action = make_service_hooks_sync_nickname_action(mock_service)
|
||||||
|
action(MagicMock(), MagicMock(), [self.user_1])
|
||||||
|
self.assertFalse(mock_service.sync_nickname.called)
|
||||||
|
self.assertTrue(mock_service.sync_nicknames_bulk.called)
|
||||||
|
|||||||
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):
|
||||||
@@ -343,10 +216,10 @@ class CharacterOwnershipCheckTestCase(TestCase):
|
|||||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
|
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
|
||||||
corp_name='Test Corp', alliance_name='Test Alliance')
|
corp_name='Test Corp', alliance_name='Test Alliance')
|
||||||
cls.character = EveCharacter.objects.get(character_id='1')
|
cls.character = EveCharacter.objects.get(character_id=1)
|
||||||
cls.token = Token.objects.create(
|
cls.token = Token.objects.create(
|
||||||
user=cls.user,
|
user=cls.user,
|
||||||
character_id='1',
|
character_id=1,
|
||||||
character_name='Test',
|
character_name='Test',
|
||||||
character_owner_hash='1',
|
character_owner_hash='1',
|
||||||
)
|
)
|
||||||
@@ -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())
|
|
||||||
290
allianceauth/authentication/tests/test_templatetags.py
Normal file
290
allianceauth/authentication/tests/test_templatetags.py
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
from math import ceil
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from requests import RequestException
|
||||||
|
import requests_mock
|
||||||
|
from packaging.version import Version as Pep440Version
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from allianceauth.templatetags.admin_status import (
|
||||||
|
status_overview,
|
||||||
|
_fetch_list_from_gitlab,
|
||||||
|
_current_notifications,
|
||||||
|
_current_version_summary,
|
||||||
|
_fetch_notification_issues_from_gitlab,
|
||||||
|
_fetch_tags_from_gitlab,
|
||||||
|
_latests_versions
|
||||||
|
)
|
||||||
|
|
||||||
|
MODULE_PATH = 'allianceauth.templatetags'
|
||||||
|
|
||||||
|
|
||||||
|
def create_tags_list(tag_names: list):
|
||||||
|
return [{'name': str(tag_name)} for tag_name in tag_names]
|
||||||
|
|
||||||
|
|
||||||
|
GITHUB_TAGS = create_tags_list(['v2.4.6a1', 'v2.4.5', 'v2.4.0', 'v2.0.0', 'v1.1.1'])
|
||||||
|
GITHUB_NOTIFICATION_ISSUES = [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'title': 'first issue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 2,
|
||||||
|
'title': 'second issue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 3,
|
||||||
|
'title': 'third issue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 4,
|
||||||
|
'title': 'forth issue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 5,
|
||||||
|
'title': 'fifth issue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 6,
|
||||||
|
'title': 'sixth issue'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
TEST_VERSION = '2.6.5'
|
||||||
|
|
||||||
|
|
||||||
|
class TestStatusOverviewTag(TestCase):
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||||
|
@patch(MODULE_PATH + '.admin_status._fetch_celery_queue_length')
|
||||||
|
@patch(MODULE_PATH + '.admin_status._current_version_summary')
|
||||||
|
@patch(MODULE_PATH + '.admin_status._current_notifications')
|
||||||
|
def test_status_overview(
|
||||||
|
self,
|
||||||
|
mock_current_notifications,
|
||||||
|
mock_current_version_info,
|
||||||
|
mock_fetch_celery_queue_length
|
||||||
|
):
|
||||||
|
notifications = {
|
||||||
|
'notifications': GITHUB_NOTIFICATION_ISSUES[:5]
|
||||||
|
}
|
||||||
|
mock_current_notifications.return_value = notifications
|
||||||
|
version_info = {
|
||||||
|
'latest_major': True,
|
||||||
|
'latest_minor': True,
|
||||||
|
'latest_patch': True,
|
||||||
|
'latest_beta': False,
|
||||||
|
'current_version': TEST_VERSION,
|
||||||
|
'latest_major_version': '2.4.5',
|
||||||
|
'latest_minor_version': '2.4.0',
|
||||||
|
'latest_patch_version': '2.4.5',
|
||||||
|
'latest_beta_version': '2.4.4a1',
|
||||||
|
}
|
||||||
|
mock_current_version_info.return_value = version_info
|
||||||
|
mock_fetch_celery_queue_length.return_value = 3
|
||||||
|
|
||||||
|
result = status_overview()
|
||||||
|
expected = {
|
||||||
|
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
|
||||||
|
'latest_major': True,
|
||||||
|
'latest_minor': True,
|
||||||
|
'latest_patch': True,
|
||||||
|
'latest_beta': False,
|
||||||
|
'current_version': TEST_VERSION,
|
||||||
|
'latest_major_version': '2.4.5',
|
||||||
|
'latest_minor_version': '2.4.0',
|
||||||
|
'latest_patch_version': '2.4.5',
|
||||||
|
'latest_beta_version': '2.4.4a1',
|
||||||
|
'task_queue_length': 3,
|
||||||
|
}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNotifications(TestCase):
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_fetch_notification_issues_from_gitlab(self, requests_mocker):
|
||||||
|
url = (
|
||||||
|
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
|
||||||
|
'?labels=announcement'
|
||||||
|
)
|
||||||
|
requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES)
|
||||||
|
result = _fetch_notification_issues_from_gitlab()
|
||||||
|
self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES)
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.cache')
|
||||||
|
def test_current_notifications_normal(self, mock_cache):
|
||||||
|
mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES
|
||||||
|
|
||||||
|
result = _current_notifications()
|
||||||
|
self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5])
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.cache')
|
||||||
|
def test_current_notifications_failed(self, mock_cache):
|
||||||
|
mock_cache.get_or_set.side_effect = RequestException
|
||||||
|
|
||||||
|
result = _current_notifications()
|
||||||
|
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):
|
||||||
|
|
||||||
|
def test_get_celery_queue_length(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestVersionTags(TestCase):
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||||
|
@patch(MODULE_PATH + '.admin_status.cache')
|
||||||
|
def test_current_version_info_normal(self, mock_cache):
|
||||||
|
mock_cache.get_or_set.return_value = GITHUB_TAGS
|
||||||
|
|
||||||
|
result = _current_version_summary()
|
||||||
|
self.assertTrue(result['latest_major'])
|
||||||
|
self.assertTrue(result['latest_minor'])
|
||||||
|
self.assertTrue(result['latest_patch'])
|
||||||
|
self.assertEqual(result['latest_major_version'], '2.0.0')
|
||||||
|
self.assertEqual(result['latest_minor_version'], '2.4.0')
|
||||||
|
self.assertEqual(result['latest_patch_version'], '2.4.5')
|
||||||
|
self.assertEqual(result['latest_beta_version'], '2.4.6a1')
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||||
|
@patch(MODULE_PATH + '.admin_status.cache')
|
||||||
|
def test_current_version_info_failed(self, mock_cache):
|
||||||
|
mock_cache.get_or_set.side_effect = RequestException
|
||||||
|
|
||||||
|
expected = {}
|
||||||
|
result = _current_version_summary()
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_fetch_tags_from_gitlab(self, requests_mocker):
|
||||||
|
url = (
|
||||||
|
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
|
||||||
|
'/repository/tags'
|
||||||
|
)
|
||||||
|
requests_mocker.get(url, json=GITHUB_TAGS)
|
||||||
|
result = _fetch_tags_from_gitlab()
|
||||||
|
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):
|
||||||
|
|
||||||
|
def test_all_version_types_defined(self):
|
||||||
|
|
||||||
|
tags = create_tags_list(
|
||||||
|
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
|
||||||
|
)
|
||||||
|
major, minor, patch, beta = _latests_versions(tags)
|
||||||
|
self.assertEqual(major, Pep440Version('2.0.0'))
|
||||||
|
self.assertEqual(minor, Pep440Version('2.1.0'))
|
||||||
|
self.assertEqual(patch, Pep440Version('2.1.1'))
|
||||||
|
self.assertEqual(beta, Pep440Version('2.1.1a1'))
|
||||||
|
|
||||||
|
def test_major_and_minor_not_defined_with_zero(self):
|
||||||
|
|
||||||
|
tags = create_tags_list(
|
||||||
|
['2.1.2', '2.1.1', '2.0.1', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
|
||||||
|
)
|
||||||
|
major, minor, patch, beta = _latests_versions(tags)
|
||||||
|
self.assertEqual(major, Pep440Version('2.0.1'))
|
||||||
|
self.assertEqual(minor, Pep440Version('2.1.1'))
|
||||||
|
self.assertEqual(patch, Pep440Version('2.1.2'))
|
||||||
|
self.assertEqual(beta, Pep440Version('2.1.1a1'))
|
||||||
|
|
||||||
|
def test_can_ignore_invalid_versions(self):
|
||||||
|
|
||||||
|
tags = create_tags_list(
|
||||||
|
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', 'invalid']
|
||||||
|
)
|
||||||
|
major, minor, patch, beta = _latests_versions(tags)
|
||||||
|
self.assertEqual(major, Pep440Version('2.0.0'))
|
||||||
|
self.assertEqual(minor, Pep440Version('2.1.0'))
|
||||||
|
self.assertEqual(patch, Pep440Version('2.1.1'))
|
||||||
|
self.assertEqual(beta, Pep440Version('2.1.1a1'))
|
||||||
|
|
||||||
|
|
||||||
|
class TestFetchListFromGitlab(TestCase):
|
||||||
|
|
||||||
|
page_size = 2
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.url = (
|
||||||
|
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
|
||||||
|
'/repository/tags'
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def my_callback(cls, request, context):
|
||||||
|
page = int(request.qs['page'][0])
|
||||||
|
start = (page - 1) * cls.page_size
|
||||||
|
end = start + cls.page_size
|
||||||
|
return GITHUB_TAGS[start:end]
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_can_fetch_one_page_with_header(self, requests_mocker):
|
||||||
|
headers = {
|
||||||
|
'x-total-pages': '1'
|
||||||
|
}
|
||||||
|
requests_mocker.get(self.url, json=GITHUB_TAGS, headers=headers)
|
||||||
|
result = _fetch_list_from_gitlab(self.url)
|
||||||
|
self.assertEqual(result, GITHUB_TAGS)
|
||||||
|
self.assertEqual(requests_mocker.call_count, 1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_can_fetch_one_page_wo_header(self, requests_mocker):
|
||||||
|
requests_mocker.get(self.url, json=GITHUB_TAGS)
|
||||||
|
result = _fetch_list_from_gitlab(self.url)
|
||||||
|
self.assertEqual(result, GITHUB_TAGS)
|
||||||
|
self.assertEqual(requests_mocker.call_count, 1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_can_fetch_one_page_and_ignore_invalid_header(self, requests_mocker):
|
||||||
|
headers = {
|
||||||
|
'x-total-pages': 'invalid'
|
||||||
|
}
|
||||||
|
requests_mocker.get(self.url, json=GITHUB_TAGS, headers=headers)
|
||||||
|
result = _fetch_list_from_gitlab(self.url)
|
||||||
|
self.assertEqual(result, GITHUB_TAGS)
|
||||||
|
self.assertEqual(requests_mocker.call_count, 1)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_can_fetch_multiple_pages(self, requests_mocker):
|
||||||
|
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
|
||||||
|
headers = {
|
||||||
|
'x-total-pages': str(total_pages)
|
||||||
|
}
|
||||||
|
requests_mocker.get(self.url, json=self.my_callback, headers=headers)
|
||||||
|
result = _fetch_list_from_gitlab(self.url)
|
||||||
|
self.assertEqual(result, GITHUB_TAGS)
|
||||||
|
self.assertEqual(requests_mocker.call_count, total_pages)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_can_fetch_given_number_of_pages_only(self, requests_mocker):
|
||||||
|
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
|
||||||
|
headers = {
|
||||||
|
'x-total-pages': str(total_pages)
|
||||||
|
}
|
||||||
|
requests_mocker.get(self.url, json=self.my_callback, headers=headers)
|
||||||
|
max_pages = 2
|
||||||
|
result = _fetch_list_from_gitlab(self.url, max_pages=max_pages)
|
||||||
|
self.assertEqual(result, GITHUB_TAGS[:4])
|
||||||
|
self.assertEqual(requests_mocker.call_count, max_pages)
|
||||||
@@ -22,13 +22,6 @@ urlpatterns = [
|
|||||||
r'^account/characters/add/$',
|
r'^account/characters/add/$',
|
||||||
views.add_character,
|
views.add_character,
|
||||||
name='add_character'
|
name='add_character'
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^help/$',
|
|
||||||
login_required(
|
|
||||||
TemplateView.as_view(template_name='allianceauth/help.html')
|
|
||||||
),
|
|
||||||
name='help'
|
|
||||||
),
|
|
||||||
url(r'^dashboard/$', views.dashboard, name='dashboard'),
|
url(r'^dashboard/$', views.dashboard, name='dashboard'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class CorpStats(MenuItemHook):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(self,
|
||||||
_('Corporation Stats'),
|
_('Corporation Stats'),
|
||||||
'fa fa-share-alt fa-fw',
|
'fas fa-share-alt fa-fw',
|
||||||
'corputils:view',
|
'corputils:view',
|
||||||
navactive=['corputils:'])
|
navactive=['corputils:'])
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -17,9 +17,9 @@ class CorpStatsManagerTestCase(TestCase):
|
|||||||
cls.user = AuthUtils.create_user('test')
|
cls.user = AuthUtils.create_user('test')
|
||||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
||||||
cls.user.profile.refresh_from_db()
|
cls.user.profile.refresh_from_db()
|
||||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
cls.alliance = EveAllianceInfo.objects.create(alliance_id=3, alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id=2)
|
||||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
|
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
|
||||||
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z')
|
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
|
||||||
cls.corpstats = CorpStats.objects.create(corp=cls.corp, token=cls.token)
|
cls.corpstats = CorpStats.objects.create(corp=cls.corp, token=cls.token)
|
||||||
cls.view_corp_permission = Permission.objects.get_by_natural_key('view_corp_corpstats', 'corputils', 'corpstats')
|
cls.view_corp_permission = Permission.objects.get_by_natural_key('view_corp_corpstats', 'corputils', 'corpstats')
|
||||||
cls.view_alliance_permission = Permission.objects.get_by_natural_key('view_alliance_corpstats', 'corputils', 'corpstats')
|
cls.view_alliance_permission = Permission.objects.get_by_natural_key('view_alliance_corpstats', 'corputils', 'corpstats')
|
||||||
@@ -66,9 +66,9 @@ class CorpStatsUpdateTestCase(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.user = AuthUtils.create_user('test')
|
cls.user = AuthUtils.create_user('test')
|
||||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id=2, corp_name='test_corp', corp_ticker='TEST', alliance_id=3, alliance_name='TEST')
|
||||||
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z')
|
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
|
||||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', member_count=1)
|
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.corpstats = CorpStats.objects.get_or_create(token=self.token, corp=self.corp)[0]
|
self.corpstats = CorpStats.objects.get_or_create(token=self.token, corp=self.corp)[0]
|
||||||
@@ -88,11 +88,11 @@ class CorpStatsUpdateTestCase(TestCase):
|
|||||||
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
||||||
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
|
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
|
||||||
self.corpstats.update()
|
self.corpstats.update()
|
||||||
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists())
|
self.assertTrue(CorpMember.objects.filter(character_id=1, character_name='test character', corpstats=self.corpstats).exists())
|
||||||
|
|
||||||
@mock.patch('esi.clients.SwaggerClient')
|
@mock.patch('esi.clients.SwaggerClient')
|
||||||
def test_update_remove_member(self, SwaggerClient):
|
def test_update_remove_member(self, SwaggerClient):
|
||||||
CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats)
|
CorpMember.objects.create(character_id=2, character_name='old test character', corpstats=self.corpstats)
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
||||||
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
||||||
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
|
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
|
||||||
@@ -130,15 +130,15 @@ class CorpStatsPropertiesTestCase(TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.user = AuthUtils.create_user('test')
|
cls.user = AuthUtils.create_user('test')
|
||||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id=2, corp_name='test_corp', corp_ticker='TEST', alliance_id=3, alliance_name='TEST')
|
||||||
cls.user.profile.refresh_from_db()
|
cls.user.profile.refresh_from_db()
|
||||||
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z')
|
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
|
||||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', member_count=1)
|
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
|
||||||
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
|
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
|
||||||
cls.character = EveCharacter.objects.create(character_name='another test character', character_id='4', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST')
|
cls.character = EveCharacter.objects.create(character_name='another test character', character_id=4, corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
|
||||||
|
|
||||||
def test_member_count(self):
|
def test_member_count(self):
|
||||||
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='1', character_name='test character')
|
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=2, character_name='test character')
|
||||||
self.assertEqual(self.corpstats.member_count, 1)
|
self.assertEqual(self.corpstats.member_count, 1)
|
||||||
member.delete()
|
member.delete()
|
||||||
self.assertEqual(self.corpstats.member_count, 0)
|
self.assertEqual(self.corpstats.member_count, 0)
|
||||||
@@ -147,7 +147,7 @@ class CorpStatsPropertiesTestCase(TestCase):
|
|||||||
AuthUtils.disconnect_signals()
|
AuthUtils.disconnect_signals()
|
||||||
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
|
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
|
||||||
AuthUtils.connect_signals()
|
AuthUtils.connect_signals()
|
||||||
CorpMember.objects.create(corpstats=self.corpstats, character_id='4', character_name='test character')
|
CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
|
||||||
self.assertEqual(self.corpstats.user_count, 1)
|
self.assertEqual(self.corpstats.user_count, 1)
|
||||||
co.delete()
|
co.delete()
|
||||||
self.assertEqual(self.corpstats.user_count, 0)
|
self.assertEqual(self.corpstats.user_count, 0)
|
||||||
@@ -156,7 +156,8 @@ class CorpStatsPropertiesTestCase(TestCase):
|
|||||||
AuthUtils.disconnect_signals()
|
AuthUtils.disconnect_signals()
|
||||||
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
|
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
|
||||||
AuthUtils.connect_signals()
|
AuthUtils.connect_signals()
|
||||||
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='4', character_name='test character')
|
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
|
||||||
|
self.corpstats.refresh_from_db()
|
||||||
self.assertIn(member, self.corpstats.registered_members)
|
self.assertIn(member, self.corpstats.registered_members)
|
||||||
self.assertEqual(self.corpstats.registered_member_count, 1)
|
self.assertEqual(self.corpstats.registered_member_count, 1)
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@ class CorpStatsPropertiesTestCase(TestCase):
|
|||||||
self.assertEqual(self.corpstats.registered_member_count, 0)
|
self.assertEqual(self.corpstats.registered_member_count, 0)
|
||||||
|
|
||||||
def test_unregistered_members(self):
|
def test_unregistered_members(self):
|
||||||
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='4', character_name='test character')
|
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
|
||||||
self.corpstats.refresh_from_db()
|
self.corpstats.refresh_from_db()
|
||||||
self.assertIn(member, self.corpstats.unregistered_members)
|
self.assertIn(member, self.corpstats.unregistered_members)
|
||||||
self.assertEqual(self.corpstats.unregistered_member_count, 1)
|
self.assertEqual(self.corpstats.unregistered_member_count, 1)
|
||||||
@@ -178,13 +179,13 @@ class CorpStatsPropertiesTestCase(TestCase):
|
|||||||
|
|
||||||
def test_mains(self):
|
def test_mains(self):
|
||||||
# test when is a main
|
# test when is a main
|
||||||
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='1', character_name='test character')
|
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=1, character_name='test character')
|
||||||
self.assertIn(member, self.corpstats.mains)
|
self.assertIn(member, self.corpstats.mains)
|
||||||
self.assertEqual(self.corpstats.main_count, 1)
|
self.assertEqual(self.corpstats.main_count, 1)
|
||||||
|
|
||||||
# test when is an alt
|
# test when is an alt
|
||||||
old_main = self.user.profile.main_character
|
old_main = self.user.profile.main_character
|
||||||
character = EveCharacter.objects.create(character_name='other character', character_id=10, corporation_name='test corp', corporation_id='2', corporation_ticker='TEST')
|
character = EveCharacter.objects.create(character_name='other character', character_id=10, corporation_name='test corp', corporation_id=2, corporation_ticker='TEST')
|
||||||
AuthUtils.disconnect_signals()
|
AuthUtils.disconnect_signals()
|
||||||
co = CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
co = CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
||||||
self.user.profile.main_character = character
|
self.user.profile.main_character = character
|
||||||
@@ -208,7 +209,7 @@ class CorpStatsPropertiesTestCase(TestCase):
|
|||||||
self.assertEqual(self.corpstats.corp_logo(size=128), 'https://images.evetech.net/corporations/2/logo?size=128')
|
self.assertEqual(self.corpstats.corp_logo(size=128), 'https://images.evetech.net/corporations/2/logo?size=128')
|
||||||
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/1/logo?size=128')
|
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/1/logo?size=128')
|
||||||
|
|
||||||
alliance = EveAllianceInfo.objects.create(alliance_name='test alliance', alliance_id='3', alliance_ticker='TEST', executor_corp_id='2')
|
alliance = EveAllianceInfo.objects.create(alliance_name='test alliance', alliance_id=3, alliance_ticker='TEST', executor_corp_id=2)
|
||||||
self.corp.alliance = alliance
|
self.corp.alliance = alliance
|
||||||
self.corp.save()
|
self.corp.save()
|
||||||
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/3/logo?size=128')
|
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/3/logo?size=128')
|
||||||
@@ -221,14 +222,14 @@ class CorpMemberTestCase(TestCase):
|
|||||||
cls.user = AuthUtils.create_user('test')
|
cls.user = AuthUtils.create_user('test')
|
||||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
||||||
cls.user.profile.refresh_from_db()
|
cls.user.profile.refresh_from_db()
|
||||||
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='a')
|
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='a')
|
||||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', member_count=1)
|
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
|
||||||
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
|
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
|
||||||
cls.member = CorpMember.objects.create(corpstats=cls.corpstats, character_id='2', character_name='other test character')
|
cls.member = CorpMember.objects.create(corpstats=cls.corpstats, character_id=2, character_name='other test character')
|
||||||
|
|
||||||
def test_character(self):
|
def test_character(self):
|
||||||
self.assertIsNone(self.member.character)
|
self.assertIsNone(self.member.character)
|
||||||
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST')
|
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
|
||||||
self.assertEqual(self.member.character, character)
|
self.assertEqual(self.member.character, character)
|
||||||
|
|
||||||
def test_main_character(self):
|
def test_main_character(self):
|
||||||
@@ -238,7 +239,7 @@ class CorpMemberTestCase(TestCase):
|
|||||||
self.assertIsNone(self.member.main_character)
|
self.assertIsNone(self.member.main_character)
|
||||||
|
|
||||||
# test when member.character is not None but also not a main
|
# test when member.character is not None but also not a main
|
||||||
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST')
|
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
|
||||||
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
||||||
self.member.refresh_from_db()
|
self.member.refresh_from_db()
|
||||||
self.assertNotEqual(self.member.main_character, self.member.character)
|
self.assertNotEqual(self.member.main_character, self.member.character)
|
||||||
@@ -260,14 +261,14 @@ class CorpMemberTestCase(TestCase):
|
|||||||
def test_alts(self):
|
def test_alts(self):
|
||||||
self.assertListEqual(self.member.alts, [])
|
self.assertListEqual(self.member.alts, [])
|
||||||
|
|
||||||
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST')
|
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
|
||||||
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
||||||
self.assertIn(character, self.member.alts)
|
self.assertIn(character, self.member.alts)
|
||||||
|
|
||||||
def test_registered(self):
|
def test_registered(self):
|
||||||
self.assertFalse(self.member.registered)
|
self.assertFalse(self.member.registered)
|
||||||
AuthUtils.disconnect_signals()
|
AuthUtils.disconnect_signals()
|
||||||
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST')
|
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
|
||||||
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
|
||||||
self.assertTrue(self.member.registered)
|
self.assertTrue(self.member.registered)
|
||||||
AuthUtils.connect_signals()
|
AuthUtils.connect_signals()
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
# It contains of modules for views and templatetags for templates
|
# It contains of modules for views and templatetags for templates
|
||||||
|
|
||||||
# list of all eve entity categories as defined in ESI
|
# list of all eve entity categories as defined in ESI
|
||||||
ESI_CATEGORY_AGENT = "agent"
|
_ESI_CATEGORY_AGENT = "agent"
|
||||||
ESI_CATEGORY_ALLIANCE = "alliance"
|
_ESI_CATEGORY_ALLIANCE = "alliance"
|
||||||
ESI_CATEGORY_CHARACTER = "character"
|
_ESI_CATEGORY_CHARACTER = "character"
|
||||||
ESI_CATEGORY_CONSTELLATION = "constellation"
|
_ESI_CATEGORY_CONSTELLATION = "constellation"
|
||||||
ESI_CATEGORY_CORPORATION = "corporation"
|
_ESI_CATEGORY_CORPORATION = "corporation"
|
||||||
ESI_CATEGORY_FACTION = "faction"
|
_ESI_CATEGORY_FACTION = "faction"
|
||||||
ESI_CATEGORY_INVENTORYTYPE = "inventory_type"
|
_ESI_CATEGORY_INVENTORYTYPE = "inventory_type"
|
||||||
ESI_CATEGORY_REGION = "region"
|
_ESI_CATEGORY_REGION = "region"
|
||||||
ESI_CATEGORY_SOLARSYSTEM = "solar_system"
|
_ESI_CATEGORY_SOLARSYSTEM = "solar_system"
|
||||||
ESI_CATEGORY_STATION = "station"
|
_ESI_CATEGORY_STATION = "station"
|
||||||
ESI_CATEGORY_WORMHOLE = "wormhole"
|
_ESI_CATEGORY_WORMHOLE = "wormhole"
|
||||||
|
|||||||
@@ -2,24 +2,30 @@
|
|||||||
|
|
||||||
from urllib.parse import urljoin, quote
|
from urllib.parse import urljoin, quote
|
||||||
|
|
||||||
from . import *
|
from . import (
|
||||||
|
_ESI_CATEGORY_ALLIANCE,
|
||||||
|
_ESI_CATEGORY_CORPORATION,
|
||||||
|
_ESI_CATEGORY_REGION,
|
||||||
|
_ESI_CATEGORY_SOLARSYSTEM
|
||||||
|
)
|
||||||
|
|
||||||
BASE_URL = 'http://evemaps.dotlan.net'
|
|
||||||
|
_BASE_URL = 'http://evemaps.dotlan.net'
|
||||||
|
|
||||||
|
|
||||||
def _build_url(category: str, name: str) -> str:
|
def _build_url(category: str, name: str) -> str:
|
||||||
"""return url to profile page for an eve entity"""
|
"""return url to profile page for an eve entity"""
|
||||||
|
|
||||||
if category == ESI_CATEGORY_ALLIANCE:
|
if category == _ESI_CATEGORY_ALLIANCE:
|
||||||
partial = 'alliance'
|
partial = 'alliance'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_CORPORATION:
|
elif category == _ESI_CATEGORY_CORPORATION:
|
||||||
partial = 'corp'
|
partial = 'corp'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_REGION:
|
elif category == _ESI_CATEGORY_REGION:
|
||||||
partial = 'map'
|
partial = 'map'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_SOLARSYSTEM:
|
elif category == _ESI_CATEGORY_SOLARSYSTEM:
|
||||||
partial = 'system'
|
partial = 'system'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -28,7 +34,7 @@ def _build_url(category: str, name: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
url = urljoin(
|
url = urljoin(
|
||||||
BASE_URL,
|
_BASE_URL,
|
||||||
'{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
|
'{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -37,16 +43,19 @@ def _build_url(category: str, name: str) -> str:
|
|||||||
|
|
||||||
def alliance_url(name: str) -> str:
|
def alliance_url(name: str) -> str:
|
||||||
"""url for page about given alliance on dotlan"""
|
"""url for page about given alliance on dotlan"""
|
||||||
return _build_url(ESI_CATEGORY_ALLIANCE, name)
|
return _build_url(_ESI_CATEGORY_ALLIANCE, name)
|
||||||
|
|
||||||
|
|
||||||
def corporation_url(name: str) -> str:
|
def corporation_url(name: str) -> str:
|
||||||
"""url for page about given corporation on dotlan"""
|
"""url for page about given corporation on dotlan"""
|
||||||
return _build_url(ESI_CATEGORY_CORPORATION, name)
|
return _build_url(_ESI_CATEGORY_CORPORATION, name)
|
||||||
|
|
||||||
|
|
||||||
def region_url(name: str) -> str:
|
def region_url(name: str) -> str:
|
||||||
"""url for page about given region on dotlan"""
|
"""url for page about given region on dotlan"""
|
||||||
return _build_url(ESI_CATEGORY_REGION, name)
|
return _build_url(_ESI_CATEGORY_REGION, name)
|
||||||
|
|
||||||
|
|
||||||
def solar_system_url(name: str) -> str:
|
def solar_system_url(name: str) -> str:
|
||||||
"""url for page about given solar system on dotlan"""
|
"""url for page about given solar system on dotlan"""
|
||||||
return _build_url(ESI_CATEGORY_SOLARSYSTEM, name)
|
return _build_url(_ESI_CATEGORY_SOLARSYSTEM, name)
|
||||||
|
|||||||
129
allianceauth/eveonline/evelinks/eveimageserver.py
Normal file
129
allianceauth/eveonline/evelinks/eveimageserver.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
from . import (
|
||||||
|
_ESI_CATEGORY_ALLIANCE,
|
||||||
|
_ESI_CATEGORY_CHARACTER,
|
||||||
|
_ESI_CATEGORY_CORPORATION,
|
||||||
|
_ESI_CATEGORY_INVENTORYTYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
|
||||||
|
_DEFAULT_IMAGE_SIZE = 32
|
||||||
|
|
||||||
|
|
||||||
|
def _eve_entity_image_url(
|
||||||
|
category: str,
|
||||||
|
entity_id: int,
|
||||||
|
size: int = 32,
|
||||||
|
variant: str = None,
|
||||||
|
tenant: str = None,
|
||||||
|
) -> str:
|
||||||
|
"""returns image URL for an Eve Online ID.
|
||||||
|
Supported categories: alliance, corporation, character, inventory_type
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- category: category of the ID, see ESI category constants
|
||||||
|
- entity_id: Eve ID of the entity
|
||||||
|
- size: (optional) render size of the image.must be between 32 (default) and 1024
|
||||||
|
- variant: (optional) image variant for category. currently not relevant.
|
||||||
|
- tenant: (optional) Eve Server, either `tranquility`(default) or `singularity`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- URL string for the requested image on the Eve image server
|
||||||
|
|
||||||
|
Exceptions:
|
||||||
|
- Throws ValueError on invalid input
|
||||||
|
"""
|
||||||
|
|
||||||
|
# input validations
|
||||||
|
categories = {
|
||||||
|
_ESI_CATEGORY_ALLIANCE: {
|
||||||
|
'endpoint': 'alliances',
|
||||||
|
'variants': ['logo']
|
||||||
|
},
|
||||||
|
_ESI_CATEGORY_CORPORATION: {
|
||||||
|
'endpoint': 'corporations',
|
||||||
|
'variants': ['logo']
|
||||||
|
},
|
||||||
|
_ESI_CATEGORY_CHARACTER: {
|
||||||
|
'endpoint': 'characters',
|
||||||
|
'variants': ['portrait']
|
||||||
|
},
|
||||||
|
_ESI_CATEGORY_INVENTORYTYPE: {
|
||||||
|
'endpoint': 'types',
|
||||||
|
'variants': ['icon', 'render']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tenants = ['tranquility', 'singularity']
|
||||||
|
|
||||||
|
if not entity_id:
|
||||||
|
raise ValueError('Invalid entity_id: {}'.format(entity_id))
|
||||||
|
else:
|
||||||
|
entity_id = int(entity_id)
|
||||||
|
|
||||||
|
if not size or size < 32 or size > 1024 or (size & (size - 1) != 0):
|
||||||
|
raise ValueError('Invalid size: {}'.format(size))
|
||||||
|
|
||||||
|
if category not in categories:
|
||||||
|
raise ValueError('Invalid category {}'.format(category))
|
||||||
|
else:
|
||||||
|
endpoint = categories[category]['endpoint']
|
||||||
|
|
||||||
|
if variant:
|
||||||
|
if variant not in categories[category]['variants']:
|
||||||
|
raise ValueError('Invalid variant {} for category {}'.format(
|
||||||
|
variant,
|
||||||
|
category
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
variant = categories[category]['variants'][0]
|
||||||
|
|
||||||
|
if tenant and tenant not in tenants:
|
||||||
|
raise ValueError('Invalid tenant {}'.format(tenant))
|
||||||
|
|
||||||
|
# compose result URL
|
||||||
|
result = '{}/{}/{}/{}?size={}'.format(
|
||||||
|
_EVE_IMAGE_SERVER_URL,
|
||||||
|
endpoint,
|
||||||
|
entity_id,
|
||||||
|
variant,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
if tenant:
|
||||||
|
result += '&tenant={}'.format(tenant)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def alliance_logo_url(alliance_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||||
|
"""image URL for the given alliance ID"""
|
||||||
|
return _eve_entity_image_url(_ESI_CATEGORY_ALLIANCE, alliance_id, size)
|
||||||
|
|
||||||
|
|
||||||
|
def corporation_logo_url(
|
||||||
|
corporation_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
|
"""image URL for the given corporation ID"""
|
||||||
|
return _eve_entity_image_url(
|
||||||
|
_ESI_CATEGORY_CORPORATION, corporation_id, size
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def character_portrait_url(
|
||||||
|
character_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
|
"""image URL for the given character ID"""
|
||||||
|
return _eve_entity_image_url(_ESI_CATEGORY_CHARACTER, character_id, size)
|
||||||
|
|
||||||
|
|
||||||
|
def type_icon_url(type_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||||
|
"""icon image URL for the given type ID"""
|
||||||
|
return _eve_entity_image_url(
|
||||||
|
_ESI_CATEGORY_INVENTORYTYPE, type_id, size, variant='icon'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def type_render_url(type_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||||
|
"""render image URL for the given type ID"""
|
||||||
|
return _eve_entity_image_url(
|
||||||
|
_ESI_CATEGORY_INVENTORYTYPE, type_id, size, variant='render'
|
||||||
|
)
|
||||||
@@ -1,22 +1,27 @@
|
|||||||
# this module generates profile URLs for evewho
|
# this module generates profile URLs for evewho
|
||||||
|
|
||||||
from urllib.parse import urljoin, quote
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from . import *
|
from . import (
|
||||||
|
_ESI_CATEGORY_ALLIANCE,
|
||||||
|
_ESI_CATEGORY_CORPORATION,
|
||||||
|
_ESI_CATEGORY_CHARACTER,
|
||||||
|
)
|
||||||
|
|
||||||
BASE_URL = 'https://evewho.com'
|
|
||||||
|
_BASE_URL = 'https://evewho.com'
|
||||||
|
|
||||||
|
|
||||||
def _build_url(category: str, eve_id: int) -> str:
|
def _build_url(category: str, eve_id: int) -> str:
|
||||||
"""return url to profile page for an eve entity"""
|
"""return url to profile page for an eve entity"""
|
||||||
|
|
||||||
if category == ESI_CATEGORY_ALLIANCE:
|
if category == _ESI_CATEGORY_ALLIANCE:
|
||||||
partial = 'alliance'
|
partial = 'alliance'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_CORPORATION:
|
elif category == _ESI_CATEGORY_CORPORATION:
|
||||||
partial = 'corporation'
|
partial = 'corporation'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_CHARACTER:
|
elif category == _ESI_CATEGORY_CHARACTER:
|
||||||
partial = 'character'
|
partial = 'character'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -25,7 +30,7 @@ def _build_url(category: str, eve_id: int) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
url = urljoin(
|
url = urljoin(
|
||||||
BASE_URL,
|
_BASE_URL,
|
||||||
'{}/{}'.format(partial, int(eve_id))
|
'{}/{}'.format(partial, int(eve_id))
|
||||||
)
|
)
|
||||||
return url
|
return url
|
||||||
@@ -33,12 +38,14 @@ def _build_url(category: str, eve_id: int) -> str:
|
|||||||
|
|
||||||
def alliance_url(eve_id: int) -> str:
|
def alliance_url(eve_id: int) -> str:
|
||||||
"""url for page about given alliance on evewho"""
|
"""url for page about given alliance on evewho"""
|
||||||
return _build_url(ESI_CATEGORY_ALLIANCE, eve_id)
|
return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
|
||||||
|
|
||||||
|
|
||||||
def character_url(eve_id: int) -> str:
|
def character_url(eve_id: int) -> str:
|
||||||
"""url for page about given character on evewho"""
|
"""url for page about given character on evewho"""
|
||||||
return _build_url(ESI_CATEGORY_CHARACTER, eve_id)
|
return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
|
||||||
|
|
||||||
|
|
||||||
def corporation_url(eve_id: int) -> str:
|
def corporation_url(eve_id: int) -> str:
|
||||||
"""url for page about given corporation on evewho"""
|
"""url for page about given corporation on evewho"""
|
||||||
return _build_url(ESI_CATEGORY_CORPORATION, eve_id)
|
return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
from .. import dotlan, zkillboard, evewho
|
from .. import dotlan, zkillboard, evewho, eveimageserver
|
||||||
from ...templatetags import evelinks
|
from ...templatetags import evelinks
|
||||||
|
|
||||||
|
|
||||||
@@ -90,3 +90,115 @@ class TestZkillboard(TestCase):
|
|||||||
'https://zkillboard.com/system/12345678/'
|
'https://zkillboard.com/system/12345678/'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEveImageServer(TestCase):
|
||||||
|
"""unit test for eveimageserver"""
|
||||||
|
|
||||||
|
def test_sizes(self):
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, size=32),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, size=64),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=64'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, size=128),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=128'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, size=256),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=256'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, size=512),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=512'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, size=1024),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=1024'
|
||||||
|
)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('corporation', 42, size=-5)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('corporation', 42, size=0)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('corporation', 42, size=31)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('corporation', 42, size=1025)
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('corporation', 42, size=2048)
|
||||||
|
|
||||||
|
|
||||||
|
def test_variant(self):
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, variant='portrait'),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('alliance', 42, variant='logo'),
|
||||||
|
'https://images.evetech.net/alliances/42/logo?size=32'
|
||||||
|
)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, variant='logo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_alliance(self):
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('alliance', 42),
|
||||||
|
'https://images.evetech.net/alliances/42/logo?size=32'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('corporation', 42),
|
||||||
|
'https://images.evetech.net/corporations/42/logo?size=32'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||||
|
)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('station', 42)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tenants(self):
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, tenant='tranquility'),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, tenant='singularity'),
|
||||||
|
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
|
||||||
|
)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
eveimageserver._eve_entity_image_url('character', 42, tenant='xxx')
|
||||||
|
|
||||||
|
def test_alliance_logo_url(self):
|
||||||
|
expected = 'https://images.evetech.net/alliances/42/logo?size=128'
|
||||||
|
self.assertEqual(eveimageserver.alliance_logo_url(42, 128), expected)
|
||||||
|
|
||||||
|
def test_corporation_logo_url(self):
|
||||||
|
expected = 'https://images.evetech.net/corporations/42/logo?size=128'
|
||||||
|
self.assertEqual(eveimageserver.corporation_logo_url(42, 128), expected)
|
||||||
|
|
||||||
|
def test_character_portrait_url(self):
|
||||||
|
expected = 'https://images.evetech.net/characters/42/portrait?size=128'
|
||||||
|
self.assertEqual(
|
||||||
|
eveimageserver.character_portrait_url(42, 128), expected
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_type_icon_url(self):
|
||||||
|
expected = 'https://images.evetech.net/types/42/icon?size=128'
|
||||||
|
self.assertEqual(eveimageserver.type_icon_url(42, 128), expected)
|
||||||
|
|
||||||
|
def test_type_render_url(self):
|
||||||
|
expected = 'https://images.evetech.net/types/42/render?size=128'
|
||||||
|
self.assertEqual(eveimageserver.type_render_url(42, 128), expected)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
from .. import dotlan, zkillboard, evewho
|
from .. import eveimageserver, evewho, dotlan, zkillboard
|
||||||
from ...templatetags import evelinks
|
from ...templatetags import evelinks
|
||||||
|
|
||||||
|
|
||||||
@@ -332,3 +332,28 @@ class TestTemplateTags(TestCase):
|
|||||||
''
|
''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_type_icon_url(self):
|
||||||
|
expected = eveimageserver.type_icon_url(123)
|
||||||
|
self.assertEqual(evelinks.type_icon_url(123), expected)
|
||||||
|
|
||||||
|
expected = eveimageserver.type_icon_url(123, 128)
|
||||||
|
self.assertEqual(evelinks.type_icon_url(123, 128), expected)
|
||||||
|
|
||||||
|
expected = ''
|
||||||
|
self.assertEqual(evelinks.type_icon_url(123, 99), expected)
|
||||||
|
|
||||||
|
expected = ''
|
||||||
|
self.assertEqual(evelinks.type_icon_url(None), expected)
|
||||||
|
|
||||||
|
def test_type_render_url(self):
|
||||||
|
expected = eveimageserver.type_render_url(123)
|
||||||
|
self.assertEqual(evelinks.type_render_url(123), expected)
|
||||||
|
|
||||||
|
expected = eveimageserver.type_render_url(123, 128)
|
||||||
|
self.assertEqual(evelinks.type_render_url(123, 128), expected)
|
||||||
|
|
||||||
|
expected = ''
|
||||||
|
self.assertEqual(evelinks.type_render_url(123, 99), expected)
|
||||||
|
|
||||||
|
expected = ''
|
||||||
|
self.assertEqual(evelinks.type_render_url(None), expected)
|
||||||
@@ -1,28 +1,35 @@
|
|||||||
# this module generates profile URLs for zKillboard
|
# this module generates profile URLs for zKillboard
|
||||||
|
|
||||||
from urllib.parse import urljoin, quote
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from . import *
|
from . import (
|
||||||
|
_ESI_CATEGORY_ALLIANCE,
|
||||||
|
_ESI_CATEGORY_CORPORATION,
|
||||||
|
_ESI_CATEGORY_CHARACTER,
|
||||||
|
_ESI_CATEGORY_REGION,
|
||||||
|
_ESI_CATEGORY_SOLARSYSTEM
|
||||||
|
)
|
||||||
|
|
||||||
BASE_URL = 'https://zkillboard.com'
|
|
||||||
|
_BASE_URL = 'https://zkillboard.com'
|
||||||
|
|
||||||
|
|
||||||
def _build_url(category: str, eve_id: int) -> str:
|
def _build_url(category: str, eve_id: int) -> str:
|
||||||
"""return url to profile page for an eve entity"""
|
"""return url to profile page for an eve entity"""
|
||||||
|
|
||||||
if category == ESI_CATEGORY_ALLIANCE:
|
if category == _ESI_CATEGORY_ALLIANCE:
|
||||||
partial = 'alliance'
|
partial = 'alliance'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_CORPORATION:
|
elif category == _ESI_CATEGORY_CORPORATION:
|
||||||
partial = 'corporation'
|
partial = 'corporation'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_CHARACTER:
|
elif category == _ESI_CATEGORY_CHARACTER:
|
||||||
partial = 'character'
|
partial = 'character'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_REGION:
|
elif category == _ESI_CATEGORY_REGION:
|
||||||
partial = 'region'
|
partial = 'region'
|
||||||
|
|
||||||
elif category == ESI_CATEGORY_SOLARSYSTEM:
|
elif category == _ESI_CATEGORY_SOLARSYSTEM:
|
||||||
partial = 'system'
|
partial = 'system'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -31,7 +38,7 @@ def _build_url(category: str, eve_id: int) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
url = urljoin(
|
url = urljoin(
|
||||||
BASE_URL,
|
_BASE_URL,
|
||||||
'{}/{}/'.format(partial, int(eve_id))
|
'{}/{}/'.format(partial, int(eve_id))
|
||||||
)
|
)
|
||||||
return url
|
return url
|
||||||
@@ -39,19 +46,23 @@ def _build_url(category: str, eve_id: int) -> str:
|
|||||||
|
|
||||||
def alliance_url(eve_id: int) -> str:
|
def alliance_url(eve_id: int) -> str:
|
||||||
"""url for page about given alliance on zKillboard"""
|
"""url for page about given alliance on zKillboard"""
|
||||||
return _build_url(ESI_CATEGORY_ALLIANCE, eve_id)
|
return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
|
||||||
|
|
||||||
|
|
||||||
def character_url(eve_id: int) -> str:
|
def character_url(eve_id: int) -> str:
|
||||||
"""url for page about given character on zKillboard"""
|
"""url for page about given character on zKillboard"""
|
||||||
return _build_url(ESI_CATEGORY_CHARACTER, eve_id)
|
return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
|
||||||
|
|
||||||
|
|
||||||
def corporation_url(eve_id: int) -> str:
|
def corporation_url(eve_id: int) -> str:
|
||||||
"""url for page about given corporation on zKillboard"""
|
"""url for page about given corporation on zKillboard"""
|
||||||
return _build_url(ESI_CATEGORY_CORPORATION, eve_id)
|
return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)
|
||||||
|
|
||||||
|
|
||||||
def region_url(eve_id: int) -> str:
|
def region_url(eve_id: int) -> str:
|
||||||
"""url for page about given region on zKillboard"""
|
"""url for page about given region on zKillboard"""
|
||||||
return _build_url(ESI_CATEGORY_REGION, eve_id)
|
return _build_url(_ESI_CATEGORY_REGION, eve_id)
|
||||||
|
|
||||||
|
|
||||||
def solar_system_url(eve_id: int) -> str:
|
def solar_system_url(eve_id: int) -> str:
|
||||||
return _build_url(ESI_CATEGORY_SOLARSYSTEM, eve_id)
|
return _build_url(_ESI_CATEGORY_SOLARSYSTEM, eve_id)
|
||||||
|
|||||||
@@ -89,4 +89,6 @@ class EveCorporationManager(models.Manager):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_corporation(self, corp_id):
|
def update_corporation(self, corp_id):
|
||||||
return self.get(corporation_id=corp_id).update_corporation(self.provider.get_corporation(corp_id))
|
return self\
|
||||||
|
.get(corporation_id=corp_id)\
|
||||||
|
.update_corporation(self.provider.get_corporation(corp_id))
|
||||||
|
|||||||
43
allianceauth/eveonline/migrations/0011_ids_to_integers.py
Normal file
43
allianceauth/eveonline/migrations/0011_ids_to_integers.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 2.2.12 on 2020-05-25 02:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('eveonline', '0010_alliance_ticker'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eveallianceinfo',
|
||||||
|
name='alliance_id',
|
||||||
|
field=models.PositiveIntegerField(unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eveallianceinfo',
|
||||||
|
name='executor_corp_id',
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='alliance_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='character_id',
|
||||||
|
field=models.PositiveIntegerField(unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='corporation_id',
|
||||||
|
field=models.PositiveIntegerField(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='evecorporationinfo',
|
||||||
|
name='corporation_id',
|
||||||
|
field=models.PositiveIntegerField(unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
33
allianceauth/eveonline/migrations/0012_index_additions.py
Normal file
33
allianceauth/eveonline/migrations/0012_index_additions.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 2.2.12 on 2020-05-26 02:09
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('eveonline', '0011_ids_to_integers'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='eveallianceinfo',
|
||||||
|
index=models.Index(fields=['executor_corp_id'], name='eveonline_e_executo_7f3280_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='evecharacter',
|
||||||
|
index=models.Index(fields=['corporation_id'], name='eveonline_e_corpora_cb4cd9_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='evecharacter',
|
||||||
|
index=models.Index(fields=['alliance_id'], name='eveonline_e_allianc_39ee2a_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='evecharacter',
|
||||||
|
index=models.Index(fields=['corporation_name'], name='eveonline_e_corpora_893c60_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='evecharacter',
|
||||||
|
index=models.Index(fields=['alliance_name'], name='eveonline_e_allianc_63fd98_idx'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -5,109 +5,35 @@ from .managers import EveCharacterManager, EveCharacterProviderManager
|
|||||||
from .managers import EveCorporationManager, EveCorporationProviderManager
|
from .managers import EveCorporationManager, EveCorporationProviderManager
|
||||||
from .managers import EveAllianceManager, EveAllianceProviderManager
|
from .managers import EveAllianceManager, EveAllianceProviderManager
|
||||||
from . import providers
|
from . import providers
|
||||||
|
from .evelinks import eveimageserver
|
||||||
|
|
||||||
|
_DEFAULT_IMAGE_SIZE = 32
|
||||||
EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
|
|
||||||
|
|
||||||
|
|
||||||
def _eve_entity_image_url(
|
|
||||||
category: str,
|
|
||||||
id: int,
|
|
||||||
size: int = 32,
|
|
||||||
variant: str = None,
|
|
||||||
tenant: str = None,
|
|
||||||
) -> str:
|
|
||||||
"""returns image URL for an Eve Online ID.
|
|
||||||
Supported categories: `alliance`, `corporation`, `character`
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- category: category of the ID
|
|
||||||
- id: Eve ID of the entity
|
|
||||||
- size: (optional) render size of the image.must be between 32 (default) and 1024
|
|
||||||
- variant: (optional) image variant for category. currently not relevant.
|
|
||||||
- tentant: (optional) Eve Server, either `tranquility`(default) or `singularity`
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- URL string for the requested image on the Eve image server
|
|
||||||
|
|
||||||
Exceptions:
|
|
||||||
- Throws ValueError on invalid input
|
|
||||||
"""
|
|
||||||
|
|
||||||
# input validations
|
|
||||||
categories = {
|
|
||||||
'alliance': {
|
|
||||||
'endpoint': 'alliances',
|
|
||||||
'variants': [
|
|
||||||
'logo'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'corporation': {
|
|
||||||
'endpoint': 'corporations',
|
|
||||||
'variants': [
|
|
||||||
'logo'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'character': {
|
|
||||||
'endpoint': 'characters',
|
|
||||||
'variants': [
|
|
||||||
'portrait'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tenants = ['tranquility', 'singularity']
|
|
||||||
|
|
||||||
if size < 32 or size > 1024 or (size & (size - 1) != 0):
|
|
||||||
raise ValueError('Invalid size: {}'.format(size))
|
|
||||||
|
|
||||||
if category not in categories:
|
|
||||||
raise ValueError('Invalid category {}'.format(category))
|
|
||||||
else:
|
|
||||||
endpoint = categories[category]['endpoint']
|
|
||||||
|
|
||||||
if variant:
|
|
||||||
if variant not in categories[category]['variants']:
|
|
||||||
raise ValueError('Invalid variant {} for category {}'.format(
|
|
||||||
variant,
|
|
||||||
category
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
variant = categories[category]['variants'][0]
|
|
||||||
|
|
||||||
if tenant and tenant not in tenants:
|
|
||||||
raise ValueError('Invalid tentant {}'.format(tenant))
|
|
||||||
|
|
||||||
# compose result URL
|
|
||||||
result = '{}/{}/{}/{}?size={}'.format(
|
|
||||||
EVE_IMAGE_SERVER_URL,
|
|
||||||
endpoint,
|
|
||||||
id,
|
|
||||||
variant,
|
|
||||||
size
|
|
||||||
)
|
|
||||||
if tenant:
|
|
||||||
result += '&tenant={}'.format(tenant)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class EveAllianceInfo(models.Model):
|
class EveAllianceInfo(models.Model):
|
||||||
alliance_id = models.CharField(max_length=254, unique=True)
|
alliance_id = models.PositiveIntegerField(unique=True)
|
||||||
alliance_name = models.CharField(max_length=254, unique=True)
|
alliance_name = models.CharField(max_length=254, unique=True)
|
||||||
alliance_ticker = models.CharField(max_length=254)
|
alliance_ticker = models.CharField(max_length=254)
|
||||||
executor_corp_id = models.CharField(max_length=254)
|
executor_corp_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
objects = EveAllianceManager()
|
objects = EveAllianceManager()
|
||||||
provider = EveAllianceProviderManager()
|
provider = EveAllianceProviderManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [models.Index(fields=['executor_corp_id',])]
|
||||||
|
|
||||||
def populate_alliance(self):
|
def populate_alliance(self):
|
||||||
alliance = self.provider.get_alliance(self.alliance_id)
|
alliance = self.provider.get_alliance(self.alliance_id)
|
||||||
for corp_id in alliance.corp_ids:
|
for corp_id in alliance.corp_ids:
|
||||||
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
|
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
|
||||||
EveCorporationInfo.objects.create_corporation(corp_id)
|
EveCorporationInfo.objects.create_corporation(corp_id)
|
||||||
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(alliance=self)
|
EveCorporationInfo.objects.filter(
|
||||||
EveCorporationInfo.objects.filter(alliance=self).exclude(corporation_id__in=alliance.corp_ids).update(
|
corporation_id__in=alliance.corp_ids).update(alliance=self
|
||||||
alliance=None)
|
)
|
||||||
|
EveCorporationInfo.objects\
|
||||||
|
.filter(alliance=self)\
|
||||||
|
.exclude(corporation_id__in=alliance.corp_ids)\
|
||||||
|
.update(alliance=None)
|
||||||
|
|
||||||
def update_alliance(self, alliance: providers.Alliance = None):
|
def update_alliance(self, alliance: providers.Alliance = None):
|
||||||
if alliance is None:
|
if alliance is None:
|
||||||
@@ -120,11 +46,13 @@ class EveAllianceInfo(models.Model):
|
|||||||
return self.alliance_name
|
return self.alliance_name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generic_logo_url(alliance_id: int, size: int = 32) -> str:
|
def generic_logo_url(
|
||||||
|
alliance_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
"""image URL for the given alliance ID"""
|
"""image URL for the given alliance ID"""
|
||||||
return _eve_entity_image_url('alliance', alliance_id, size)
|
return eveimageserver.alliance_logo_url(alliance_id, size)
|
||||||
|
|
||||||
def logo_url(self, size:int = 32) -> str:
|
def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||||
"""image URL of this alliance"""
|
"""image URL of this alliance"""
|
||||||
return self.generic_logo_url(self.alliance_id, size)
|
return self.generic_logo_url(self.alliance_id, size)
|
||||||
|
|
||||||
@@ -150,11 +78,13 @@ class EveAllianceInfo(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class EveCorporationInfo(models.Model):
|
class EveCorporationInfo(models.Model):
|
||||||
corporation_id = models.CharField(max_length=254, unique=True)
|
corporation_id = models.PositiveIntegerField(unique=True)
|
||||||
corporation_name = models.CharField(max_length=254, unique=True)
|
corporation_name = models.CharField(max_length=254, unique=True)
|
||||||
corporation_ticker = models.CharField(max_length=254)
|
corporation_ticker = models.CharField(max_length=254)
|
||||||
member_count = models.IntegerField()
|
member_count = models.IntegerField()
|
||||||
alliance = models.ForeignKey(EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL)
|
alliance = models.ForeignKey(
|
||||||
|
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
|
||||||
objects = EveCorporationManager()
|
objects = EveCorporationManager()
|
||||||
provider = EveCorporationProviderManager()
|
provider = EveCorporationProviderManager()
|
||||||
@@ -174,11 +104,13 @@ class EveCorporationInfo(models.Model):
|
|||||||
return self.corporation_name
|
return self.corporation_name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generic_logo_url(corporation_id: int, size: int = 32) -> str:
|
def generic_logo_url(
|
||||||
|
corporation_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
"""image URL for the given corporation ID"""
|
"""image URL for the given corporation ID"""
|
||||||
return _eve_entity_image_url('corporation', corporation_id, size)
|
return eveimageserver.corporation_logo_url(corporation_id, size)
|
||||||
|
|
||||||
def logo_url(self, size:int = 32) -> str:
|
def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||||
"""image URL for this corporation"""
|
"""image URL for this corporation"""
|
||||||
return self.generic_logo_url(self.corporation_id, size)
|
return self.generic_logo_url(self.corporation_id, size)
|
||||||
|
|
||||||
@@ -204,18 +136,26 @@ class EveCorporationInfo(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class EveCharacter(models.Model):
|
class EveCharacter(models.Model):
|
||||||
character_id = models.CharField(max_length=254, unique=True)
|
character_id = models.PositiveIntegerField(unique=True)
|
||||||
character_name = models.CharField(max_length=254, unique=True)
|
character_name = models.CharField(max_length=254, unique=True)
|
||||||
corporation_id = models.CharField(max_length=254)
|
corporation_id = models.PositiveIntegerField()
|
||||||
corporation_name = models.CharField(max_length=254)
|
corporation_name = models.CharField(max_length=254)
|
||||||
corporation_ticker = models.CharField(max_length=5)
|
corporation_ticker = models.CharField(max_length=5)
|
||||||
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='')
|
alliance_id = models.PositiveIntegerField(blank=True, null=True, default=None)
|
||||||
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
||||||
alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
|
alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
|
||||||
|
|
||||||
objects = EveCharacterManager()
|
objects = EveCharacterManager()
|
||||||
provider = EveCharacterProviderManager()
|
provider = EveCharacterProviderManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['corporation_id',]),
|
||||||
|
models.Index(fields=['alliance_id',]),
|
||||||
|
models.Index(fields=['corporation_name',]),
|
||||||
|
models.Index(fields=['alliance_name',]),
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def alliance(self) -> Union[EveAllianceInfo, None]:
|
def alliance(self) -> Union[EveAllianceInfo, None]:
|
||||||
"""
|
"""
|
||||||
@@ -253,11 +193,13 @@ class EveCharacter(models.Model):
|
|||||||
return self.character_name
|
return self.character_name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generic_portrait_url(character_id: int, size: int = 32) -> str:
|
def generic_portrait_url(
|
||||||
|
character_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
"""image URL for the given character ID"""
|
"""image URL for the given character ID"""
|
||||||
return _eve_entity_image_url('character', character_id, size)
|
return eveimageserver.character_portrait_url(character_id, size)
|
||||||
|
|
||||||
def portrait_url(self, size = 32) -> str:
|
def portrait_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
|
||||||
"""image URL for this character"""
|
"""image URL for this character"""
|
||||||
return self.generic_portrait_url(self.character_id, size)
|
return self.generic_portrait_url(self.character_id, size)
|
||||||
|
|
||||||
@@ -281,7 +223,7 @@ class EveCharacter(models.Model):
|
|||||||
"""image URL for this character"""
|
"""image URL for this character"""
|
||||||
return self.portrait_url(256)
|
return self.portrait_url(256)
|
||||||
|
|
||||||
def corporation_logo_url(self, size = 32) -> str:
|
def corporation_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
|
||||||
"""image URL for corporation of this character"""
|
"""image URL for corporation of this character"""
|
||||||
return EveCorporationInfo.generic_logo_url(self.corporation_id, size)
|
return EveCorporationInfo.generic_logo_url(self.corporation_id, size)
|
||||||
|
|
||||||
@@ -305,7 +247,7 @@ class EveCharacter(models.Model):
|
|||||||
"""image URL for corporation of this character"""
|
"""image URL for corporation of this character"""
|
||||||
return self.corporation_logo_url(256)
|
return self.corporation_logo_url(256)
|
||||||
|
|
||||||
def alliance_logo_url(self, size = 32) -> str:
|
def alliance_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
|
||||||
"""image URL for alliance of this character or empty string"""
|
"""image URL for alliance of this character or empty string"""
|
||||||
if self.alliance_id:
|
if self.alliance_id:
|
||||||
return EveAllianceInfo.generic_logo_url(self.alliance_id, size)
|
return EveAllianceInfo.generic_logo_url(self.alliance_id, size)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
from ..evelinks import evewho, dotlan, zkillboard
|
from ..evelinks import eveimageserver, evewho, dotlan, zkillboard
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ def dotlan_solar_system_url(eve_obj: object) -> str:
|
|||||||
return _generic_evelinks_url(dotlan, 'solar_system_url', eve_obj)
|
return _generic_evelinks_url(dotlan, 'solar_system_url', eve_obj)
|
||||||
|
|
||||||
|
|
||||||
#zkillboard
|
# zkillboard
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def zkillboard_character_url(eve_obj: EveCharacter) -> str:
|
def zkillboard_character_url(eve_obj: EveCharacter) -> str:
|
||||||
@@ -212,7 +212,6 @@ def zkillboard_solar_system_url(eve_obj: object) -> str:
|
|||||||
|
|
||||||
# image urls
|
# image urls
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def character_portrait_url(
|
def character_portrait_url(
|
||||||
eve_obj: object,
|
eve_obj: object,
|
||||||
@@ -284,3 +283,30 @@ def alliance_logo_url(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def type_icon_url(
|
||||||
|
type_id: int,
|
||||||
|
size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
|
"""generates a icon image URL for the given type ID
|
||||||
|
Returns URL or empty string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return eveimageserver.type_icon_url(type_id, size)
|
||||||
|
except ValueError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def type_render_url(
|
||||||
|
type_id: int,
|
||||||
|
size: int = _DEFAULT_IMAGE_SIZE
|
||||||
|
) -> str:
|
||||||
|
"""generates a render image URL for the given type ID
|
||||||
|
Returns URL or empty string
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return eveimageserver.type_render_url(type_id, size)
|
||||||
|
except ValueError:
|
||||||
|
return ''
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class EveCharacterProviderManagerTestCase(TestCase):
|
|||||||
expected = Character()
|
expected = Character()
|
||||||
provider.get_character.return_value = expected
|
provider.get_character.return_value = expected
|
||||||
|
|
||||||
result = EveCharacter.provider.get_character('1234')
|
result = EveCharacter.provider.get_character(1234)
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
@@ -22,30 +22,30 @@ class EveCharacterManagerTestCase(TestCase):
|
|||||||
class TestCharacter(Character):
|
class TestCharacter(Character):
|
||||||
@property
|
@property
|
||||||
def alliance(self):
|
def alliance(self):
|
||||||
return Alliance(id='3456', name='Test Alliance')
|
return Alliance(id=3456, name='Test Alliance')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def corp(self):
|
def corp(self):
|
||||||
return Corporation(
|
return Corporation(
|
||||||
id='2345',
|
id=2345,
|
||||||
name='Test Corp',
|
name='Test Corp',
|
||||||
alliance_id='3456',
|
alliance_id=3456,
|
||||||
ticker='0BUGS'
|
ticker='0BUGS' #lies, blatant lies!
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||||
def test_create_character(self, provider):
|
def test_create_character(self, provider):
|
||||||
# Also covers create_character_obj
|
# Also covers create_character_obj
|
||||||
expected = self.TestCharacter(
|
expected = self.TestCharacter(
|
||||||
id='1234',
|
id=1234,
|
||||||
name='Test Character',
|
name='Test Character',
|
||||||
corp_id='2345',
|
corp_id=2345,
|
||||||
alliance_id='3456'
|
alliance_id=3456
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_character.return_value = expected
|
provider.get_character.return_value = expected
|
||||||
|
|
||||||
result = EveCharacter.objects.create_character('1234')
|
result = EveCharacter.objects.create_character(1234)
|
||||||
|
|
||||||
self.assertEqual(result.character_id, expected.id)
|
self.assertEqual(result.character_id, expected.id)
|
||||||
self.assertEqual(result.character_name, expected.name)
|
self.assertEqual(result.character_name, expected.name)
|
||||||
@@ -59,25 +59,24 @@ class EveCharacterManagerTestCase(TestCase):
|
|||||||
def test_update_character(self, provider):
|
def test_update_character(self, provider):
|
||||||
# Also covers Model.update_character
|
# Also covers Model.update_character
|
||||||
existing = EveCharacter.objects.create(
|
existing = EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='character.corp.id',
|
corporation_id=23457,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id='character.alliance.id',
|
alliance_id=34567,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = self.TestCharacter(
|
expected = self.TestCharacter(
|
||||||
id='1234',
|
id=1234,
|
||||||
name='Test Character',
|
name='Test Character',
|
||||||
corp_id='2345',
|
corp_id=2345,
|
||||||
alliance_id='3456'
|
alliance_id=3456
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_character.return_value = expected
|
provider.get_character.return_value = expected
|
||||||
|
|
||||||
result = EveCharacter.objects.update_character('1234')
|
result = EveCharacter.objects.update_character(1234)
|
||||||
|
|
||||||
self.assertEqual(result.character_id, expected.id)
|
self.assertEqual(result.character_id, expected.id)
|
||||||
self.assertEqual(result.character_name, expected.name)
|
self.assertEqual(result.character_name, expected.name)
|
||||||
@@ -90,23 +89,23 @@ class EveCharacterManagerTestCase(TestCase):
|
|||||||
def test_get_character_by_id(self):
|
def test_get_character_by_id(self):
|
||||||
EveCharacter.objects.all().delete()
|
EveCharacter.objects.all().delete()
|
||||||
EveCharacter.objects.create(
|
EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='character.corp.id',
|
corporation_id=2345,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id='character.alliance.id',
|
alliance_id=3456,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
# try to get existing character
|
# try to get existing character
|
||||||
result = EveCharacter.objects.get_character_by_id('1234')
|
result = EveCharacter.objects.get_character_by_id(1234)
|
||||||
|
|
||||||
self.assertEqual(result.character_id, '1234')
|
self.assertEqual(result.character_id, 1234)
|
||||||
self.assertEqual(result.character_name, 'character.name')
|
self.assertEqual(result.character_name, 'character.name')
|
||||||
|
|
||||||
# try to get non existing character
|
# try to get non existing character
|
||||||
self.assertIsNone(EveCharacter.objects.get_character_by_id('9999'))
|
self.assertIsNone(EveCharacter.objects.get_character_by_id(9999))
|
||||||
|
|
||||||
|
|
||||||
class EveAllianceProviderManagerTestCase(TestCase):
|
class EveAllianceProviderManagerTestCase(TestCase):
|
||||||
@@ -115,7 +114,7 @@ class EveAllianceProviderManagerTestCase(TestCase):
|
|||||||
expected = Alliance()
|
expected = Alliance()
|
||||||
provider.get_alliance.return_value = expected
|
provider.get_alliance.return_value = expected
|
||||||
|
|
||||||
result = EveAllianceInfo.provider.get_alliance('1234')
|
result = EveAllianceInfo.provider.get_alliance(1234)
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
@@ -131,16 +130,16 @@ class EveAllianceManagerTestCase(TestCase):
|
|||||||
def test_create_alliance(self, provider, populate_alliance):
|
def test_create_alliance(self, provider, populate_alliance):
|
||||||
# Also covers create_alliance_obj
|
# Also covers create_alliance_obj
|
||||||
expected = self.TestAlliance(
|
expected = self.TestAlliance(
|
||||||
id='3456',
|
id=3456,
|
||||||
name='Test Alliance',
|
name='Test Alliance',
|
||||||
ticker='TEST',
|
ticker='TEST',
|
||||||
corp_ids=['2345'],
|
corp_ids=[2345],
|
||||||
executor_corp_id='2345'
|
executor_corp_id=2345
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_alliance.return_value = expected
|
provider.get_alliance.return_value = expected
|
||||||
|
|
||||||
result = EveAllianceInfo.objects.create_alliance('3456')
|
result = EveAllianceInfo.objects.create_alliance(3456)
|
||||||
|
|
||||||
self.assertEqual(result.alliance_id, expected.id)
|
self.assertEqual(result.alliance_id, expected.id)
|
||||||
self.assertEqual(result.alliance_name, expected.name)
|
self.assertEqual(result.alliance_name, expected.name)
|
||||||
@@ -152,22 +151,22 @@ class EveAllianceManagerTestCase(TestCase):
|
|||||||
def test_update_alliance(self, provider):
|
def test_update_alliance(self, provider):
|
||||||
# Also covers Model.update_alliance
|
# Also covers Model.update_alliance
|
||||||
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='at1',
|
||||||
executor_corp_id='alliance.executor_corp_id',
|
executor_corp_id=2345,
|
||||||
)
|
)
|
||||||
expected = self.TestAlliance(
|
expected = self.TestAlliance(
|
||||||
id='3456',
|
id=3456,
|
||||||
name='Test Alliance',
|
name='Test Alliance',
|
||||||
ticker='TEST',
|
ticker='TEST',
|
||||||
corp_ids=['2345'],
|
corp_ids=[2345],
|
||||||
executor_corp_id='2345'
|
executor_corp_id=2345
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_alliance.return_value = expected
|
provider.get_alliance.return_value = expected
|
||||||
|
|
||||||
result = EveAllianceInfo.objects.update_alliance('3456')
|
result = EveAllianceInfo.objects.update_alliance(3456)
|
||||||
|
|
||||||
# This is the only thing ever updated in code
|
# This is the only thing ever updated in code
|
||||||
self.assertEqual(result.executor_corp_id, expected.executor_corp_id)
|
self.assertEqual(result.executor_corp_id, expected.executor_corp_id)
|
||||||
@@ -179,7 +178,7 @@ class EveCorporationProviderManagerTestCase(TestCase):
|
|||||||
expected = Corporation()
|
expected = Corporation()
|
||||||
provider.get_corp.return_value = expected
|
provider.get_corp.return_value = expected
|
||||||
|
|
||||||
result = EveCorporationInfo.provider.get_corporation('2345')
|
result = EveCorporationInfo.provider.get_corporation(2345)
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
@@ -190,39 +189,39 @@ class EveCorporationManagerTestCase(TestCase):
|
|||||||
@property
|
@property
|
||||||
def alliance(self):
|
def alliance(self):
|
||||||
return EveAllianceManagerTestCase.TestAlliance(
|
return EveAllianceManagerTestCase.TestAlliance(
|
||||||
id='3456',
|
id=3456,
|
||||||
name='Test Alliance',
|
name='Test Alliance',
|
||||||
ticker='TEST',
|
ticker='TEST',
|
||||||
corp_ids=['2345'],
|
corp_ids=[2345],
|
||||||
executor_corp_id='2345'
|
executor_corp_id=2345
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ceo(self):
|
def ceo(self):
|
||||||
return EveCharacterManagerTestCase.TestCharacter(
|
return EveCharacterManagerTestCase.TestCharacter(
|
||||||
id='1234',
|
id=1234,
|
||||||
name='Test Character',
|
name='Test Character',
|
||||||
corp_id='2345',
|
corp_id=2345,
|
||||||
alliance_id='3456'
|
alliance_id=3456
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||||
def test_create_corporation(self, provider):
|
def test_create_corporation(self, provider):
|
||||||
# Also covers create_corp_obj
|
# Also covers create_corp_obj
|
||||||
exp_alliance = EveAllianceInfo.objects.create(
|
exp_alliance = EveAllianceInfo.objects.create(
|
||||||
alliance_id='3456',
|
alliance_id=3456,
|
||||||
alliance_name='alliance.name',
|
alliance_name='alliance.name',
|
||||||
alliance_ticker='alliance.ticker',
|
alliance_ticker='99bug',
|
||||||
executor_corp_id='alliance.executor_corp_id',
|
executor_corp_id=2345,
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = self.TestCorporation(
|
expected = self.TestCorporation(
|
||||||
id='2345',
|
id=2345,
|
||||||
name='Test Corp',
|
name='Test Corp',
|
||||||
ticker='0BUGS',
|
ticker='0BUGS',
|
||||||
ceo_id='1234',
|
ceo_id=1234,
|
||||||
members=1,
|
members=1,
|
||||||
alliance_id='3456'
|
alliance_id=3456
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_corp.return_value = expected
|
provider.get_corp.return_value = expected
|
||||||
@@ -240,17 +239,17 @@ class EveCorporationManagerTestCase(TestCase):
|
|||||||
# variant to test no alliance case
|
# variant to test no alliance case
|
||||||
# Also covers create_corp_obj
|
# Also covers create_corp_obj
|
||||||
expected = self.TestCorporation(
|
expected = self.TestCorporation(
|
||||||
id='2345',
|
id=2345,
|
||||||
name='Test Corp',
|
name='Test Corp',
|
||||||
ticker='0BUGS',
|
ticker='0BUGS',
|
||||||
ceo_id='1234',
|
ceo_id=1234,
|
||||||
members=1,
|
members=1,
|
||||||
alliance_id='3456'
|
alliance_id=3456
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_corp.return_value = expected
|
provider.get_corp.return_value = expected
|
||||||
|
|
||||||
result = EveCorporationInfo.objects.create_corporation('2345')
|
result = EveCorporationInfo.objects.create_corporation(2345)
|
||||||
|
|
||||||
self.assertEqual(result.corporation_id, expected.id)
|
self.assertEqual(result.corporation_id, expected.id)
|
||||||
self.assertEqual(result.corporation_name, expected.name)
|
self.assertEqual(result.corporation_name, expected.name)
|
||||||
@@ -262,27 +261,27 @@ class EveCorporationManagerTestCase(TestCase):
|
|||||||
def test_update_corporation(self, provider):
|
def test_update_corporation(self, provider):
|
||||||
# Also covers Model.update_corporation
|
# Also covers Model.update_corporation
|
||||||
exp_alliance = EveAllianceInfo.objects.create(
|
exp_alliance = EveAllianceInfo.objects.create(
|
||||||
alliance_id='3456',
|
alliance_id=3456,
|
||||||
alliance_name='alliance.name',
|
alliance_name='alliance.name',
|
||||||
alliance_ticker='alliance.ticker',
|
alliance_ticker='at1',
|
||||||
executor_corp_id='alliance.executor_corp_id',
|
executor_corp_id=2345,
|
||||||
)
|
)
|
||||||
|
|
||||||
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='cc1',
|
||||||
member_count=10,
|
member_count=10,
|
||||||
alliance=None,
|
alliance=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = self.TestCorporation(
|
expected = self.TestCorporation(
|
||||||
id='2345',
|
id=2345,
|
||||||
name='Test Corp',
|
name='Test Corp',
|
||||||
ticker='0BUGS',
|
ticker='0BUGS',
|
||||||
ceo_id='1234',
|
ceo_id=1234,
|
||||||
members=1,
|
members=1,
|
||||||
alliance_id='3456'
|
alliance_id=3456
|
||||||
)
|
)
|
||||||
|
|
||||||
provider.get_corp.return_value = expected
|
provider.get_corp.return_value = expected
|
||||||
|
|||||||
@@ -2,130 +2,40 @@ from unittest.mock import Mock, patch
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ..models import EveCharacter, EveCorporationInfo, \
|
from ..models import (
|
||||||
EveAllianceInfo, _eve_entity_image_url
|
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
|
)
|
||||||
from ..providers import Alliance, Corporation, Character
|
from ..providers import Alliance, Corporation, Character
|
||||||
|
from ..evelinks import eveimageserver
|
||||||
|
|
||||||
|
|
||||||
class EveUniverseImageUrlTestCase(TestCase):
|
|
||||||
"""unit test for _eve_entity_image_url()"""
|
|
||||||
|
|
||||||
def test_sizes(self):
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, size=32),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, size=64),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=64'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, size=128),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=128'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, size=256),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=256'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, size=512),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=512'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, size=1024),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=1024'
|
|
||||||
)
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('corporation', 42, size=-5)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('corporation', 42, size=0)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('corporation', 42, size=31)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('corporation', 42, size=1025)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('corporation', 42, size=2048)
|
|
||||||
|
|
||||||
|
|
||||||
def test_variant(self):
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, variant='portrait'),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('alliance', 42, variant='logo'),
|
|
||||||
'https://images.evetech.net/alliances/42/logo?size=32'
|
|
||||||
)
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('character', 42, variant='logo')
|
|
||||||
|
|
||||||
|
|
||||||
def test_alliance(self):
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('alliance', 42),
|
|
||||||
'https://images.evetech.net/alliances/42/logo?size=32'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('corporation', 42),
|
|
||||||
'https://images.evetech.net/corporations/42/logo?size=32'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
|
||||||
)
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('station', 42)
|
|
||||||
|
|
||||||
|
|
||||||
def test_tenants(self):
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, tenant='tranquility'),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
_eve_entity_image_url('character', 42, tenant='singularity'),
|
|
||||||
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
|
|
||||||
)
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
_eve_entity_image_url('character', 42, tenant='xxx')
|
|
||||||
|
|
||||||
|
|
||||||
class EveCharacterTestCase(TestCase):
|
class EveCharacterTestCase(TestCase):
|
||||||
def test_corporation_prop(self):
|
def test_corporation_prop(self):
|
||||||
"""
|
"""
|
||||||
Test that the correct corporation is returned by the corporation property
|
Test that the correct corporation is returned by the corporation property
|
||||||
"""
|
"""
|
||||||
character = EveCharacter.objects.create(
|
character = EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='2345',
|
corporation_id=2345,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id='character.alliance.id',
|
alliance_id=12345,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = EveCorporationInfo.objects.create(
|
expected = EveCorporationInfo.objects.create(
|
||||||
corporation_id='2345',
|
corporation_id=2345,
|
||||||
corporation_name='corp.name',
|
corporation_name='corp.name',
|
||||||
corporation_ticker='corp.ticker',
|
corporation_ticker='cc1',
|
||||||
member_count=10,
|
member_count=10,
|
||||||
alliance=None,
|
alliance=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
incorrect = EveCorporationInfo.objects.create(
|
incorrect = EveCorporationInfo.objects.create(
|
||||||
corporation_id='9999',
|
corporation_id=9999,
|
||||||
corporation_name='corp.name1',
|
corporation_name='corp.name1',
|
||||||
corporation_ticker='corp.ticker1',
|
corporation_ticker='cc11',
|
||||||
member_count=10,
|
member_count=10,
|
||||||
alliance=None,
|
alliance=None,
|
||||||
)
|
)
|
||||||
@@ -139,44 +49,44 @@ class EveCharacterTestCase(TestCase):
|
|||||||
object is not in the database
|
object is not in the database
|
||||||
"""
|
"""
|
||||||
character = EveCharacter.objects.create(
|
character = EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='2345',
|
corporation_id=2345,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id='character.alliance.id',
|
alliance_id=123456,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(EveCorporationInfo.DoesNotExist):
|
with self.assertRaises(EveCorporationInfo.DoesNotExist):
|
||||||
result = character.corporation
|
character.corporation
|
||||||
|
|
||||||
def test_alliance_prop(self):
|
def test_alliance_prop(self):
|
||||||
"""
|
"""
|
||||||
Test that the correct alliance is returned by the alliance property
|
Test that the correct alliance is returned by the alliance property
|
||||||
"""
|
"""
|
||||||
character = EveCharacter.objects.create(
|
character = EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='2345',
|
corporation_id=2345,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id='3456',
|
alliance_id=3456,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
expected = EveAllianceInfo.objects.create(
|
expected = EveAllianceInfo.objects.create(
|
||||||
alliance_id='3456',
|
alliance_id=3456,
|
||||||
alliance_name='alliance.name',
|
alliance_name='alliance.name',
|
||||||
alliance_ticker='alliance.ticker',
|
alliance_ticker='ac2',
|
||||||
executor_corp_id='alliance.executor_corp_id',
|
executor_corp_id=2345,
|
||||||
)
|
)
|
||||||
|
|
||||||
incorrect = EveAllianceInfo.objects.create(
|
incorrect = EveAllianceInfo.objects.create(
|
||||||
alliance_id='9001',
|
alliance_id=9001,
|
||||||
alliance_name='alliance.name1',
|
alliance_name='alliance.name1',
|
||||||
alliance_ticker='alliance.ticker1',
|
alliance_ticker='ac1',
|
||||||
executor_corp_id='alliance.executor_corp_id1',
|
executor_corp_id=2654,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(character.alliance, expected)
|
self.assertEqual(character.alliance, expected)
|
||||||
@@ -188,28 +98,28 @@ class EveCharacterTestCase(TestCase):
|
|||||||
object is not in the database
|
object is not in the database
|
||||||
"""
|
"""
|
||||||
character = EveCharacter.objects.create(
|
character = EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='2345',
|
corporation_id=2345,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id='3456',
|
alliance_id=3456,
|
||||||
alliance_name='character.alliance.name',
|
alliance_name='character.alliance.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(EveAllianceInfo.DoesNotExist):
|
with self.assertRaises(EveAllianceInfo.DoesNotExist):
|
||||||
result = character.alliance
|
character.alliance
|
||||||
|
|
||||||
def test_alliance_prop_none(self):
|
def test_alliance_prop_none(self):
|
||||||
"""
|
"""
|
||||||
Check that None is returned when the character has no alliance
|
Check that None is returned when the character has no alliance
|
||||||
"""
|
"""
|
||||||
character = EveCharacter.objects.create(
|
character = EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1234,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='2345',
|
corporation_id=2345,
|
||||||
corporation_name='character.corp.name',
|
corporation_name='character.corp.name',
|
||||||
corporation_ticker='character.corp.ticker',
|
corporation_ticker='cc1',
|
||||||
alliance_id=None,
|
alliance_id=None,
|
||||||
alliance_name=None,
|
alliance_name=None,
|
||||||
)
|
)
|
||||||
@@ -227,12 +137,12 @@ class EveCharacterTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
my_character = EveCharacter.objects.create(
|
my_character = EveCharacter.objects.create(
|
||||||
character_id='1001',
|
character_id=1001,
|
||||||
character_name='Bruce Wayne',
|
character_name='Bruce Wayne',
|
||||||
corporation_id='2001',
|
corporation_id=2001,
|
||||||
corporation_name='Dummy Corp 1',
|
corporation_name='Dummy Corp 1',
|
||||||
corporation_ticker='DC1',
|
corporation_ticker='DC1',
|
||||||
alliance_id='3001',
|
alliance_id=3001,
|
||||||
alliance_name='Dummy Alliance 1',
|
alliance_name='Dummy Alliance 1',
|
||||||
)
|
)
|
||||||
my_updated_character = Character(
|
my_updated_character = Character(
|
||||||
@@ -244,90 +154,87 @@ class EveCharacterTestCase(TestCase):
|
|||||||
|
|
||||||
# todo: add test cases not yet covered, e.g. with alliance
|
# todo: add test cases not yet covered, e.g. with alliance
|
||||||
|
|
||||||
|
|
||||||
def test_image_url(self):
|
def test_image_url(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
EveCharacter.generic_portrait_url(42),
|
EveCharacter.generic_portrait_url(42),
|
||||||
_eve_entity_image_url('character', 42)
|
eveimageserver._eve_entity_image_url('character', 42)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
EveCharacter.generic_portrait_url(42, 256),
|
EveCharacter.generic_portrait_url(42, 256),
|
||||||
_eve_entity_image_url('character', 42, 256)
|
eveimageserver._eve_entity_image_url('character', 42, 256)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_portrait_urls(self):
|
def test_portrait_urls(self):
|
||||||
x = EveCharacter(
|
x = EveCharacter(
|
||||||
character_id='42',
|
character_id=42,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='123',
|
corporation_id=123,
|
||||||
corporation_name='corporation.name',
|
corporation_name='corporation.name',
|
||||||
corporation_ticker='ABC',
|
corporation_ticker='ABC',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.portrait_url(),
|
x.portrait_url(),
|
||||||
_eve_entity_image_url('character', 42)
|
eveimageserver._eve_entity_image_url('character', 42)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.portrait_url(64),
|
x.portrait_url(64),
|
||||||
_eve_entity_image_url('character', 42, size=64)
|
eveimageserver._eve_entity_image_url('character', 42, size=64)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.portrait_url_32,
|
x.portrait_url_32,
|
||||||
_eve_entity_image_url('character', 42, size=32)
|
eveimageserver._eve_entity_image_url('character', 42, size=32)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.portrait_url_64,
|
x.portrait_url_64,
|
||||||
_eve_entity_image_url('character', 42, size=64)
|
eveimageserver._eve_entity_image_url('character', 42, size=64)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.portrait_url_128,
|
x.portrait_url_128,
|
||||||
_eve_entity_image_url('character', 42, size=128)
|
eveimageserver._eve_entity_image_url('character', 42, size=128)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.portrait_url_256,
|
x.portrait_url_256,
|
||||||
_eve_entity_image_url('character', 42, size=256)
|
eveimageserver._eve_entity_image_url('character', 42, size=256)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_corporation_logo_urls(self):
|
def test_corporation_logo_urls(self):
|
||||||
x = EveCharacter(
|
x = EveCharacter(
|
||||||
character_id='42',
|
character_id=42,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='123',
|
corporation_id=123,
|
||||||
corporation_name='corporation.name',
|
corporation_name='corporation.name',
|
||||||
corporation_ticker='ABC',
|
corporation_ticker='ABC',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.corporation_logo_url(),
|
x.corporation_logo_url(),
|
||||||
_eve_entity_image_url('corporation', 123)
|
eveimageserver._eve_entity_image_url('corporation', 123)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.corporation_logo_url(256),
|
x.corporation_logo_url(256),
|
||||||
_eve_entity_image_url('corporation', 123, size=256)
|
eveimageserver._eve_entity_image_url('corporation', 123, size=256)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.corporation_logo_url_32,
|
x.corporation_logo_url_32,
|
||||||
_eve_entity_image_url('corporation', 123, size=32)
|
eveimageserver._eve_entity_image_url('corporation', 123, size=32)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.corporation_logo_url_64,
|
x.corporation_logo_url_64,
|
||||||
_eve_entity_image_url('corporation', 123, size=64)
|
eveimageserver._eve_entity_image_url('corporation', 123, size=64)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.corporation_logo_url_128,
|
x.corporation_logo_url_128,
|
||||||
_eve_entity_image_url('corporation', 123, size=128)
|
eveimageserver._eve_entity_image_url('corporation', 123, size=128)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.corporation_logo_url_256,
|
x.corporation_logo_url_256,
|
||||||
_eve_entity_image_url('corporation', 123, size=256)
|
eveimageserver._eve_entity_image_url('corporation', 123, size=256)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_alliance_logo_urls(self):
|
def test_alliance_logo_urls(self):
|
||||||
x = EveCharacter(
|
x = EveCharacter(
|
||||||
character_id='42',
|
character_id=42,
|
||||||
character_name='character.name',
|
character_name='character.name',
|
||||||
corporation_id='123',
|
corporation_id=123,
|
||||||
corporation_name='corporation.name',
|
corporation_name='corporation.name',
|
||||||
corporation_ticker='ABC',
|
corporation_ticker='ABC',
|
||||||
)
|
)
|
||||||
@@ -354,27 +261,27 @@ class EveCharacterTestCase(TestCase):
|
|||||||
x.alliance_id = 987
|
x.alliance_id = 987
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.alliance_logo_url(),
|
x.alliance_logo_url(),
|
||||||
_eve_entity_image_url('alliance', 987)
|
eveimageserver._eve_entity_image_url('alliance', 987)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.alliance_logo_url(128),
|
x.alliance_logo_url(128),
|
||||||
_eve_entity_image_url('alliance', 987, size=128)
|
eveimageserver._eve_entity_image_url('alliance', 987, size=128)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.alliance_logo_url_32,
|
x.alliance_logo_url_32,
|
||||||
_eve_entity_image_url('alliance', 987, size=32)
|
eveimageserver._eve_entity_image_url('alliance', 987, size=32)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.alliance_logo_url_64,
|
x.alliance_logo_url_64,
|
||||||
_eve_entity_image_url('alliance', 987, size=64)
|
eveimageserver._eve_entity_image_url('alliance', 987, size=64)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.alliance_logo_url_128,
|
x.alliance_logo_url_128,
|
||||||
_eve_entity_image_url('alliance', 987, size=128)
|
eveimageserver._eve_entity_image_url('alliance', 987, size=128)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.alliance_logo_url_256,
|
x.alliance_logo_url_256,
|
||||||
_eve_entity_image_url('alliance', 987, size=256)
|
eveimageserver._eve_entity_image_url('alliance', 987, size=256)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -456,7 +363,6 @@ class EveAllianceTestCase(TestCase):
|
|||||||
# potential bug
|
# potential bug
|
||||||
# update_alliance() is only updateting executor_corp_id when object is given
|
# update_alliance() is only updateting executor_corp_id when object is given
|
||||||
|
|
||||||
|
|
||||||
def test_update_alliance_wo_object(self):
|
def test_update_alliance_wo_object(self):
|
||||||
mock_EveAllianceProviderManager = Mock()
|
mock_EveAllianceProviderManager = Mock()
|
||||||
mock_EveAllianceProviderManager.get_alliance.return_value = \
|
mock_EveAllianceProviderManager.get_alliance.return_value = \
|
||||||
@@ -475,11 +381,11 @@ class EveAllianceTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
my_alliance.provider = mock_EveAllianceProviderManager
|
my_alliance.provider = mock_EveAllianceProviderManager
|
||||||
my_alliance.save()
|
my_alliance.save()
|
||||||
updated_alliance = Alliance(
|
Alliance(
|
||||||
name='Dummy Alliance 2',
|
name='Dummy Alliance 2',
|
||||||
corp_ids=[2004],
|
corp_ids=[2004],
|
||||||
executor_corp_id=2004
|
executor_corp_id=2004
|
||||||
)
|
)
|
||||||
my_alliance.update_alliance()
|
my_alliance.update_alliance()
|
||||||
my_alliance.refresh_from_db()
|
my_alliance.refresh_from_db()
|
||||||
self.assertEqual(int(my_alliance.executor_corp_id), 2004)
|
self.assertEqual(int(my_alliance.executor_corp_id), 2004)
|
||||||
@@ -487,23 +393,22 @@ class EveAllianceTestCase(TestCase):
|
|||||||
# potential bug
|
# potential bug
|
||||||
# update_alliance() is only updateting executor_corp_id nothing else ???
|
# update_alliance() is only updateting executor_corp_id nothing else ???
|
||||||
|
|
||||||
|
|
||||||
def test_image_url(self):
|
def test_image_url(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
EveAllianceInfo.generic_logo_url(42),
|
EveAllianceInfo.generic_logo_url(42),
|
||||||
_eve_entity_image_url('alliance', 42)
|
eveimageserver._eve_entity_image_url('alliance', 42)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
EveAllianceInfo.generic_logo_url(42, 256),
|
EveAllianceInfo.generic_logo_url(42, 256),
|
||||||
_eve_entity_image_url('alliance', 42, 256)
|
eveimageserver._eve_entity_image_url('alliance', 42, 256)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_logo_url(self):
|
def test_logo_url(self):
|
||||||
x = EveAllianceInfo(
|
x = EveAllianceInfo(
|
||||||
alliance_id='42',
|
alliance_id=42,
|
||||||
alliance_name='alliance.name',
|
alliance_name='alliance.name',
|
||||||
alliance_ticker='ABC',
|
alliance_ticker='ABC',
|
||||||
executor_corp_id='123'
|
executor_corp_id=123
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
x.logo_url(),
|
x.logo_url(),
|
||||||
@@ -563,9 +468,7 @@ class EveCorporationTestCase(TestCase):
|
|||||||
|
|
||||||
def test_update_corporation_no_object_w_alliance(self):
|
def test_update_corporation_no_object_w_alliance(self):
|
||||||
mock_provider = Mock()
|
mock_provider = Mock()
|
||||||
mock_provider.get_corporation.return_value = Corporation(
|
mock_provider.get_corporation.return_value = Corporation(members=87)
|
||||||
members=87
|
|
||||||
)
|
|
||||||
self.my_corp.provider = mock_provider
|
self.my_corp.provider = mock_provider
|
||||||
|
|
||||||
self.my_corp.update_corporation()
|
self.my_corp.update_corporation()
|
||||||
@@ -585,15 +488,14 @@ class EveCorporationTestCase(TestCase):
|
|||||||
self.assertEqual(my_corp2.member_count, 8)
|
self.assertEqual(my_corp2.member_count, 8)
|
||||||
self.assertIsNone(my_corp2.alliance)
|
self.assertIsNone(my_corp2.alliance)
|
||||||
|
|
||||||
|
|
||||||
def test_image_url(self):
|
def test_image_url(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
EveCorporationInfo.generic_logo_url(42),
|
EveCorporationInfo.generic_logo_url(42),
|
||||||
_eve_entity_image_url('corporation', 42)
|
eveimageserver._eve_entity_image_url('corporation', 42)
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
EveCorporationInfo.generic_logo_url(42, 256),
|
EveCorporationInfo.generic_logo_url(42, 256),
|
||||||
_eve_entity_image_url('corporation', 42, 256)
|
eveimageserver._eve_entity_image_url('corporation', 42, 256)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_logo_url(self):
|
def test_logo_url(self):
|
||||||
@@ -621,4 +523,3 @@ class EveCorporationTestCase(TestCase):
|
|||||||
self.my_corp.logo_url_256,
|
self.my_corp.logo_url_256,
|
||||||
'https://images.evetech.net/corporations/2001/logo?size=256'
|
'https://images.evetech.net/corporations/2001/logo?size=256'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
import os
|
import os
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
|
from bravado.exception import HTTPNotFound
|
||||||
from jsonschema.exceptions import RefResolutionError
|
from jsonschema.exceptions import RefResolutionError
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from . import set_logger
|
from . import set_logger
|
||||||
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from ..providers import (
|
||||||
from ..providers import ObjectNotFound, Entity, Character, Corporation, \
|
ObjectNotFound,
|
||||||
Alliance, ItemType, EveProvider, EveSwaggerProvider
|
Entity,
|
||||||
|
Character,
|
||||||
|
Corporation,
|
||||||
|
Alliance,
|
||||||
|
ItemType,
|
||||||
|
EveProvider,
|
||||||
|
EveSwaggerProvider
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
MODULE_PATH = 'allianceauth.eveonline.providers'
|
MODULE_PATH = 'allianceauth.eveonline.providers'
|
||||||
SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname(
|
SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname(
|
||||||
os.path.abspath(__file__)), 'swagger_old.json'
|
os.path.abspath(__file__)), 'swagger_old.json'
|
||||||
)
|
)
|
||||||
set_logger(MODULE_PATH, __file__)
|
set_logger(MODULE_PATH, __file__)
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +58,6 @@ class TestEntity(TestCase):
|
|||||||
x = Entity()
|
x = Entity()
|
||||||
self.assertEqual(repr(x), '<Entity (None): None>')
|
self.assertEqual(repr(x), '<Entity (None): None>')
|
||||||
|
|
||||||
|
|
||||||
def test_bool(self):
|
def test_bool(self):
|
||||||
x = Entity(1001)
|
x = Entity(1001)
|
||||||
self.assertTrue(bool(x))
|
self.assertTrue(bool(x))
|
||||||
@@ -99,7 +105,6 @@ class TestCorporation(TestCase):
|
|||||||
# should fetch alliance once only
|
# should fetch alliance once only
|
||||||
self.assertEqual(mock_provider_get_alliance.call_count, 1)
|
self.assertEqual(mock_provider_get_alliance.call_count, 1)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
||||||
def test_alliance_not_defined(self, mock_provider_get_alliance):
|
def test_alliance_not_defined(self, mock_provider_get_alliance):
|
||||||
mock_provider_get_alliance.return_value = None
|
mock_provider_get_alliance.return_value = None
|
||||||
@@ -110,7 +115,6 @@ class TestCorporation(TestCase):
|
|||||||
Entity(None, None)
|
Entity(None, None)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_character')
|
@patch(MODULE_PATH + '.EveSwaggerProvider.get_character')
|
||||||
def test_ceo(self, mock_provider_get_character):
|
def test_ceo(self, mock_provider_get_character):
|
||||||
my_ceo = Character(
|
my_ceo = Character(
|
||||||
@@ -200,7 +204,6 @@ class TestAlliance(TestCase):
|
|||||||
# should be called once by used corp only
|
# should be called once by used corp only
|
||||||
self.assertEqual(mock_provider_get_corp.call_count, 2)
|
self.assertEqual(mock_provider_get_corp.call_count, 2)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||||
def test_corps(self, mock_provider_get_corp):
|
def test_corps(self, mock_provider_get_corp):
|
||||||
mock_provider_get_corp.side_effect = TestAlliance._get_corp
|
mock_provider_get_corp.side_effect = TestAlliance._get_corp
|
||||||
@@ -253,7 +256,6 @@ class TestCharacter(TestCase):
|
|||||||
|
|
||||||
# should call the provider one time only
|
# should call the provider one time only
|
||||||
self.assertEqual(mock_provider_get_corp.call_count, 1)
|
self.assertEqual(mock_provider_get_corp.call_count, 1)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
||||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||||
@@ -283,7 +285,6 @@ class TestCharacter(TestCase):
|
|||||||
self.assertEqual(mock_provider_get_corp.call_count, 1)
|
self.assertEqual(mock_provider_get_corp.call_count, 1)
|
||||||
self.assertEqual(mock_provider_get_alliance.call_count, 1)
|
self.assertEqual(mock_provider_get_alliance.call_count, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_alliance_has_none(self):
|
def test_alliance_has_none(self):
|
||||||
self.my_character.alliance_id = None
|
self.my_character.alliance_id = None
|
||||||
self.assertEqual(self.my_character.alliance, Entity(None, None))
|
self.assertEqual(self.my_character.alliance, Entity(None, None))
|
||||||
@@ -343,7 +344,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
else:
|
else:
|
||||||
raise HTTPNotFound(Mock())
|
raise HTTPNotFound(Mock())
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def esi_get_alliances_alliance_id_corporations(alliance_id):
|
def esi_get_alliances_alliance_id_corporations(alliance_id):
|
||||||
alliances = {
|
alliances = {
|
||||||
@@ -357,7 +357,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
else:
|
else:
|
||||||
raise HTTPNotFound(Mock())
|
raise HTTPNotFound(Mock())
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def esi_get_corporations_corporation_id(corporation_id):
|
def esi_get_corporations_corporation_id(corporation_id):
|
||||||
corporations = {
|
corporations = {
|
||||||
@@ -382,7 +381,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
else:
|
else:
|
||||||
raise HTTPNotFound(Mock())
|
raise HTTPNotFound(Mock())
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def esi_get_characters_character_id(character_id):
|
def esi_get_characters_character_id(character_id):
|
||||||
characters = {
|
characters = {
|
||||||
@@ -403,7 +401,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
else:
|
else:
|
||||||
raise HTTPNotFound(Mock())
|
raise HTTPNotFound(Mock())
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def esi_post_characters_affiliation(characters):
|
def esi_post_characters_affiliation(characters):
|
||||||
character_data = {
|
character_data = {
|
||||||
@@ -428,7 +425,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
else:
|
else:
|
||||||
raise TypeError()
|
raise TypeError()
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def esi_get_universe_types_type_id(type_id):
|
def esi_get_universe_types_type_id(type_id):
|
||||||
types = {
|
types = {
|
||||||
@@ -446,13 +442,11 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
else:
|
else:
|
||||||
raise HTTPNotFound(Mock())
|
raise HTTPNotFound(Mock())
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.esi_client_factory')
|
@patch(MODULE_PATH + '.esi_client_factory')
|
||||||
def test_str(self, mock_esi_client_factory):
|
def test_str(self, mock_esi_client_factory):
|
||||||
my_provider = EveSwaggerProvider()
|
my_provider = EveSwaggerProvider()
|
||||||
self.assertEqual(str(my_provider), 'esi')
|
self.assertEqual(str(my_provider), 'esi')
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.esi_client_factory')
|
@patch(MODULE_PATH + '.esi_client_factory')
|
||||||
def test_get_alliance(self, mock_esi_client_factory):
|
def test_get_alliance(self, mock_esi_client_factory):
|
||||||
mock_esi_client_factory.return_value\
|
mock_esi_client_factory.return_value\
|
||||||
@@ -481,7 +475,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
with self.assertRaises(ObjectNotFound):
|
with self.assertRaises(ObjectNotFound):
|
||||||
my_provider.get_alliance(3999)
|
my_provider.get_alliance(3999)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.esi_client_factory')
|
@patch(MODULE_PATH + '.esi_client_factory')
|
||||||
def test_get_corp(self, mock_esi_client_factory):
|
def test_get_corp(self, mock_esi_client_factory):
|
||||||
mock_esi_client_factory.return_value\
|
mock_esi_client_factory.return_value\
|
||||||
@@ -508,7 +501,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
with self.assertRaises(ObjectNotFound):
|
with self.assertRaises(ObjectNotFound):
|
||||||
my_provider.get_corp(2999)
|
my_provider.get_corp(2999)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.esi_client_factory')
|
@patch(MODULE_PATH + '.esi_client_factory')
|
||||||
def test_get_character(self, mock_esi_client_factory):
|
def test_get_character(self, mock_esi_client_factory):
|
||||||
mock_esi_client_factory.return_value\
|
mock_esi_client_factory.return_value\
|
||||||
@@ -536,7 +528,6 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
with self.assertRaises(ObjectNotFound):
|
with self.assertRaises(ObjectNotFound):
|
||||||
my_provider.get_character(1999)
|
my_provider.get_character(1999)
|
||||||
|
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.esi_client_factory')
|
@patch(MODULE_PATH + '.esi_client_factory')
|
||||||
def test_get_itemtype(self, mock_esi_client_factory):
|
def test_get_itemtype(self, mock_esi_client_factory):
|
||||||
mock_esi_client_factory.return_value\
|
mock_esi_client_factory.return_value\
|
||||||
@@ -601,5 +592,3 @@ class TestEveSwaggerProvider(TestCase):
|
|||||||
self.assertTrue(mock_esi_client_factory.called)
|
self.assertTrue(mock_esi_client_factory.called)
|
||||||
self.assertIsNotNone(my_provider._client)
|
self.assertIsNotNone(my_provider._client)
|
||||||
self.assertEqual(my_client, 'my_client')
|
self.assertEqual(my_client, 'my_client')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ from unittest.mock import patch, Mock
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
from ..tasks import update_alliance, update_corp, update_character, \
|
from ..tasks import (
|
||||||
|
update_alliance,
|
||||||
|
update_corp,
|
||||||
|
update_character,
|
||||||
run_model_update
|
run_model_update
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestTasks(TestCase):
|
class TestTasks(TestCase):
|
||||||
@@ -13,98 +17,229 @@ class TestTasks(TestCase):
|
|||||||
def test_update_corp(self, mock_EveCorporationInfo):
|
def test_update_corp(self, mock_EveCorporationInfo):
|
||||||
update_corp(42)
|
update_corp(42)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_EveCorporationInfo.objects.update_corporation.call_count,
|
mock_EveCorporationInfo.objects.update_corporation.call_count, 1
|
||||||
1
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0],
|
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0], 42
|
||||||
42
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@patch('allianceauth.eveonline.tasks.EveAllianceInfo')
|
@patch('allianceauth.eveonline.tasks.EveAllianceInfo')
|
||||||
def test_update_alliance(self, mock_EveAllianceInfo):
|
def test_update_alliance(self, mock_EveAllianceInfo):
|
||||||
update_alliance(42)
|
update_alliance(42)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0],
|
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0], 42
|
||||||
42
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_EveAllianceInfo.objects\
|
mock_EveAllianceInfo.objects
|
||||||
.update_alliance.return_value.populate_alliance.call_count,
|
.update_alliance.return_value.populate_alliance.call_count, 1
|
||||||
1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@patch('allianceauth.eveonline.tasks.EveCharacter')
|
@patch('allianceauth.eveonline.tasks.EveCharacter')
|
||||||
def test_update_character(self, mock_EveCharacter):
|
def test_update_character(self, mock_EveCharacter):
|
||||||
update_character(42)
|
update_character(42)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_EveCharacter.objects.update_character.call_count,
|
mock_EveCharacter.objects.update_character.call_count, 1
|
||||||
1
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_EveCharacter.objects.update_character.call_args[0][0],
|
mock_EveCharacter.objects.update_character.call_args[0][0], 42
|
||||||
42
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@patch('allianceauth.eveonline.tasks.update_character')
|
@patch('allianceauth.eveonline.tasks.update_character')
|
||||||
@patch('allianceauth.eveonline.tasks.update_alliance')
|
@patch('allianceauth.eveonline.tasks.update_alliance')
|
||||||
@patch('allianceauth.eveonline.tasks.update_corp')
|
@patch('allianceauth.eveonline.tasks.update_corp')
|
||||||
def test_run_model_update(
|
@patch('allianceauth.eveonline.providers.provider')
|
||||||
self,
|
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
|
||||||
mock_update_corp,
|
class TestRunModelUpdate(TestCase):
|
||||||
mock_update_alliance,
|
|
||||||
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='alliance.executor_corp_id',
|
executor_corp_id=5,
|
||||||
)
|
)
|
||||||
EveCharacter.objects.create(
|
EveCharacter.objects.create(
|
||||||
character_id='1234',
|
character_id=1,
|
||||||
character_name='character.name',
|
character_name='character.name1',
|
||||||
corporation_id='character.corp.id',
|
corporation_id=2345,
|
||||||
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='character.alliance.id',
|
alliance_id=None
|
||||||
|
)
|
||||||
|
EveCharacter.objects.create(
|
||||||
|
character_id=2,
|
||||||
|
character_name='character.name2',
|
||||||
|
corporation_id=9876,
|
||||||
|
corporation_name='character.corp.name',
|
||||||
|
corporation_ticker='c.c.t', # max 5 chars
|
||||||
|
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]),
|
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
|
||||||
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]),
|
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456
|
||||||
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]),
|
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
|
||||||
1234
|
}
|
||||||
)
|
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)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from allianceauth.services.hooks import MenuItemHook, UrlHook
|
|||||||
|
|
||||||
@hooks.register('menu_item_hook')
|
@hooks.register('menu_item_hook')
|
||||||
def register_menu():
|
def register_menu():
|
||||||
return MenuItemHook(_('Fleet Activity Tracking'), 'fa fa-users fa-lightbulb-o fa-fw', 'fatlink:view',
|
return MenuItemHook(_('Fleet Activity Tracking'), 'fas fa-users fa-fw', 'fatlink:view',
|
||||||
navactive=['fatlink:'])
|
navactive=['fatlink:'])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
37
allianceauth/groupmanagement/auth_hooks.py
Normal file
37
allianceauth/groupmanagement/auth_hooks.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||||
|
from allianceauth import hooks
|
||||||
|
|
||||||
|
from . import urls
|
||||||
|
from .managers import GroupManager
|
||||||
|
|
||||||
|
|
||||||
|
class GroupManagementMenuItem(MenuItemHook):
|
||||||
|
""" This class ensures only authorized users will see the menu entry """
|
||||||
|
def __init__(self):
|
||||||
|
# setup menu entry for sidebar
|
||||||
|
MenuItemHook.__init__(
|
||||||
|
self,
|
||||||
|
text=_('Group Management'),
|
||||||
|
classes='fas fa-users-cog fa-fw',
|
||||||
|
url_name='groupmanagement:management',
|
||||||
|
order=50,
|
||||||
|
navactive=['groupmanagement:management']
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, request):
|
||||||
|
if GroupManager.can_manage_groups(request.user):
|
||||||
|
self.count = GroupManager.pending_requests_count_for_user(request.user)
|
||||||
|
return MenuItemHook.render(self, request)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('menu_item_hook')
|
||||||
|
def register_menu():
|
||||||
|
return GroupManagementMenuItem()
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('url_hook')
|
||||||
|
def register_urls():
|
||||||
|
return UrlHook(urls, 'group', r'^group/')
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from allianceauth.groupmanagement.managers import GroupManager
|
|
||||||
|
|
||||||
|
|
||||||
def can_manage_groups(request):
|
|
||||||
return {'can_manage_groups': GroupManager.can_manage_groups(request.user)}
|
|
||||||
@@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, User
|
|||||||
from django.db.models import Q, QuerySet
|
from django.db.models import Q, QuerySet
|
||||||
|
|
||||||
from allianceauth.authentication.models import State
|
from allianceauth.authentication.models import State
|
||||||
|
from .models import GroupRequest
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -101,3 +102,18 @@ class GroupManager:
|
|||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
|
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pending_requests_count_for_user(cls, user: User) -> int:
|
||||||
|
"""Returns the number of pending group requests for the given user"""
|
||||||
|
|
||||||
|
if cls.has_management_permission(user):
|
||||||
|
return GroupRequest.objects.filter(status="pending").count()
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
GroupRequest.objects
|
||||||
|
.filter(status="pending")
|
||||||
|
.filter(group__authgroup__group_leaders__exact=user)
|
||||||
|
.select_related("group__authgroup__group_leaders")
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import models
|
|||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from allianceauth.authentication.models import State
|
from allianceauth.authentication.models import State
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
class GroupRequest(models.Model):
|
class GroupRequest(models.Model):
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ entry.date }}</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,7 +66,8 @@
|
|||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js.html' %}
|
||||||
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
{% include 'bundles/moment-js.html' with locale=True %}
|
||||||
|
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% if member.is_leader %}
|
{% if member.is_leader %}
|
||||||
<i class="fa fa-star"></i>
|
<i class="fas fa-star"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle">
|
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle">
|
||||||
</td>
|
</td>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p class="text-muted"><i class="fa fa-star"></i>: Group leader</p>
|
<p class="text-muted"><i class="fas fa-star"></i>: Group leader</p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning text-center">
|
<div class="alert alert-warning text-center">
|
||||||
|
|||||||
@@ -53,6 +53,10 @@
|
|||||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
|
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
|
||||||
<i class="glyphicon glyphicon-list-alt"></i>
|
<i class="glyphicon glyphicon-list-alt"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% trans "Copy Direct Join Link" %}">
|
||||||
|
<i class="glyphicon glyphicon-copy"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -68,3 +72,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
{% block extra_javascript %}
|
||||||
|
{% include 'bundles/clipboard-js.html' %}
|
||||||
|
<script>
|
||||||
|
new ClipboardJS('#clipboard-copy');
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -20,8 +20,22 @@
|
|||||||
{% include 'groupmanagement/menu.html' %}
|
{% include 'groupmanagement/menu.html' %}
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="active"><a data-toggle="tab" href="#add">{% trans "Join Requests" %}</a></li>
|
<li class="active">
|
||||||
<li><a data-toggle="tab" href="#leave">{% trans "Leave Requests" %}</a></li>
|
<a data-toggle="tab" href="#add">
|
||||||
|
{% trans "Join Requests" %}
|
||||||
|
{% if acceptrequests %}
|
||||||
|
<span class="badge">{{ acceptrequests|length }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a data-toggle="tab" href="#leave">
|
||||||
|
{% trans "Leave Requests" %}
|
||||||
|
{% if leaverequests %}
|
||||||
|
<span class="badge">{{ leaverequests|length }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ from allianceauth.eveonline.models import (
|
|||||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..admin import (
|
from ..admin import HasLeaderFilter, GroupAdmin, Group
|
||||||
HasLeaderFilter,
|
|
||||||
GroupAdmin,
|
|
||||||
Group
|
|
||||||
)
|
|
||||||
from . import get_admin_change_view_url
|
from . import get_admin_change_view_url
|
||||||
|
|
||||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||||
@@ -88,33 +84,33 @@ class TestGroupAdmin(TestCase):
|
|||||||
|
|
||||||
# user 1 - corp and alliance, normal user
|
# user 1 - corp and alliance, normal user
|
||||||
cls.character_1 = EveCharacter.objects.create(
|
cls.character_1 = EveCharacter.objects.create(
|
||||||
character_id='1001',
|
character_id=1001,
|
||||||
character_name='Bruce Wayne',
|
character_name='Bruce Wayne',
|
||||||
corporation_id='2001',
|
corporation_id=2001,
|
||||||
corporation_name='Wayne Technologies',
|
corporation_name='Wayne Technologies',
|
||||||
corporation_ticker='WT',
|
corporation_ticker='WT',
|
||||||
alliance_id='3001',
|
alliance_id=3001,
|
||||||
alliance_name='Wayne Enterprises',
|
alliance_name='Wayne Enterprises',
|
||||||
alliance_ticker='WE',
|
alliance_ticker='WE',
|
||||||
)
|
)
|
||||||
cls.character_1a = EveCharacter.objects.create(
|
cls.character_1a = EveCharacter.objects.create(
|
||||||
character_id='1002',
|
character_id=1002,
|
||||||
character_name='Batman',
|
character_name='Batman',
|
||||||
corporation_id='2001',
|
corporation_id=2001,
|
||||||
corporation_name='Wayne Technologies',
|
corporation_name='Wayne Technologies',
|
||||||
corporation_ticker='WT',
|
corporation_ticker='WT',
|
||||||
alliance_id='3001',
|
alliance_id=3001,
|
||||||
alliance_name='Wayne Enterprises',
|
alliance_name='Wayne Enterprises',
|
||||||
alliance_ticker='WE',
|
alliance_ticker='WE',
|
||||||
)
|
)
|
||||||
alliance = EveAllianceInfo.objects.create(
|
alliance = EveAllianceInfo.objects.create(
|
||||||
alliance_id='3001',
|
alliance_id=3001,
|
||||||
alliance_name='Wayne Enterprises',
|
alliance_name='Wayne Enterprises',
|
||||||
alliance_ticker='WE',
|
alliance_ticker='WE',
|
||||||
executor_corp_id='2001'
|
executor_corp_id=2001
|
||||||
)
|
)
|
||||||
EveCorporationInfo.objects.create(
|
EveCorporationInfo.objects.create(
|
||||||
corporation_id='2001',
|
corporation_id=2001,
|
||||||
corporation_name='Wayne Technologies',
|
corporation_name='Wayne Technologies',
|
||||||
corporation_ticker='WT',
|
corporation_ticker='WT',
|
||||||
member_count=42,
|
member_count=42,
|
||||||
@@ -189,10 +185,10 @@ class TestGroupAdmin(TestCase):
|
|||||||
alliance=None
|
alliance=None
|
||||||
)
|
)
|
||||||
EveAllianceInfo.objects.create(
|
EveAllianceInfo.objects.create(
|
||||||
alliance_id='3101',
|
alliance_id=3101,
|
||||||
alliance_name='Lex World Domination',
|
alliance_name='Lex World Domination',
|
||||||
alliance_ticker='LWD',
|
alliance_ticker='LWD',
|
||||||
executor_corp_id=''
|
executor_corp_id=2101
|
||||||
)
|
)
|
||||||
cls.user_3 = User.objects.create_user(
|
cls.user_3 = User.objects.create_user(
|
||||||
cls.character_3.character_name.replace(' ', '_'),
|
cls.character_3.character_name.replace(' ', '_'),
|
||||||
@@ -219,8 +215,8 @@ class TestGroupAdmin(TestCase):
|
|||||||
"""create autogroups for corps and alliances"""
|
"""create autogroups for corps and alliances"""
|
||||||
if _has_auto_groups:
|
if _has_auto_groups:
|
||||||
autogroups_config = AutogroupsConfig(
|
autogroups_config = AutogroupsConfig(
|
||||||
corp_groups = True,
|
corp_groups=True,
|
||||||
alliance_groups = True
|
alliance_groups=True
|
||||||
)
|
)
|
||||||
autogroups_config.save()
|
autogroups_config.save()
|
||||||
for state in State.objects.all():
|
for state in State.objects.all():
|
||||||
@@ -277,7 +273,7 @@ class TestGroupAdmin(TestCase):
|
|||||||
|
|
||||||
if _has_auto_groups:
|
if _has_auto_groups:
|
||||||
@patch(MODULE_PATH + '._has_auto_groups', True)
|
@patch(MODULE_PATH + '._has_auto_groups', True)
|
||||||
def test_properties_6(self):
|
def test_properties_7(self):
|
||||||
self._create_autogroups()
|
self._create_autogroups()
|
||||||
expected = ['Auto Group']
|
expected = ['Auto Group']
|
||||||
my_group = Group.objects\
|
my_group = Group.objects\
|
||||||
@@ -337,8 +333,8 @@ class TestGroupAdmin(TestCase):
|
|||||||
changelist = my_modeladmin.get_changelist_instance(request)
|
changelist = my_modeladmin.get_changelist_instance(request)
|
||||||
queryset = changelist.get_queryset(request)
|
queryset = changelist.get_queryset(request)
|
||||||
expected = Group.objects.exclude(
|
expected = Group.objects.exclude(
|
||||||
managedalliancegroup__isnull=True,
|
managedalliancegroup__isnull=True,
|
||||||
managedcorpgroup__isnull=True
|
managedcorpgroup__isnull=True
|
||||||
)
|
)
|
||||||
self.assertSetEqual(set(queryset), set(expected))
|
self.assertSetEqual(set(queryset), set(expected))
|
||||||
|
|
||||||
@@ -394,4 +390,4 @@ class TestGroupAdmin(TestCase):
|
|||||||
c = Client()
|
c = Client()
|
||||||
c.login(username='superuser', password='secret')
|
c.login(username='superuser', password='secret')
|
||||||
response = c.get(get_admin_change_view_url(self.group_1))
|
response = c.get(get_admin_change_view_url(self.group_1))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from django.urls import reverse
|
|||||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from ..models import AuthGroup
|
from ..models import GroupRequest
|
||||||
from ..managers import GroupManager
|
from ..managers import GroupManager
|
||||||
|
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ class MockUserNotAuthenticated():
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.is_authenticated = False
|
self.is_authenticated = False
|
||||||
|
|
||||||
|
|
||||||
class GroupManagementVisibilityTestCase(TestCase):
|
class GroupManagementVisibilityTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@@ -37,22 +38,20 @@ class GroupManagementVisibilityTestCase(TestCase):
|
|||||||
def _refresh_user(self):
|
def _refresh_user(self):
|
||||||
self.user = User.objects.get(pk=self.user.pk)
|
self.user = User.objects.get(pk=self.user.pk)
|
||||||
|
|
||||||
|
|
||||||
def test_get_group_leaders_groups(self):
|
def test_get_group_leaders_groups(self):
|
||||||
self.group1.authgroup.group_leaders.add(self.user)
|
self.group1.authgroup.group_leaders.add(self.user)
|
||||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||||
self._refresh_user()
|
self._refresh_user()
|
||||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||||
|
|
||||||
self.assertIn(self.group1, groups) #avail due to user
|
self.assertIn(self.group1, groups) #avail due to user
|
||||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||||
self.assertNotIn(self.group3, groups) #not avail at all
|
self.assertNotIn(self.group3, groups) #not avail at all
|
||||||
|
|
||||||
self.user.groups.add(self.group1)
|
self.user.groups.add(self.group1)
|
||||||
self._refresh_user()
|
self._refresh_user()
|
||||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||||
|
|
||||||
|
|
||||||
def test_can_manage_group(self):
|
def test_can_manage_group(self):
|
||||||
self.group1.authgroup.group_leaders.add(self.user)
|
self.group1.authgroup.group_leaders.add(self.user)
|
||||||
self.user.groups.add(self.group1)
|
self.user.groups.add(self.group1)
|
||||||
@@ -182,7 +181,6 @@ class TestGroupManager(TestCase):
|
|||||||
]:
|
]:
|
||||||
self.assertFalse(GroupManager.joinable_group(x, member_state))
|
self.assertFalse(GroupManager.joinable_group(x, member_state))
|
||||||
|
|
||||||
|
|
||||||
def test_joinable_group_guest(self):
|
def test_joinable_group_guest(self):
|
||||||
guest_state = AuthUtils.get_guest_state()
|
guest_state = AuthUtils.get_guest_state()
|
||||||
for x in [
|
for x in [
|
||||||
@@ -200,7 +198,6 @@ class TestGroupManager(TestCase):
|
|||||||
]:
|
]:
|
||||||
self.assertFalse(GroupManager.joinable_group(x, guest_state))
|
self.assertFalse(GroupManager.joinable_group(x, guest_state))
|
||||||
|
|
||||||
|
|
||||||
def test_get_all_non_internal_groups(self):
|
def test_get_all_non_internal_groups(self):
|
||||||
result = GroupManager.get_all_non_internal_groups()
|
result = GroupManager.get_all_non_internal_groups()
|
||||||
expected = {
|
expected = {
|
||||||
@@ -224,7 +221,7 @@ class TestGroupManager(TestCase):
|
|||||||
def test_get_joinable_groups_for_user_no_permission(self):
|
def test_get_joinable_groups_for_user_no_permission(self):
|
||||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||||
expected= {self.group_public_1, self.group_public_2}
|
expected = {self.group_public_1, self.group_public_2}
|
||||||
self.assertSetEqual(set(result), expected)
|
self.assertSetEqual(set(result), expected)
|
||||||
|
|
||||||
def test_get_joinable_groups_for_user_guest_w_permission_(self):
|
def test_get_joinable_groups_for_user_guest_w_permission_(self):
|
||||||
@@ -335,3 +332,96 @@ class TestGroupManager(TestCase):
|
|||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
GroupManager.can_manage_group(user, self.group_default)
|
GroupManager.can_manage_group(user, self.group_default)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPendingRequestsCountForUser(TestCase):
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.group_1 = Group.objects.create(name="Group 1")
|
||||||
|
self.group_2 = Group.objects.create(name="Group 2")
|
||||||
|
self.user_leader_1 = AuthUtils.create_member('Clark Kent')
|
||||||
|
self.group_1.authgroup.group_leaders.add(self.user_leader_1)
|
||||||
|
self.user_leader_2 = AuthUtils.create_member('Peter Parker')
|
||||||
|
self.group_2.authgroup.group_leaders.add(self.user_leader_2)
|
||||||
|
self.user_requestor = AuthUtils.create_member('Bruce Wayne')
|
||||||
|
|
||||||
|
def test_single_request_for_leader(self):
|
||||||
|
# given user_leader_1 is leader of group_1
|
||||||
|
# and user_leader_2 is leader of group_2
|
||||||
|
# when user_requestor is requesting access to group 1
|
||||||
|
# then return 1 for user_leader 1 and 0 for user_leader_2
|
||||||
|
GroupRequest.objects.create(
|
||||||
|
status="pending", user=self.user_requestor, group=self.group_1
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
GroupManager.pending_requests_count_for_user(self.user_leader_2), 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_return_none_for_none_leader(self):
|
||||||
|
# given user_requestor is leader of no group
|
||||||
|
# when user_requestor is requesting access to group 1
|
||||||
|
# then return 0 for user_requestor
|
||||||
|
GroupRequest.objects.create(
|
||||||
|
status="pending", user=self.user_requestor, group=self.group_1
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
GroupManager.pending_requests_count_for_user(self.user_requestor), 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_single_leave_request(self):
|
||||||
|
# given user_leader_2 is leader of group_2
|
||||||
|
# and user_requestor is member of group 2
|
||||||
|
# when user_requestor is requesting to leave group 2
|
||||||
|
# then return 1 for user_leader_2
|
||||||
|
self.user_requestor.groups.add(self.group_2)
|
||||||
|
|
||||||
|
GroupRequest.objects.create(
|
||||||
|
status="pending",
|
||||||
|
user=self.user_requestor,
|
||||||
|
group=self.group_2,
|
||||||
|
leave_request=True
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
GroupManager.pending_requests_count_for_user(self.user_leader_2), 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_join_and_leave_request(self):
|
||||||
|
# given user_leader_2 is leader of group_2
|
||||||
|
# and user_requestor is member of group 2
|
||||||
|
# when user_requestor is requesting to leave group 2
|
||||||
|
# and user_requestor_2 is requesting to join group 2
|
||||||
|
# then return 2 for user_leader_2
|
||||||
|
self.user_requestor.groups.add(self.group_2)
|
||||||
|
user_requestor_2 = AuthUtils.create_member("Lex Luther")
|
||||||
|
GroupRequest.objects.create(
|
||||||
|
status="pending",
|
||||||
|
user=user_requestor_2,
|
||||||
|
group=self.group_2
|
||||||
|
)
|
||||||
|
GroupRequest.objects.create(
|
||||||
|
status="pending",
|
||||||
|
user=self.user_requestor,
|
||||||
|
group=self.group_2,
|
||||||
|
leave_request=True
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
GroupManager.pending_requests_count_for_user(self.user_leader_2), 2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_single_request_for_user_with_management_perm(self):
|
||||||
|
# given user_leader_4 which is leafer of no group
|
||||||
|
# but has the management permissions
|
||||||
|
# when user_requestor is requesting access to group 1
|
||||||
|
# then return 1 for user_leader_4
|
||||||
|
user_leader_4 = AuthUtils.create_member("Lex Luther")
|
||||||
|
AuthUtils.add_permission_to_user_by_name("auth.group_management", user_leader_4)
|
||||||
|
user_leader_4 = User.objects.get(pk=user_leader_4.pk)
|
||||||
|
GroupRequest.objects.create(
|
||||||
|
status="pending", user=self.user_requestor, group=self.group_1
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,32 +1,29 @@
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import url
|
||||||
app_name = 'groupmanagement'
|
app_name = 'groupmanagement'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^groups/', views.groups_view, name='groups'),
|
url(r'^groups/', views.groups_view, name='groups'),
|
||||||
url(r'^group/', include([
|
url(r'^management/', views.group_management,
|
||||||
url(r'^management/', views.group_management,
|
name='management'),
|
||||||
name='management'),
|
url(r'^membership/$', views.group_membership,
|
||||||
url(r'^membership/$', views.group_membership,
|
name='membership'),
|
||||||
name='membership'),
|
url(r'^membership/(\w+)/$', views.group_membership_list,
|
||||||
url(r'^membership/(\w+)/$', views.group_membership_list,
|
name='membership_list'),
|
||||||
name='membership_list'),
|
url(r'^membership/(\w+)/audit/$', views.group_membership_audit, name="audit_log"),
|
||||||
url(r'^membership/(\w+)/audit/$', views.group_membership_audit, name="audit_log"),
|
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
|
||||||
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
|
name='membership_remove'),
|
||||||
name='membership_remove'),
|
url(r'^request_add/(\w+)', views.group_request_add,
|
||||||
url(r'^request_add/(\w+)', views.group_request_add,
|
name='request_add'),
|
||||||
name='request_add'),
|
url(r'^request/accept/(\w+)', views.group_accept_request,
|
||||||
url(r'^request/accept/(\w+)', views.group_accept_request,
|
name='accept_request'),
|
||||||
name='accept_request'),
|
url(r'^request/reject/(\w+)', views.group_reject_request,
|
||||||
url(r'^request/reject/(\w+)', views.group_reject_request,
|
name='reject_request'),
|
||||||
name='reject_request'),
|
url(r'^request_leave/(\w+)', views.group_request_leave,
|
||||||
|
name='request_leave'),
|
||||||
url(r'^request_leave/(\w+)', views.group_request_leave,
|
url(r'leave_request/accept/(\w+)', views.group_leave_accept_request,
|
||||||
name='request_leave'),
|
name='leave_accept_request'),
|
||||||
url(r'leave_request/accept/(\w+)', views.group_leave_accept_request,
|
url(r'^leave_request/reject/(\w+)', views.group_leave_reject_request,
|
||||||
name='leave_accept_request'),
|
name='leave_reject_request'),
|
||||||
url(r'^leave_request/reject/(\w+)', views.group_leave_reject_request,
|
|
||||||
name='leave_reject_request'),
|
|
||||||
])),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.contrib.auth.decorators import user_passes_test
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||||
from django.core.paginator import Paginator, EmptyPage
|
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
@@ -28,7 +27,7 @@ def group_management(request):
|
|||||||
acceptrequests = []
|
acceptrequests = []
|
||||||
leaverequests = []
|
leaverequests = []
|
||||||
|
|
||||||
base_group_query = GroupRequest.objects.select_related('user', 'group')
|
base_group_query = GroupRequest.objects.select_related('user', 'group', 'user__profile__main_character')
|
||||||
if GroupManager.has_management_permission(request.user):
|
if GroupManager.has_management_permission(request.user):
|
||||||
# Full access
|
# Full access
|
||||||
group_requests = base_group_query.all()
|
group_requests = base_group_query.all()
|
||||||
@@ -76,7 +75,6 @@ def group_membership_audit(request, group_id):
|
|||||||
logger.debug("group_management_audit called by user %s" % request.user)
|
logger.debug("group_management_audit called by user %s" % request.user)
|
||||||
group = get_object_or_404(Group, id=group_id)
|
group = get_object_or_404(Group, id=group_id)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# Check its a joinable group i.e. not corp or internal
|
# Check its a joinable group i.e. not corp or internal
|
||||||
# And the user has permission to manage it
|
# And the user has permission to manage it
|
||||||
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||||
@@ -93,8 +91,6 @@ def group_membership_audit(request, group_id):
|
|||||||
return render(request, 'groupmanagement/audit.html', context=render_items)
|
return render(request, 'groupmanagement/audit.html', context=render_items)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(GroupManager.can_manage_groups)
|
@user_passes_test(GroupManager.can_manage_groups)
|
||||||
def group_membership_list(request, group_id):
|
def group_membership_list(request, group_id):
|
||||||
@@ -124,7 +120,7 @@ def group_membership_list(request, group_id):
|
|||||||
for member in \
|
for member in \
|
||||||
group.user_set\
|
group.user_set\
|
||||||
.all()\
|
.all()\
|
||||||
.select_related('profile')\
|
.select_related('profile', 'profile__main_character')\
|
||||||
.order_by('profile__main_character__character_name'):
|
.order_by('profile__main_character__character_name'):
|
||||||
|
|
||||||
members.append({
|
members.append({
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.hrapplications import urls
|
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||||
|
|
||||||
|
from . import urls
|
||||||
|
from .models import Application
|
||||||
|
|
||||||
|
|
||||||
class ApplicationsMenu(MenuItemHook):
|
class ApplicationsMenu(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(self,
|
||||||
_('Applications'),
|
_('Applications'),
|
||||||
'fa fa-file-o fa-fw',
|
'far fa-file fa-fw',
|
||||||
'hrapplications:index',
|
'hrapplications:index',
|
||||||
navactive=['hrapplications:'])
|
navactive=['hrapplications:'])
|
||||||
|
|
||||||
|
def render(self, request):
|
||||||
|
app_count = Application.objects.pending_requests_count_for_user(request.user)
|
||||||
|
self.count = app_count if app_count and app_count > 0 else None
|
||||||
|
return MenuItemHook.render(self, request)
|
||||||
|
|
||||||
|
|
||||||
@hooks.register('menu_item_hook')
|
@hooks.register('menu_item_hook')
|
||||||
def register_menu():
|
def register_menu():
|
||||||
|
|||||||
25
allianceauth/hrapplications/managers.py
Normal file
25
allianceauth/hrapplications/managers.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationManager(models.Manager):
|
||||||
|
|
||||||
|
def pending_requests_count_for_user(self, user: User) -> Optional[int]:
|
||||||
|
"""Returns the number of pending group requests for the given user"""
|
||||||
|
if user.is_superuser:
|
||||||
|
return self.filter(approved__isnull=True).count()
|
||||||
|
elif user.has_perm("auth.human_resources"):
|
||||||
|
main_character = user.profile.main_character
|
||||||
|
if main_character:
|
||||||
|
return (
|
||||||
|
self
|
||||||
|
.select_related("form__corp")
|
||||||
|
.filter(form__corp__corporation_id=main_character.corporation_id)
|
||||||
|
.filter(approved__isnull=True)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
@@ -2,8 +2,9 @@ from django.contrib.auth.models import User
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from sortedm2m.fields import SortedManyToManyField
|
from sortedm2m.fields import SortedManyToManyField
|
||||||
|
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo
|
||||||
from allianceauth.eveonline.models import EveCorporationInfo
|
|
||||||
|
from .managers import ApplicationManager
|
||||||
|
|
||||||
|
|
||||||
class ApplicationQuestion(models.Model):
|
class ApplicationQuestion(models.Model):
|
||||||
@@ -22,6 +23,7 @@ class ApplicationChoice(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.choice_text
|
return self.choice_text
|
||||||
|
|
||||||
|
|
||||||
class ApplicationForm(models.Model):
|
class ApplicationForm(models.Model):
|
||||||
questions = SortedManyToManyField(ApplicationQuestion)
|
questions = SortedManyToManyField(ApplicationQuestion)
|
||||||
corp = models.OneToOneField(EveCorporationInfo, on_delete=models.CASCADE)
|
corp = models.OneToOneField(EveCorporationInfo, on_delete=models.CASCADE)
|
||||||
@@ -38,6 +40,8 @@ class Application(models.Model):
|
|||||||
reviewer_character = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, blank=True, null=True)
|
reviewer_character = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, blank=True, null=True)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
objects = ApplicationManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.user) + " Application To " + str(self.form)
|
return str(self.user) + " Application To " + str(self.form)
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,103 @@
|
|||||||
# Create your tests here.
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from allianceauth.eveonline.models import EveCorporationInfo
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
from .models import Application, ApplicationForm, ApplicationQuestion, ApplicationChoice
|
||||||
|
|
||||||
|
|
||||||
|
class TestApplicationManagersPendingRequestsCountForUser(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.corporation_1 = EveCorporationInfo.objects.create(
|
||||||
|
corporation_id=2001, corporation_name="Wayne Tech", member_count=42
|
||||||
|
)
|
||||||
|
self.corporation_2 = EveCorporationInfo.objects.create(
|
||||||
|
corporation_id=2011, corporation_name="Lex Corp", member_count=666
|
||||||
|
)
|
||||||
|
question = ApplicationQuestion.objects.create(title="Dummy Question")
|
||||||
|
ApplicationChoice.objects.create(question=question, choice_text="yes")
|
||||||
|
ApplicationChoice.objects.create(question=question, choice_text="no")
|
||||||
|
self.form_corporation_1 = ApplicationForm.objects.create(
|
||||||
|
corp=self.corporation_1
|
||||||
|
)
|
||||||
|
self.form_corporation_1.questions.add(question)
|
||||||
|
self.form_corporation_2 = ApplicationForm.objects.create(
|
||||||
|
corp=self.corporation_2
|
||||||
|
)
|
||||||
|
self.form_corporation_2.questions.add(question)
|
||||||
|
|
||||||
|
self.user_requestor = AuthUtils.create_member("Peter Parker")
|
||||||
|
|
||||||
|
self.user_manager = AuthUtils.create_member("Bruce Wayne")
|
||||||
|
AuthUtils.add_main_character_2(
|
||||||
|
self.user_manager,
|
||||||
|
self.user_manager.username,
|
||||||
|
1001,
|
||||||
|
self.corporation_1.corporation_id,
|
||||||
|
self.corporation_1.corporation_name,
|
||||||
|
)
|
||||||
|
AuthUtils.add_permission_to_user_by_name(
|
||||||
|
"auth.human_resources", self.user_manager
|
||||||
|
)
|
||||||
|
self.user_manager = User.objects.get(pk=self.user_manager.pk)
|
||||||
|
|
||||||
|
def test_no_pending_application(self):
|
||||||
|
# given manager of corporation 1 has permission
|
||||||
|
# when no application is pending for corporation 1
|
||||||
|
# return 0
|
||||||
|
self.assertEqual(
|
||||||
|
Application.objects.pending_requests_count_for_user(self.user_manager), 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_single_pending_application(self):
|
||||||
|
# given manager of corporation 1 has permission
|
||||||
|
# when 1 application is pending for corporation 1
|
||||||
|
# return 1
|
||||||
|
Application.objects.create(
|
||||||
|
form=self.form_corporation_1, user=self.user_requestor
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
Application.objects.pending_requests_count_for_user(self.user_manager), 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_has_no_permission(self):
|
||||||
|
# given user has no permission
|
||||||
|
# when 1 application is pending
|
||||||
|
# return None
|
||||||
|
self.assertIsNone(
|
||||||
|
Application.objects.pending_requests_count_for_user(self.user_requestor)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_two_pending_applications_for_different_corporations_normal_manager(self):
|
||||||
|
# given manager of corporation 1 has permission
|
||||||
|
# when 1 application is pending for corporation 1
|
||||||
|
# and 1 application is pending for corporation 2
|
||||||
|
# return 1
|
||||||
|
Application.objects.create(
|
||||||
|
form=self.form_corporation_1, user=self.user_requestor
|
||||||
|
)
|
||||||
|
Application.objects.create(
|
||||||
|
form=self.form_corporation_2, user=self.user_requestor
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
Application.objects.pending_requests_count_for_user(self.user_manager), 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_two_pending_applications_for_different_corporations_manager_is_super(self):
|
||||||
|
# given manager of corporation 1 has permission
|
||||||
|
# when 1 application is pending for corporation 1
|
||||||
|
# and 1 application is pending for corporation 2
|
||||||
|
# return 1
|
||||||
|
Application.objects.create(
|
||||||
|
form=self.form_corporation_1, user=self.user_requestor
|
||||||
|
)
|
||||||
|
Application.objects.create(
|
||||||
|
form=self.form_corporation_2, user=self.user_requestor
|
||||||
|
)
|
||||||
|
superuser = User.objects.create_superuser(
|
||||||
|
"Superman", "superman@example.com", "password"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
Application.objects.pending_requests_count_for_user(superuser), 2
|
||||||
|
)
|
||||||
|
|||||||
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-04-02 03:23+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"
|
||||||
|
|
||||||
@@ -657,7 +667,11 @@ msgstr "Mitglieder ansehen"
|
|||||||
msgid "Audit Members"
|
msgid "Audit Members"
|
||||||
msgstr "Mitglieder Protokoll"
|
msgstr "Mitglieder Protokoll"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||||
|
msgid "Copy Direct Join Link"
|
||||||
|
msgstr "Direktlink kopieren"
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||||
msgid "No groups to list."
|
msgid "No groups to list."
|
||||||
msgstr "Keine Gruppen vorhanden."
|
msgstr "Keine Gruppen vorhanden."
|
||||||
|
|
||||||
@@ -686,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"
|
||||||
|
|
||||||
@@ -725,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"
|
||||||
|
|
||||||
@@ -737,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 "
|
||||||
@@ -765,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 "
|
||||||
@@ -785,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
|
||||||
@@ -816,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."
|
||||||
@@ -1177,7 +1191,7 @@ msgstr "Änderungen für Operation timer %(opname)s gespeichert."
|
|||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:6
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:6
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:10
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:10
|
||||||
msgid "Permissions Audit"
|
msgid "Permissions Audit"
|
||||||
msgstr "Berechtigungsprüfung"
|
msgstr "Berechtigungsübersicht"
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
||||||
msgid "User / Character"
|
msgid "User / Character"
|
||||||
@@ -1296,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."
|
||||||
@@ -1880,44 +1925,36 @@ 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"
|
||||||
msgstr[0] "%(tasks)sAufgabe"
|
msgstr[0] "%(tasks)sAufgabe"
|
||||||
msgstr[1] "%(tasks)sAufgaben"
|
msgstr[1] "%(tasks)sAufgaben"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/help.html:4
|
|
||||||
#: allianceauth/templates/allianceauth/help.html:9
|
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:34
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "Hilfe"
|
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
||||||
msgid "Night"
|
msgid "Night"
|
||||||
msgstr "Nacht"
|
msgstr "Nacht"
|
||||||
|
|||||||
@@ -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-04-02 03:23+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 ""
|
||||||
|
|
||||||
@@ -642,7 +650,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 ""
|
||||||
|
|
||||||
@@ -671,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 ""
|
||||||
|
|
||||||
@@ -710,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 ""
|
||||||
|
|
||||||
@@ -722,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
|
||||||
@@ -797,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 ""
|
||||||
@@ -1277,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
|
||||||
@@ -1844,44 +1883,36 @@ 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] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
#: 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 ""
|
||||||
|
|||||||
Binary file not shown.
@@ -12,7 +12,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-03-26 03:07+0000\n"
|
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||||
"Last-Translator: frank1210 <francolopez_16@hotmail.com>, 2020\n"
|
"Last-Translator: frank1210 <francolopez_16@hotmail.com>, 2020\n"
|
||||||
"Language-Team: Spanish (https://www.transifex.com/alliance-auth/teams/107430/es/)\n"
|
"Language-Team: Spanish (https://www.transifex.com/alliance-auth/teams/107430/es/)\n"
|
||||||
@@ -653,7 +653,11 @@ msgstr "Ver Miembros"
|
|||||||
msgid "Audit Members"
|
msgid "Audit Members"
|
||||||
msgstr "Auditar Miembros"
|
msgstr "Auditar Miembros"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||||
|
msgid "Copy Direrct Join Link"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||||
msgid "No groups to list."
|
msgid "No groups to list."
|
||||||
msgstr "No hay grupos para listar"
|
msgstr "No hay grupos para listar"
|
||||||
|
|
||||||
@@ -785,21 +789,21 @@ msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Se rechazo la solicitud de %(mainchar)s para dejar el grupo %(group)s."
|
"Se rechazo la solicitud de %(mainchar)s para dejar el grupo %(group)s."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:347
|
#: allianceauth/groupmanagement/views.py:346
|
||||||
#: allianceauth/groupmanagement/views.py:359
|
#: allianceauth/groupmanagement/views.py:358
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr "No puedes unirte a ese grupo"
|
msgstr "No puedes unirte a ese grupo"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:353
|
#: allianceauth/groupmanagement/views.py:352
|
||||||
msgid "You are already a member of that group."
|
msgid "You are already a member of that group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:368
|
#: allianceauth/groupmanagement/views.py:367
|
||||||
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:371
|
#: allianceauth/groupmanagement/views.py:370
|
||||||
#: allianceauth/groupmanagement/views.py:409
|
#: allianceauth/groupmanagement/views.py:408
|
||||||
#: 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
|
||||||
@@ -811,24 +815,24 @@ msgstr ""
|
|||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Pendiente"
|
msgstr "Pendiente"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:377
|
#: allianceauth/groupmanagement/views.py:376
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to group %(group)s."
|
msgid "Applied to group %(group)s."
|
||||||
msgstr "Solicitud enviada al grupo %(group)s."
|
msgstr "Solicitud enviada al grupo %(group)s."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:388
|
#: allianceauth/groupmanagement/views.py:387
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr "No puedes dejar el grupos"
|
msgstr "No puedes dejar el grupos"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:393
|
#: allianceauth/groupmanagement/views.py:392
|
||||||
msgid "You are not a member of that group"
|
msgid "You are not a member of that group"
|
||||||
msgstr "No eres miembro de ese grupo"
|
msgstr "No eres miembro de ese grupo"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:402
|
#: allianceauth/groupmanagement/views.py:401
|
||||||
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:415
|
#: allianceauth/groupmanagement/views.py:414
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to leave group %(group)s."
|
msgid "Applied to leave group %(group)s."
|
||||||
msgstr "Solicitaste dejar el grupo %(group)s."
|
msgstr "Solicitaste dejar el grupo %(group)s."
|
||||||
@@ -1894,12 +1898,6 @@ msgid_plural "%(tasks)s tasks"
|
|||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/help.html:4
|
|
||||||
#: allianceauth/templates/allianceauth/help.html:9
|
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:34
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "Ayuda"
|
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
||||||
msgid "Night"
|
msgid "Night"
|
||||||
msgstr "Noche"
|
msgstr "Noche"
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
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-03-26 03:07+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 "Группы"
|
||||||
|
|
||||||
@@ -651,7 +663,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 "Нет групп в списке"
|
||||||
|
|
||||||
@@ -680,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 "Нет групповых запросов на выход"
|
||||||
|
|
||||||
@@ -719,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 "Управление Группой"
|
||||||
|
|
||||||
@@ -731,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 "
|
||||||
@@ -759,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:347
|
#: allianceauth/groupmanagement/views.py:342
|
||||||
#: allianceauth/groupmanagement/views.py:359
|
#: allianceauth/groupmanagement/views.py:354
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr "Вы не можете вступить"
|
msgstr "Вы не можете вступить"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:353
|
#: 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:368
|
#: 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:371
|
#: allianceauth/groupmanagement/views.py:366
|
||||||
#: allianceauth/groupmanagement/views.py:409
|
#: 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
|
||||||
@@ -808,24 +826,24 @@ msgstr ""
|
|||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Ожидание"
|
msgstr "Ожидание"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:377
|
#: 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:388
|
#: allianceauth/groupmanagement/views.py:383
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr "Вы не можете покинуть эту группу"
|
msgstr "Вы не можете покинуть эту группу"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:393
|
#: 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:402
|
#: 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:415
|
#: 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."
|
||||||
@@ -1218,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:"
|
||||||
@@ -1288,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
|
||||||
@@ -1350,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
|
||||||
@@ -1376,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."
|
||||||
@@ -1469,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."
|
||||||
@@ -1862,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"
|
||||||
@@ -1896,12 +1949,6 @@ msgstr[1] "%(tasks)s задач"
|
|||||||
msgstr[2] "%(tasks)s задач"
|
msgstr[2] "%(tasks)s задач"
|
||||||
msgstr[3] "%(tasks)s задач"
|
msgstr[3] "%(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 "Ночь"
|
||||||
|
|||||||
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 "暗色皮肤"
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
from .models import Notification
|
from .models import Notification
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
def user_notification_count(request):
|
def user_notification_count(request):
|
||||||
return {'notifications': len(Notification.objects.filter(user__id=request.user.id).filter(viewed=False))}
|
user_id = request.user.id
|
||||||
|
notification_count = cache.get("u-note:{}".format(user_id), -1)
|
||||||
|
if notification_count<0:
|
||||||
|
notification_count = Notification.objects.filter(user__id=user_id).filter(viewed=False).count()
|
||||||
|
cache.set("u-note:{}".format(user_id),notification_count,5)
|
||||||
|
|
||||||
|
return {'notifications': notification_count}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
# Create your tests here.
|
|
||||||
0
allianceauth/notifications/tests/__init__.py
Normal file
0
allianceauth/notifications/tests/__init__.py
Normal file
76
allianceauth/notifications/tests/test_processors.py
Normal file
76
allianceauth/notifications/tests/test_processors.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from unittest import mock
|
||||||
|
from django.test import TestCase
|
||||||
|
from allianceauth.notifications.context_processors import user_notification_count
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
from django.core.cache import cache
|
||||||
|
from allianceauth.notifications.models import Notification
|
||||||
|
|
||||||
|
class TestNotificationCount(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = AuthUtils.create_user('magic_mike')
|
||||||
|
AuthUtils.add_main_character(cls.user, 'Magic Mike', '1', corp_id='2', corp_name='Pole Riders', corp_ticker='PRIDE', alliance_id='3', alliance_name='RIDERS')
|
||||||
|
cls.user.profile.refresh_from_db()
|
||||||
|
|
||||||
|
### test notifications for mike
|
||||||
|
Notification.objects.all().delete()
|
||||||
|
Notification.objects.create(user=cls.user,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 1 Failed",
|
||||||
|
message="Because it was broken",
|
||||||
|
viewed=True)
|
||||||
|
Notification.objects.create(user=cls.user,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 2 Failed",
|
||||||
|
message="Because it was broken")
|
||||||
|
Notification.objects.create(user=cls.user,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 3 Failed",
|
||||||
|
message="Because it was broken")
|
||||||
|
Notification.objects.create(user=cls.user,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 4 Failed",
|
||||||
|
message="Because it was broken")
|
||||||
|
Notification.objects.create(user=cls.user,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 5 Failed",
|
||||||
|
message="Because it was broken")
|
||||||
|
Notification.objects.create(user=cls.user,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 6 Failed",
|
||||||
|
message="Because it was broken")
|
||||||
|
|
||||||
|
cls.user2 = AuthUtils.create_user('teh_kid')
|
||||||
|
AuthUtils.add_main_character(cls.user, 'The Kid', '2', corp_id='2', corp_name='Pole Riders', corp_ticker='PRIDE', alliance_id='3', alliance_name='RIDERS')
|
||||||
|
cls.user2.profile.refresh_from_db()
|
||||||
|
|
||||||
|
# Noitification for kid
|
||||||
|
Notification.objects.create(user=cls.user2,
|
||||||
|
level="INFO",
|
||||||
|
title="Job 6 Failed",
|
||||||
|
message="Because it was broken")
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_cache(self):
|
||||||
|
mock_req = mock.MagicMock()
|
||||||
|
mock_req.user.id = self.user.id
|
||||||
|
|
||||||
|
cache.delete("u-note:{}".format(self.user.id)) # force the db to be hit
|
||||||
|
context_dict = user_notification_count(mock_req)
|
||||||
|
self.assertIsInstance(context_dict, dict)
|
||||||
|
self.assertEqual(context_dict.get('notifications'), 5) # 5 only
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('allianceauth.notifications.models.Notification.objects')
|
||||||
|
def test_cache(self, mock_foo):
|
||||||
|
mock_foo.filter.return_value = mock_foo
|
||||||
|
mock_foo.count.return_value = 5
|
||||||
|
mock_req = mock.MagicMock()
|
||||||
|
mock_req.user.id = self.user.id
|
||||||
|
|
||||||
|
cache.set("u-note:{}".format(self.user.id),10,5)
|
||||||
|
context_dict = user_notification_count(mock_req)
|
||||||
|
self.assertIsInstance(context_dict, dict)
|
||||||
|
self.assertEqual(context_dict.get('notifications'), 10) # cached value
|
||||||
|
self.assertEqual(mock_foo.called, 0) # ensure the DB was not hit
|
||||||
@@ -7,7 +7,7 @@ from . import urls
|
|||||||
class OpTimerboardMenu(MenuItemHook):
|
class OpTimerboardMenu(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self, _('Fleet Operations'),
|
MenuItemHook.__init__(self, _('Fleet Operations'),
|
||||||
'fa fa-exclamation fa-fw',
|
'fas fa-exclamation fa-fw',
|
||||||
'optimer:view',
|
'optimer:view',
|
||||||
navactive=['optimer:'])
|
navactive=['optimer:'])
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,15 @@
|
|||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
|
|
||||||
$('#id_start').datetimepicker({
|
$('#id_start').datetimepicker({
|
||||||
lang: '{{ LANGUAGE_CODE }}',
|
setlocale: '{{ LANGUAGE_CODE }}',
|
||||||
maskInput: true,
|
{% if NIGHT_MODE %}
|
||||||
format: 'Y-m-d H:i',minDate:0
|
theme: 'dark',
|
||||||
|
{% else %}
|
||||||
|
theme: 'default',
|
||||||
|
{% endif %}
|
||||||
|
mask: true,
|
||||||
|
format: 'Y-m-d H:i',
|
||||||
|
minDate: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endblock extra_script %}
|
{% endblock extra_script %}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
{% include 'bundles/moment-js.html' with locale=True %}
|
{% include 'bundles/moment-js.html' with locale=True %}
|
||||||
<script src="{% static 'js/timers.js' %}"></script>
|
<script src="{% static 'js/timers.js' %}"></script>
|
||||||
<script type="text/javascript">
|
<script type="application/javascript">
|
||||||
// Data
|
// Data
|
||||||
var timers = [
|
var timers = [
|
||||||
{% for op in optimer %}
|
{% for op in optimer %}
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="application/javascript">
|
||||||
|
|
||||||
timedUpdate();
|
timedUpdate();
|
||||||
setAllLocalTimes();
|
setAllLocalTimes();
|
||||||
|
|||||||
@@ -44,9 +44,15 @@
|
|||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
|
|
||||||
$('#id_start').datetimepicker({
|
$('#id_start').datetimepicker({
|
||||||
lang: '{{ LANGUAGE_CODE }}',
|
setlocale: '{{ LANGUAGE_CODE }}',
|
||||||
maskInput: true,
|
{% if NIGHT_MODE %}
|
||||||
format: 'Y-m-d H:i',minDate:0
|
theme: 'dark',
|
||||||
|
{% else %}
|
||||||
|
theme: 'default',
|
||||||
|
{% endif %}
|
||||||
|
mask: true,
|
||||||
|
format: 'Y-m-d H:i',
|
||||||
|
minDate: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
{% endblock extra_script %}
|
{% endblock extra_script %}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class PermissionsTool(MenuItemHook):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(self,
|
||||||
'Permissions Audit',
|
'Permissions Audit',
|
||||||
'fa fa-key fa-id-card',
|
'fas fa-id-card fa-fw',
|
||||||
'permissions_tool:overview',
|
'permissions_tool:overview',
|
||||||
order=400,
|
order=400,
|
||||||
navactive=['permissions_tool:'])
|
navactive=['permissions_tool:'])
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js.html' %}
|
||||||
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js.html' %}
|
||||||
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PermissionsToolViewsTestCase(WebTest):
|
|||||||
response_content = response.content.decode('utf-8')
|
response_content = response.content.decode('utf-8')
|
||||||
|
|
||||||
self.assertInHTML('<li><a class="active" href="/permissions/overview/">'
|
self.assertInHTML('<li><a class="active" href="/permissions/overview/">'
|
||||||
'<i class="fa fa-key fa-id-card"></i> Permissions Audit</a></li>', response_content)
|
'<i class="fas fa-id-card fa-fw"></i> Permissions Audit</a></li>', response_content)
|
||||||
|
|
||||||
def test_permissions_overview(self):
|
def test_permissions_overview(self):
|
||||||
self.app.set_user(self.member)
|
self.app.set_user(self.member)
|
||||||
|
|||||||
@@ -103,8 +103,7 @@ TEMPLATES = [
|
|||||||
'django.template.context_processors.media',
|
'django.template.context_processors.media',
|
||||||
'django.template.context_processors.static',
|
'django.template.context_processors.static',
|
||||||
'django.template.context_processors.tz',
|
'django.template.context_processors.tz',
|
||||||
'allianceauth.notifications.context_processors.user_notification_count',
|
'allianceauth.notifications.context_processors.user_notification_count',
|
||||||
'allianceauth.groupmanagement.context_processors.can_manage_groups',
|
|
||||||
'allianceauth.context_processors.auth_settings',
|
'allianceauth.context_processors.auth_settings',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -221,7 +220,7 @@ LOGGING = {
|
|||||||
'backupCount': 5, # edit this line to change number of log backups
|
'backupCount': 5, # edit this line to change number of log backups
|
||||||
},
|
},
|
||||||
'extension_file': {
|
'extension_file': {
|
||||||
'level': 'DEBUG',
|
'level': 'INFO',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': os.path.join(BASE_DIR, 'log/extensions.log'),
|
'filename': os.path.join(BASE_DIR, 'log/extensions.log'),
|
||||||
'formatter': 'verbose',
|
'formatter': 'verbose',
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ INSTALLED_APPS += [
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# To change the logging level for extensions, uncomment the following line.
|
||||||
|
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
|
||||||
|
|
||||||
|
|
||||||
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
|||||||
@@ -4,3 +4,8 @@ from allianceauth import urls
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'', include(urls)),
|
url(r'', include(urls)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
handler500 = 'allianceauth.views.Generic500Redirect'
|
||||||
|
handler404 = 'allianceauth.views.Generic404Redirect'
|
||||||
|
handler403 = 'allianceauth.views.Generic403Redirect'
|
||||||
|
handler400 = 'allianceauth.views.Generic400Redirect'
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models.functions import Lower
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.html import format_html
|
|
||||||
|
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
from allianceauth.authentication.admin import (
|
||||||
from allianceauth.authentication.admin import user_profile_pic, \
|
user_profile_pic,
|
||||||
user_username, user_main_organization, MainCorporationsFilter,\
|
user_username,
|
||||||
|
user_main_organization,
|
||||||
|
MainCorporationsFilter,
|
||||||
MainAllianceFilter
|
MainAllianceFilter
|
||||||
|
)
|
||||||
|
|
||||||
from .models import NameFormatConfig
|
from .models import NameFormatConfig
|
||||||
|
|
||||||
@@ -25,16 +25,24 @@ class ServicesUserAdmin(admin.ModelAdmin):
|
|||||||
list_select_related = True
|
list_select_related = True
|
||||||
list_display = (
|
list_display = (
|
||||||
user_profile_pic,
|
user_profile_pic,
|
||||||
user_username,
|
user_username,
|
||||||
|
'_state',
|
||||||
user_main_organization,
|
user_main_organization,
|
||||||
'_date_joined'
|
'_date_joined'
|
||||||
)
|
)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
|
'user__profile__state',
|
||||||
MainCorporationsFilter,
|
MainCorporationsFilter,
|
||||||
MainAllianceFilter,
|
MainAllianceFilter,
|
||||||
'user__date_joined'
|
'user__date_joined',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _state(self, obj):
|
||||||
|
return obj.user.profile.state.name
|
||||||
|
|
||||||
|
_state.short_description = 'state'
|
||||||
|
_state.admin_order_field = 'user__profile__state__name'
|
||||||
|
|
||||||
def _date_joined(self, obj):
|
def _date_joined(self, obj):
|
||||||
return obj.user.date_joined
|
return obj.user.date_joined
|
||||||
|
|
||||||
@@ -45,7 +53,8 @@ class ServicesUserAdmin(admin.ModelAdmin):
|
|||||||
class NameFormatConfigForm(forms.ModelForm):
|
class NameFormatConfigForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NameFormatConfigForm, self).__init__(*args, **kwargs)
|
super(NameFormatConfigForm, self).__init__(*args, **kwargs)
|
||||||
SERVICE_CHOICES = [(s.name, s.name) for h in hooks.get_hooks('services_hook') for s in [h()]]
|
SERVICE_CHOICES = \
|
||||||
|
[(s.name, s.name) for h in hooks.get_hooks('services_hook') for s in [h()]]
|
||||||
if self.instance.id:
|
if self.instance.id:
|
||||||
current_choice = (self.instance.service_name, self.instance.service_name)
|
current_choice = (self.instance.service_name, self.instance.service_name)
|
||||||
if current_choice not in SERVICE_CHOICES:
|
if current_choice not in SERVICE_CHOICES:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class Services(MenuItemHook):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(self,
|
||||||
_('Services'),
|
_('Services'),
|
||||||
'fa fa-cogs fa-fw',
|
'fas fa-cogs fa-fw',
|
||||||
'services:services', 100)
|
'services:services', 100)
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
|
|||||||
@@ -14,20 +14,22 @@ def get_extension_logger(name):
|
|||||||
Takes the name of a plugin/extension and generates a child logger of the extensions logger
|
Takes the name of a plugin/extension and generates a child logger of the extensions logger
|
||||||
to be used by the extension to log events to the extensions logger.
|
to be used by the extension to log events to the extensions logger.
|
||||||
|
|
||||||
The logging level is decided by whether or not DEBUG is set to true in the project settings. If
|
The logging level is determined by the level defined for the parent logger.
|
||||||
DEBUG is set to false, then the logging level is set to INFO.
|
|
||||||
|
|
||||||
:param: name: the name of the extension doing the logging
|
:param: name: the name of the extension doing the logging
|
||||||
:return: an extensions child logger
|
:return: an extensions child logger
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(name, str):
|
||||||
|
raise TypeError(f"get_extension_logger takes an argument of type string."
|
||||||
|
f"Instead received argument of type {type(name).__name__}.")
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from django.conf import settings
|
|
||||||
|
parent_logger = logging.getLogger('extensions')
|
||||||
|
|
||||||
logger = logging.getLogger('extensions.' + name)
|
logger = logging.getLogger('extensions.' + name)
|
||||||
logger.name = name
|
logger.name = name
|
||||||
logger.level = logging.INFO
|
logger.level = parent_logger.level
|
||||||
if settings.DEBUG:
|
|
||||||
logger.level = logging.DEBUG
|
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
@@ -137,6 +139,11 @@ class MenuItemHook:
|
|||||||
self.url_name = url_name
|
self.url_name = url_name
|
||||||
self.template = 'public/menuitem.html'
|
self.template = 'public/menuitem.html'
|
||||||
self.order = order if order is not None else 9999
|
self.order = order if order is not None else 9999
|
||||||
|
|
||||||
|
# count is an integer shown next to the menu item as badge when count != None
|
||||||
|
# apps need to set the count in their child class, e.g. in render() method
|
||||||
|
self.count = None
|
||||||
|
|
||||||
navactive = navactive or []
|
navactive = navactive or []
|
||||||
navactive.append(url_name)
|
navactive.append(url_name)
|
||||||
self.navactive = navactive
|
self.navactive = navactive
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
default_app_config = 'allianceauth.services.modules.discord.apps.DiscordServiceConfig'
|
default_app_config = 'allianceauth.services.modules.discord.apps.DiscordServiceConfig' # noqa
|
||||||
|
|
||||||
|
__title__ = 'Discord Service'
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import DiscordUser
|
from . import __title__
|
||||||
from ...admin import ServicesUserAdmin
|
from ...admin import ServicesUserAdmin
|
||||||
|
from .models import DiscordUser
|
||||||
|
from .utils import LoggerAddTag
|
||||||
|
|
||||||
|
|
||||||
|
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DiscordUser)
|
@admin.register(DiscordUser)
|
||||||
class DiscordUserAdmin(ServicesUserAdmin):
|
class DiscordUserAdmin(ServicesUserAdmin):
|
||||||
list_display = ServicesUserAdmin.list_display + ('_uid',)
|
search_fields = ServicesUserAdmin.search_fields + ('uid', 'username')
|
||||||
search_fields = ServicesUserAdmin.search_fields + ('uid', )
|
list_display = ServicesUserAdmin.list_display + ('activated', '_username', '_uid')
|
||||||
|
list_filter = ServicesUserAdmin.list_filter + ('activated',)
|
||||||
|
ordering = ('-activated',)
|
||||||
|
|
||||||
def _uid(self, obj):
|
def _uid(self, obj):
|
||||||
return obj.uid
|
return obj.uid
|
||||||
@@ -15,3 +24,11 @@ class DiscordUserAdmin(ServicesUserAdmin):
|
|||||||
_uid.short_description = 'Discord ID (UID)'
|
_uid.short_description = 'Discord ID (UID)'
|
||||||
_uid.admin_order_field = 'uid'
|
_uid.admin_order_field = 'uid'
|
||||||
|
|
||||||
|
def _username(self, obj):
|
||||||
|
if obj.username and obj.discriminator:
|
||||||
|
return f'{obj.username}#{obj.discriminator}'
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
_username.short_description = 'Discord Username'
|
||||||
|
_username.admin_order_field = 'username'
|
||||||
|
|||||||
17
allianceauth/services/modules/discord/app_settings.py
Normal file
17
allianceauth/services/modules/discord/app_settings.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from .utils import clean_setting
|
||||||
|
|
||||||
|
|
||||||
|
DISCORD_APP_ID = clean_setting('DISCORD_APP_ID', '')
|
||||||
|
DISCORD_APP_SECRET = clean_setting('DISCORD_APP_SECRET', '')
|
||||||
|
DISCORD_BOT_TOKEN = clean_setting('DISCORD_BOT_TOKEN', '')
|
||||||
|
DISCORD_CALLBACK_URL = clean_setting('DISCORD_CALLBACK_URL', '')
|
||||||
|
DISCORD_GUILD_ID = clean_setting('DISCORD_GUILD_ID', '')
|
||||||
|
|
||||||
|
# max retries of tasks after an error occurred
|
||||||
|
DISCORD_TASKS_MAX_RETRIES = clean_setting('DISCORD_TASKS_MAX_RETRIES', 3)
|
||||||
|
|
||||||
|
# Pause in seconds until next retry for tasks after the API returned an error
|
||||||
|
DISCORD_TASKS_RETRY_PAUSE = clean_setting('DISCORD_TASKS_RETRY_PAUSE', 60)
|
||||||
|
|
||||||
|
# automatically sync Discord users names to user's main character name when created
|
||||||
|
DISCORD_SYNC_NAMES = clean_setting('DISCORD_SYNC_NAMES', False)
|
||||||
@@ -1,17 +1,26 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.services.hooks import ServicesHook
|
from allianceauth.services.hooks import ServicesHook
|
||||||
from .tasks import DiscordTasks
|
|
||||||
from .urls import urlpatterns
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
from .models import DiscordUser
|
||||||
|
from .urls import urlpatterns
|
||||||
|
from .utils import LoggerAddTag
|
||||||
|
from . import tasks, __title__
|
||||||
|
|
||||||
|
|
||||||
|
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
||||||
|
|
||||||
|
# Default priority for single tasks like update group and sync nickname
|
||||||
|
SINGLE_TASK_PRIORITY = 3
|
||||||
|
|
||||||
|
|
||||||
class DiscordService(ServicesHook):
|
class DiscordService(ServicesHook):
|
||||||
|
"""Service for managing a Discord server with Auth"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ServicesHook.__init__(self)
|
ServicesHook.__init__(self)
|
||||||
self.urlpatterns = urlpatterns
|
self.urlpatterns = urlpatterns
|
||||||
@@ -20,36 +29,105 @@ class DiscordService(ServicesHook):
|
|||||||
self.access_perm = 'discord.access_discord'
|
self.access_perm = 'discord.access_discord'
|
||||||
self.name_format = '{character_name}'
|
self.name_format = '{character_name}'
|
||||||
|
|
||||||
def delete_user(self, user, notify_user=False):
|
def delete_user(self, user: User, notify_user: bool = False) -> None:
|
||||||
logger.debug('Deleting user %s %s account' % (user, self.name))
|
if self.user_has_account(user):
|
||||||
return DiscordTasks.delete_user(user, notify_user=notify_user)
|
logger.debug('Deleting user %s %s account', user, self.name)
|
||||||
|
tasks.delete_user.apply_async(
|
||||||
|
kwargs={'user_pk': user.pk, 'notify_user': notify_user},
|
||||||
|
priority=SINGLE_TASK_PRIORITY
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_services_ctrl(self, request):
|
||||||
|
if self.user_has_account(request.user):
|
||||||
|
user_has_account = True
|
||||||
|
username = request.user.discord.username
|
||||||
|
discriminator = request.user.discord.discriminator
|
||||||
|
if username and discriminator:
|
||||||
|
discord_username = f'{username}#{discriminator}'
|
||||||
|
else:
|
||||||
|
discord_username = ''
|
||||||
|
else:
|
||||||
|
discord_username = ''
|
||||||
|
user_has_account = False
|
||||||
|
|
||||||
def update_groups(self, user):
|
return render_to_string(
|
||||||
logger.debug('Processing %s groups for %s' % (self.name, user))
|
self.service_ctrl_template,
|
||||||
if DiscordTasks.has_account(user):
|
{
|
||||||
DiscordTasks.update_groups.delay(user.pk)
|
'server_name': DiscordUser.objects.server_name(),
|
||||||
|
'user_has_account': user_has_account,
|
||||||
def validate_user(self, user):
|
'discord_username': discord_username
|
||||||
logger.debug('Validating user %s %s account' % (user, self.name))
|
},
|
||||||
if DiscordTasks.has_account(user) and not self.service_active_for_user(user):
|
request=request
|
||||||
self.delete_user(user, notify_user=True)
|
)
|
||||||
|
|
||||||
def sync_nickname(self, user):
|
|
||||||
logger.debug('Syncing %s nickname for user %s' % (self.name, user))
|
|
||||||
DiscordTasks.update_nickname.delay(user.pk)
|
|
||||||
|
|
||||||
def update_all_groups(self):
|
|
||||||
logger.debug('Update all %s groups called' % self.name)
|
|
||||||
DiscordTasks.update_all_groups.delay()
|
|
||||||
|
|
||||||
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 render_services_ctrl(self, request):
|
def sync_nickname(self, user):
|
||||||
return render_to_string(self.service_ctrl_template, {
|
logger.debug('Syncing %s nickname for user %s', self.name, user)
|
||||||
'discord_uid': request.user.discord.uid if DiscordTasks.has_account(request.user) else None,
|
if self.user_has_account(user):
|
||||||
'DISCORD_SERVER_ID': getattr(settings, 'DISCORD_GUILD_ID', ''),
|
tasks.update_nickname.apply_async(
|
||||||
}, request=request)
|
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):
|
||||||
|
"""Sync nickname for a list of users in bulk.
|
||||||
|
Preferred over sync_nickname(), because it will not break the rate limit
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
'Syncing %s nicknames in bulk for %d users', self.name, len(users)
|
||||||
|
)
|
||||||
|
user_pks = [user.pk for user in users]
|
||||||
|
tasks.update_nicknames_bulk.delay(user_pks)
|
||||||
|
|
||||||
|
def update_all_groups(self):
|
||||||
|
logger.debug('Update all %s groups called', self.name)
|
||||||
|
tasks.update_all_groups.delay()
|
||||||
|
|
||||||
|
def update_groups(self, user):
|
||||||
|
logger.debug('Processing %s groups for %s', self.name, user)
|
||||||
|
if self.user_has_account(user):
|
||||||
|
tasks.update_groups.apply_async(
|
||||||
|
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):
|
||||||
|
"""Updates groups for a list of users in bulk.
|
||||||
|
Preferred over update_groups(), because it will not break the rate limit
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
'Processing %s groups in bulk for %d users', self.name, len(users)
|
||||||
|
)
|
||||||
|
user_pks = [user.pk for user in users]
|
||||||
|
tasks.update_groups_bulk.delay(user_pks)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def user_has_account(user: User) -> bool:
|
||||||
|
result = DiscordUser.objects.user_has_account(user)
|
||||||
|
if result:
|
||||||
|
logger.debug('User %s has a Discord account', user)
|
||||||
|
else:
|
||||||
|
logger.debug('User %s does not have a Discord account', user)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def validate_user(self, user):
|
||||||
|
logger.debug('Validating user %s %s account', user, self.name)
|
||||||
|
if self.user_has_account(user) and not self.service_active_for_user(user):
|
||||||
|
self.delete_user(user, notify_user=True)
|
||||||
|
|
||||||
|
|
||||||
@hooks.register('services_hook')
|
@hooks.register('services_hook')
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .client import DiscordClient # noqa
|
||||||
|
from .exceptions import DiscordApiBackoff # noqa
|
||||||
|
from .helpers import DiscordRoles # noqa
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from ..utils import clean_setting
|
||||||
|
|
||||||
|
|
||||||
|
# Base URL for all API calls. Must end with /.
|
||||||
|
DISCORD_API_BASE_URL = clean_setting(
|
||||||
|
'DISCORD_API_BASE_URL', 'https://discord.com/api/'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Low level connecttimeout for requests to the Discord API in seconds
|
||||||
|
DISCORD_API_TIMEOUT_CONNECT = clean_setting(
|
||||||
|
'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
|
||||||
|
DISCORD_OAUTH_BASE_URL = clean_setting(
|
||||||
|
'DISCORD_OAUTH_BASE_URL', 'https://discord.com/api/oauth2/authorize'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Base authorization URL for Discord Oauth
|
||||||
|
DISCORD_OAUTH_TOKEN_URL = clean_setting(
|
||||||
|
'DISCORD_OAUTH_TOKEN_URL', 'https://discord.com/api/oauth2/token'
|
||||||
|
)
|
||||||
|
|
||||||
|
# How long the Discord guild names retrieved from the server are
|
||||||
|
# caches locally in seconds.
|
||||||
|
DISCORD_GUILD_NAME_CACHE_MAX_AGE = clean_setting(
|
||||||
|
'DISCORD_GUILD_NAME_CACHE_MAX_AGE', 3600 * 24
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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', 3600 * 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Turns off creation of new roles. In case the rate limit for creating roles is
|
||||||
|
# exhausted, this setting allows the Discord service to continue to function
|
||||||
|
# and wait out the reset. Rate limit is about 250 per 48 hrs.
|
||||||
|
DISCORD_DISABLE_ROLE_CREATION = clean_setting(
|
||||||
|
'DISCORD_DISABLE_ROLE_CREATION', False
|
||||||
|
)
|
||||||
725
allianceauth/services/modules/discord/discord_client/client.py
Normal file
725
allianceauth/services/modules/discord/discord_client/client.py
Normal file
@@ -0,0 +1,725 @@
|
|||||||
|
from hashlib import md5
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
from uuid import uuid1
|
||||||
|
|
||||||
|
from redis import Redis
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.core.cache import caches
|
||||||
|
|
||||||
|
from allianceauth import __title__ as AUTH_TITLE, __url__, __version__
|
||||||
|
|
||||||
|
from .. import __title__
|
||||||
|
from .app_settings import (
|
||||||
|
DISCORD_API_BASE_URL,
|
||||||
|
DISCORD_API_TIMEOUT_CONNECT,
|
||||||
|
DISCORD_API_TIMEOUT_READ,
|
||||||
|
DISCORD_DISABLE_ROLE_CREATION,
|
||||||
|
DISCORD_GUILD_NAME_CACHE_MAX_AGE,
|
||||||
|
DISCORD_OAUTH_BASE_URL,
|
||||||
|
DISCORD_OAUTH_TOKEN_URL,
|
||||||
|
DISCORD_ROLES_CACHE_MAX_AGE,
|
||||||
|
)
|
||||||
|
from .exceptions import DiscordRateLimitExhausted, DiscordTooManyRequestsError
|
||||||
|
from .helpers import DiscordRoles
|
||||||
|
from ..utils import LoggerAddTag
|
||||||
|
|
||||||
|
|
||||||
|
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
||||||
|
|
||||||
|
# max requests that can be executed until reset
|
||||||
|
RATE_LIMIT_MAX_REQUESTS = 5
|
||||||
|
|
||||||
|
# Time until remaining requests are reset
|
||||||
|
RATE_LIMIT_RESETS_AFTER = 5000
|
||||||
|
|
||||||
|
# Delay used for API backoff in case no info returned from API on 429s
|
||||||
|
DEFAULT_BACKOFF_DELAY = 5000
|
||||||
|
|
||||||
|
# additional duration to compensate for potential clock discrepancies
|
||||||
|
# with the Discord server
|
||||||
|
DURATION_CONTINGENCY = 500
|
||||||
|
|
||||||
|
# Client will do a blocking wait rather than throwing a backoff exception if the
|
||||||
|
# time until next reset is below this threshold
|
||||||
|
WAIT_THRESHOLD = 250
|
||||||
|
|
||||||
|
# Minimum wait duration when doing a blocking wait
|
||||||
|
MINIMUM_BLOCKING_WAIT = 50
|
||||||
|
|
||||||
|
# If the rate limit resets soon we will wait it out and then retry to
|
||||||
|
# either get a remaining request from our cached counter
|
||||||
|
# or again wait out a short reset time and retry again.
|
||||||
|
# This could happen several times within a high concurrency situation,
|
||||||
|
# but must fail after x tries to avoid an infinite loop
|
||||||
|
RATE_LIMIT_RETRIES = 1000
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordClient:
|
||||||
|
"""This class provides a web client for interacting with the Discord API
|
||||||
|
|
||||||
|
The client has rate limiting that supports concurrency.
|
||||||
|
This means it is able to ensure the API rate limit is not violated,
|
||||||
|
even when used concurrently, e.g. with multiple parallel celery tasks.
|
||||||
|
|
||||||
|
In addition the client support proper API backoff.
|
||||||
|
|
||||||
|
Synchronization of rate limit infos accross multiple processes
|
||||||
|
is implemented with Redis and thus requires Redis as Django cache backend.
|
||||||
|
|
||||||
|
All durations are in milliseconds.
|
||||||
|
"""
|
||||||
|
OAUTH_BASE_URL = DISCORD_OAUTH_BASE_URL
|
||||||
|
OAUTH_TOKEN_URL = DISCORD_OAUTH_TOKEN_URL
|
||||||
|
|
||||||
|
_KEY_GLOBAL_BACKOFF_UNTIL = 'DISCORD_GLOBAL_BACKOFF_UNTIL'
|
||||||
|
_KEY_GLOBAL_RATE_LIMIT_REMAINING = 'DISCORD_GLOBAL_RATE_LIMIT_REMAINING'
|
||||||
|
_KEYPREFIX_GUILD_NAME = 'DISCORD_GUILD_NAME'
|
||||||
|
_KEYPREFIX_GUILD_ROLES = 'DISCORD_GUILD_ROLES'
|
||||||
|
_KEYPREFIX_ROLE_NAME = 'DISCORD_ROLE_NAME'
|
||||||
|
_NICK_MAX_CHARS = 32
|
||||||
|
|
||||||
|
_HTTP_STATUS_CODE_NOT_FOUND = 404
|
||||||
|
_HTTP_STATUS_CODE_RATE_LIMITED = 429
|
||||||
|
_DISCORD_STATUS_CODE_UNKNOWN_MEMBER = 10007
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
access_token: str,
|
||||||
|
redis: Redis = None,
|
||||||
|
is_rate_limited: bool = True
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Params:
|
||||||
|
- access_token: Discord access token used to authenticate all calls to the API
|
||||||
|
- redis: Redis instance to be used.
|
||||||
|
- is_rate_limited: Set to False to run of rate limiting (use with care)
|
||||||
|
If not specified will try to use the Redis instance
|
||||||
|
from the default Django cache backend.
|
||||||
|
"""
|
||||||
|
self._access_token = str(access_token)
|
||||||
|
self._is_rate_limited = bool(is_rate_limited)
|
||||||
|
if not redis:
|
||||||
|
default_cache = caches['default']
|
||||||
|
self._redis = default_cache.get_master_client()
|
||||||
|
if not isinstance(self._redis, Redis):
|
||||||
|
raise RuntimeError(
|
||||||
|
'This class requires a Redis client, but none was provided '
|
||||||
|
'and the default Django cache backend is not Redis either.'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._redis = redis
|
||||||
|
|
||||||
|
lua_1 = """
|
||||||
|
if redis.call("exists", KEYS[1]) == 0 then
|
||||||
|
redis.call("set", KEYS[1], ARGV[1], 'px', ARGV[2])
|
||||||
|
end
|
||||||
|
return redis.call("decr", KEYS[1])
|
||||||
|
"""
|
||||||
|
self.__redis_script_decr_or_set = self._redis.register_script(lua_1)
|
||||||
|
|
||||||
|
lua_2 = """
|
||||||
|
local current_px = tonumber(redis.call("pttl", KEYS[1]))
|
||||||
|
if current_px < tonumber(ARGV[2]) then
|
||||||
|
return redis.call("set", KEYS[1], ARGV[1], 'px', ARGV[2])
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
self.__redis_script_set_longer = self._redis.register_script(lua_2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def access_token(self):
|
||||||
|
return self._access_token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_rate_limited(self):
|
||||||
|
return self._is_rate_limited
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'{type(self).__name__}(access_token=...{self.access_token[-5:]})'
|
||||||
|
|
||||||
|
def _redis_decr_or_set(self, name: str, value: str, px: int) -> bool:
|
||||||
|
"""decreases the key value if it exists and returns the result
|
||||||
|
else sets the key
|
||||||
|
|
||||||
|
Implemented as Lua script to ensure atomicity.
|
||||||
|
"""
|
||||||
|
return self.__redis_script_decr_or_set(
|
||||||
|
keys=[str(name)], args=[str(value), int(px)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def _redis_set_if_longer(self, name: str, value: str, px: int) -> bool:
|
||||||
|
"""like set, but only goes through if either key doesn't exist
|
||||||
|
or px would be extended.
|
||||||
|
|
||||||
|
Implemented as Lua script to ensure atomicity.
|
||||||
|
"""
|
||||||
|
return self.__redis_script_set_longer(
|
||||||
|
keys=[str(name)], args=[str(value), int(px)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# users
|
||||||
|
|
||||||
|
def current_user(self) -> dict:
|
||||||
|
"""returns the user belonging to the current access_token"""
|
||||||
|
authorization = f'Bearer {self.access_token}'
|
||||||
|
r = self._api_request(
|
||||||
|
method='get', route='users/@me', authorization=authorization
|
||||||
|
)
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
# guild
|
||||||
|
|
||||||
|
def guild_infos(self, guild_id: int) -> dict:
|
||||||
|
"""Returns all basic infos about this guild"""
|
||||||
|
route = f"guilds/{guild_id}"
|
||||||
|
r = self._api_request(method='get', route=route)
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def guild_name(self, guild_id: int, use_cache: bool = True) -> str:
|
||||||
|
"""returns the name of this guild (cached)
|
||||||
|
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)
|
||||||
|
if use_cache:
|
||||||
|
guild_name = self._redis_decode(self._redis.get(key_name))
|
||||||
|
else:
|
||||||
|
guild_name = None
|
||||||
|
if not guild_name:
|
||||||
|
guild_infos = self.guild_infos(guild_id)
|
||||||
|
if 'name' in guild_infos:
|
||||||
|
guild_name = guild_infos['name']
|
||||||
|
self._redis.set(
|
||||||
|
name=key_name,
|
||||||
|
value=guild_name,
|
||||||
|
ex=DISCORD_GUILD_NAME_CACHE_MAX_AGE
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
guild_name = ''
|
||||||
|
|
||||||
|
return guild_name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _guild_name_cache_key(cls, guild_id: int) -> str:
|
||||||
|
"""Returns key for accessing role given by name in the role cache"""
|
||||||
|
gen_key = DiscordClient._generate_hash(f'{guild_id}')
|
||||||
|
return f'{cls._KEYPREFIX_GUILD_NAME}__{gen_key}'
|
||||||
|
|
||||||
|
# guild roles
|
||||||
|
|
||||||
|
def guild_roles(self, guild_id: int, use_cache: bool = True) -> list:
|
||||||
|
"""Returns the list of all roles for this guild
|
||||||
|
|
||||||
|
If use_cache is set to False it will always hit the API to retrieve
|
||||||
|
fresh data and update the cache
|
||||||
|
"""
|
||||||
|
cache_key = self._guild_roles_cache_key(guild_id)
|
||||||
|
if use_cache:
|
||||||
|
roles_raw = self._redis.get(name=cache_key)
|
||||||
|
if roles_raw:
|
||||||
|
logger.debug('Returning roles for guild %s from cache', guild_id)
|
||||||
|
return json.loads(self._redis_decode(roles_raw))
|
||||||
|
else:
|
||||||
|
logger.debug('No roles for guild %s in cache', guild_id)
|
||||||
|
|
||||||
|
route = f"guilds/{guild_id}/roles"
|
||||||
|
r = self._api_request(method='get', route=route)
|
||||||
|
roles = r.json()
|
||||||
|
if roles and isinstance(roles, list):
|
||||||
|
self._redis.set(
|
||||||
|
name=cache_key,
|
||||||
|
value=json.dumps(roles),
|
||||||
|
ex=DISCORD_ROLES_CACHE_MAX_AGE
|
||||||
|
)
|
||||||
|
return roles
|
||||||
|
|
||||||
|
def create_guild_role(self, guild_id: int, role_name: str, **kwargs) -> dict:
|
||||||
|
"""Create a new guild role with the given name.
|
||||||
|
See official documentation for additional optional parameters.
|
||||||
|
|
||||||
|
Note that Discord allows the creation of multiple roles with the same name,
|
||||||
|
so to avoid duplicates it's important to check existing roles
|
||||||
|
before creating new one
|
||||||
|
|
||||||
|
returns a new role dict on success
|
||||||
|
"""
|
||||||
|
route = f"guilds/{guild_id}/roles"
|
||||||
|
data = {'name': DiscordRoles.sanitize_role_name(role_name)}
|
||||||
|
data.update(kwargs)
|
||||||
|
r = self._api_request(method='post', route=route, data=data)
|
||||||
|
role = r.json()
|
||||||
|
if role:
|
||||||
|
self._invalidate_guild_roles_cache(guild_id)
|
||||||
|
return role
|
||||||
|
|
||||||
|
def delete_guild_role(self, guild_id: int, role_id: int) -> bool:
|
||||||
|
"""Deletes a guild role"""
|
||||||
|
route = f"guilds/{guild_id}/roles/{role_id}"
|
||||||
|
r = self._api_request(method='delete', route=route)
|
||||||
|
if r.status_code == 204:
|
||||||
|
self._invalidate_guild_roles_cache(guild_id)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _invalidate_guild_roles_cache(self, guild_id: int) -> None:
|
||||||
|
cache_key = self._guild_roles_cache_key(guild_id)
|
||||||
|
self._redis.delete(cache_key)
|
||||||
|
logger.debug('Guild roles cache invalidated')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _guild_roles_cache_key(cls, guild_id: int) -> str:
|
||||||
|
"""Returns key for accessing cached roles for a guild"""
|
||||||
|
gen_key = cls._generate_hash(f'{guild_id}')
|
||||||
|
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:
|
||||||
|
"""returns Discord roles matching the given names
|
||||||
|
|
||||||
|
Returns as list of tuple of role and created flag
|
||||||
|
|
||||||
|
Will try to match with existing roles names
|
||||||
|
Non-existing roles will be created, then created flag will be True
|
||||||
|
|
||||||
|
Params:
|
||||||
|
- guild_id: ID of guild
|
||||||
|
- role_names: list of name strings each defining a role
|
||||||
|
"""
|
||||||
|
roles = list()
|
||||||
|
guild_roles = DiscordRoles(self.guild_roles(guild_id))
|
||||||
|
role_names_cleaned = {
|
||||||
|
DiscordRoles.sanitize_role_name(name) for name in role_names
|
||||||
|
}
|
||||||
|
for role_name in role_names_cleaned:
|
||||||
|
role, created = self.match_or_create_role_from_name(
|
||||||
|
guild_id=guild_id,
|
||||||
|
role_name=DiscordRoles.sanitize_role_name(role_name),
|
||||||
|
guild_roles=guild_roles
|
||||||
|
)
|
||||||
|
if role:
|
||||||
|
roles.append((role, created))
|
||||||
|
if created:
|
||||||
|
guild_roles = guild_roles.union(DiscordRoles([role]))
|
||||||
|
return roles
|
||||||
|
|
||||||
|
def match_or_create_role_from_name(
|
||||||
|
self, guild_id: int, role_name: str, guild_roles: DiscordRoles = None
|
||||||
|
) -> tuple:
|
||||||
|
"""returns Discord role matching the given name
|
||||||
|
|
||||||
|
Returns as tuple of role and created flag
|
||||||
|
|
||||||
|
Will try to match with existing roles names
|
||||||
|
Non-existing roles will be created, then created flag will be True
|
||||||
|
|
||||||
|
Params:
|
||||||
|
- guild_id: ID of guild
|
||||||
|
- role_name: strings defining name of a role
|
||||||
|
- guild_roles: All known guild roles as DiscordRoles object.
|
||||||
|
Helps to void redundant lookups of guild roles
|
||||||
|
when this method is used multiple times.
|
||||||
|
"""
|
||||||
|
if not isinstance(role_name, str):
|
||||||
|
raise TypeError('role_name must be of type string')
|
||||||
|
|
||||||
|
created = False
|
||||||
|
if guild_roles is None:
|
||||||
|
guild_roles = DiscordRoles(self.guild_roles(guild_id))
|
||||||
|
role = guild_roles.role_by_name(role_name)
|
||||||
|
if not role:
|
||||||
|
if not DISCORD_DISABLE_ROLE_CREATION:
|
||||||
|
logger.debug('Need to create missing role: %s', role_name)
|
||||||
|
role = self.create_guild_role(guild_id, role_name)
|
||||||
|
created = True
|
||||||
|
else:
|
||||||
|
role = None
|
||||||
|
|
||||||
|
return role, created
|
||||||
|
|
||||||
|
# guild members
|
||||||
|
|
||||||
|
def add_guild_member(
|
||||||
|
self,
|
||||||
|
guild_id: int,
|
||||||
|
user_id: int,
|
||||||
|
access_token: str,
|
||||||
|
role_ids: list = None,
|
||||||
|
nick: str = None
|
||||||
|
) -> bool:
|
||||||
|
"""Adds a user to the guilds.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True when a new user was added
|
||||||
|
- None if the user already existed
|
||||||
|
- False when something went wrong or raises exception
|
||||||
|
"""
|
||||||
|
route = f"guilds/{guild_id}/members/{user_id}"
|
||||||
|
data = {
|
||||||
|
'access_token': str(access_token)
|
||||||
|
}
|
||||||
|
if role_ids:
|
||||||
|
data['roles'] = self._sanitize_role_ids(role_ids)
|
||||||
|
|
||||||
|
if nick:
|
||||||
|
data['nick'] = str(nick)[:self._NICK_MAX_CHARS]
|
||||||
|
|
||||||
|
r = self._api_request(method='put', route=route, data=data)
|
||||||
|
r.raise_for_status()
|
||||||
|
if r.status_code == 201:
|
||||||
|
return True
|
||||||
|
elif r.status_code == 204:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def guild_member(self, guild_id: int, user_id: int) -> dict:
|
||||||
|
"""returns the user info for a guild member
|
||||||
|
|
||||||
|
or None if the user is not a member of the guild
|
||||||
|
"""
|
||||||
|
route = f'guilds/{guild_id}/members/{user_id}'
|
||||||
|
r = self._api_request(method='get', route=route, raise_for_status=False)
|
||||||
|
if self._is_member_unknown_error(r):
|
||||||
|
logger.warning("Discord user ID %s could not be found on server.", user_id)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def modify_guild_member(
|
||||||
|
self, guild_id: int, user_id: int, role_ids: list = None, nick: str = None
|
||||||
|
) -> bool:
|
||||||
|
"""Modify attributes of a guild member.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
- True when successful
|
||||||
|
- None if user is not a member of this guild
|
||||||
|
- False otherwise
|
||||||
|
"""
|
||||||
|
if not role_ids and not nick:
|
||||||
|
raise ValueError('Must specify role_ids or nick')
|
||||||
|
|
||||||
|
if role_ids and not isinstance(role_ids, list):
|
||||||
|
raise TypeError('role_ids must be a list type')
|
||||||
|
|
||||||
|
data = dict()
|
||||||
|
if role_ids:
|
||||||
|
data['roles'] = self._sanitize_role_ids(role_ids)
|
||||||
|
|
||||||
|
if nick:
|
||||||
|
data['nick'] = self._sanitize_nick(nick)
|
||||||
|
|
||||||
|
route = f"guilds/{guild_id}/members/{user_id}"
|
||||||
|
r = self._api_request(
|
||||||
|
method='patch', route=route, data=data, raise_for_status=False
|
||||||
|
)
|
||||||
|
if self._is_member_unknown_error(r):
|
||||||
|
logger.warning('User ID %s is not a member of this guild', user_id)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
if r.status_code == 204:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def remove_guild_member(self, guild_id: int, user_id: int) -> bool:
|
||||||
|
"""Remove a member from a guild
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True when successful
|
||||||
|
- None if member does not exist
|
||||||
|
- False otherwise
|
||||||
|
"""
|
||||||
|
route = f"guilds/{guild_id}/members/{user_id}"
|
||||||
|
r = self._api_request(
|
||||||
|
method='delete', route=route, raise_for_status=False
|
||||||
|
)
|
||||||
|
if self._is_member_unknown_error(r):
|
||||||
|
logger.warning('User ID %s is not a member of this guild', user_id)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
if r.status_code == 204:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Guild member roles
|
||||||
|
|
||||||
|
def add_guild_member_role(
|
||||||
|
self, guild_id: int, user_id: int, role_id: int
|
||||||
|
) -> bool:
|
||||||
|
"""Adds a role to a guild member
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True when successful
|
||||||
|
- None if member does not exist
|
||||||
|
- False otherwise
|
||||||
|
"""
|
||||||
|
route = f"guilds/{guild_id}/members/{user_id}/roles/{role_id}"
|
||||||
|
r = self._api_request(method='put', route=route, raise_for_status=False)
|
||||||
|
if self._is_member_unknown_error(r):
|
||||||
|
logger.warning('User ID %s is not a member of this guild', user_id)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
if r.status_code == 204:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def remove_guild_member_role(
|
||||||
|
self, guild_id: int, user_id: int, role_id: int
|
||||||
|
) -> bool:
|
||||||
|
"""Removes a role to a guild member
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True when successful
|
||||||
|
- None if member does not exist
|
||||||
|
- False otherwise
|
||||||
|
"""
|
||||||
|
route = f"guilds/{guild_id}/members/{user_id}/roles/{role_id}"
|
||||||
|
r = self._api_request(method='delete', route=route, raise_for_status=False)
|
||||||
|
if self._is_member_unknown_error(r):
|
||||||
|
logger.warning('User ID %s is not a member of this guild', user_id)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
if r.status_code == 204:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _is_member_unknown_error(cls, r: requests.Response) -> bool:
|
||||||
|
try:
|
||||||
|
result = (
|
||||||
|
r.status_code == cls._HTTP_STATUS_CODE_NOT_FOUND
|
||||||
|
and r.json()['code'] == cls._DISCORD_STATUS_CODE_UNKNOWN_MEMBER
|
||||||
|
)
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
result = False
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Internal methods
|
||||||
|
|
||||||
|
def _api_request(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
route: str,
|
||||||
|
data: dict = None,
|
||||||
|
authorization: str = None,
|
||||||
|
raise_for_status: bool = True
|
||||||
|
) -> requests.Response:
|
||||||
|
"""Core method for performing all API calls"""
|
||||||
|
uid = uuid1().hex
|
||||||
|
|
||||||
|
if not hasattr(requests, method):
|
||||||
|
raise ValueError('Invalid method: %s' % method)
|
||||||
|
|
||||||
|
if not authorization:
|
||||||
|
authorization = f'Bot {self.access_token}'
|
||||||
|
|
||||||
|
self._handle_ongoing_api_backoff(uid)
|
||||||
|
if self.is_rate_limited:
|
||||||
|
self._ensure_rate_limed_not_exhausted(uid)
|
||||||
|
headers = {
|
||||||
|
'User-Agent': f'{AUTH_TITLE} ({__url__}, {__version__})',
|
||||||
|
'accept': 'application/json',
|
||||||
|
'X-RateLimit-Precision': 'millisecond',
|
||||||
|
'authorization': str(authorization)
|
||||||
|
}
|
||||||
|
if data:
|
||||||
|
headers['content-type'] = 'application/json'
|
||||||
|
|
||||||
|
url = urljoin(DISCORD_API_BASE_URL, route)
|
||||||
|
args = {
|
||||||
|
'url': url,
|
||||||
|
'headers': headers,
|
||||||
|
'timeout': (DISCORD_API_TIMEOUT_CONNECT, DISCORD_API_TIMEOUT_READ)
|
||||||
|
}
|
||||||
|
if data:
|
||||||
|
args['json'] = data
|
||||||
|
|
||||||
|
logger.info('%s: sending %s request to url \'%s\'', uid, method.upper(), url)
|
||||||
|
logger.debug('%s: request headers: %s', uid, headers)
|
||||||
|
r = getattr(requests, method)(**args)
|
||||||
|
logger.debug(
|
||||||
|
'%s: returned status code %d with headers: %s',
|
||||||
|
uid,
|
||||||
|
r.status_code,
|
||||||
|
r.headers
|
||||||
|
)
|
||||||
|
logger.debug('%s: response:\n%s', uid, r.text)
|
||||||
|
if not r.ok:
|
||||||
|
logger.warning(
|
||||||
|
'%s: Discord API returned error code %d and this response: %s',
|
||||||
|
uid,
|
||||||
|
r.status_code,
|
||||||
|
r.text
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code == self._HTTP_STATUS_CODE_RATE_LIMITED:
|
||||||
|
self._handle_new_api_backoff(r, uid)
|
||||||
|
|
||||||
|
self._report_rate_limit_from_api(r, uid)
|
||||||
|
|
||||||
|
if raise_for_status:
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def _handle_ongoing_api_backoff(self, uid: str) -> None:
|
||||||
|
"""checks if api is currently on backoff
|
||||||
|
if on backoff: will do a blocking wait if it expires soon,
|
||||||
|
else raises exception
|
||||||
|
"""
|
||||||
|
global_backoff_duration = self._redis.pttl(self._KEY_GLOBAL_BACKOFF_UNTIL)
|
||||||
|
if global_backoff_duration > 0:
|
||||||
|
if global_backoff_duration < WAIT_THRESHOLD:
|
||||||
|
logger.info(
|
||||||
|
'%s: Global API backoff still ongoing for %s ms. Waiting.',
|
||||||
|
uid,
|
||||||
|
global_backoff_duration
|
||||||
|
)
|
||||||
|
sleep(global_backoff_duration / 1000)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
'%s: Global API backoff still ongoing for %s ms. Re-raising.',
|
||||||
|
uid,
|
||||||
|
global_backoff_duration
|
||||||
|
)
|
||||||
|
raise DiscordTooManyRequestsError(retry_after=global_backoff_duration)
|
||||||
|
|
||||||
|
def _ensure_rate_limed_not_exhausted(self, uid: str) -> int:
|
||||||
|
"""ensures that the rate limit is not exhausted
|
||||||
|
if exhausted: will do a blocking wait if rate limit resets soon,
|
||||||
|
else raises exception
|
||||||
|
|
||||||
|
returns requests remaining on success
|
||||||
|
"""
|
||||||
|
for _ in range(RATE_LIMIT_RETRIES):
|
||||||
|
requests_remaining = self._redis_decr_or_set(
|
||||||
|
name=self._KEY_GLOBAL_RATE_LIMIT_REMAINING,
|
||||||
|
value=RATE_LIMIT_MAX_REQUESTS,
|
||||||
|
px=RATE_LIMIT_RESETS_AFTER + DURATION_CONTINGENCY
|
||||||
|
)
|
||||||
|
resets_in = max(
|
||||||
|
MINIMUM_BLOCKING_WAIT,
|
||||||
|
self._redis.pttl(self._KEY_GLOBAL_RATE_LIMIT_REMAINING)
|
||||||
|
)
|
||||||
|
if requests_remaining >= 0:
|
||||||
|
logger.debug(
|
||||||
|
'%s: Got one of %d remaining requests until reset in %s ms',
|
||||||
|
uid,
|
||||||
|
requests_remaining + 1,
|
||||||
|
resets_in
|
||||||
|
)
|
||||||
|
return requests_remaining
|
||||||
|
|
||||||
|
elif resets_in < WAIT_THRESHOLD:
|
||||||
|
sleep(resets_in / 1000)
|
||||||
|
logger.debug(
|
||||||
|
'%s: No requests remaining until reset in %d ms. '
|
||||||
|
'Waiting for reset.',
|
||||||
|
uid,
|
||||||
|
resets_in
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
'%s: No requests remaining until reset in %d ms. '
|
||||||
|
'Raising exception.',
|
||||||
|
uid,
|
||||||
|
resets_in
|
||||||
|
)
|
||||||
|
raise DiscordRateLimitExhausted(resets_in)
|
||||||
|
|
||||||
|
raise RuntimeError('Failed to handle rate limit after after too tries.')
|
||||||
|
|
||||||
|
def _handle_new_api_backoff(self, r: requests.Response, uid: str) -> None:
|
||||||
|
"""raises exception for new API backoff error"""
|
||||||
|
response = r.json()
|
||||||
|
if 'retry_after' in response:
|
||||||
|
try:
|
||||||
|
retry_after = \
|
||||||
|
int(response['retry_after']) + DURATION_CONTINGENCY
|
||||||
|
except ValueError:
|
||||||
|
retry_after = DEFAULT_BACKOFF_DELAY
|
||||||
|
else:
|
||||||
|
retry_after = DEFAULT_BACKOFF_DELAY
|
||||||
|
self._redis_set_if_longer(
|
||||||
|
name=self._KEY_GLOBAL_BACKOFF_UNTIL,
|
||||||
|
value='GLOBAL_API_BACKOFF',
|
||||||
|
px=retry_after
|
||||||
|
)
|
||||||
|
logger.warning(
|
||||||
|
"%s: Rate limit violated. Need to back off for at least %d ms",
|
||||||
|
uid,
|
||||||
|
retry_after
|
||||||
|
)
|
||||||
|
raise DiscordTooManyRequestsError(retry_after=retry_after)
|
||||||
|
|
||||||
|
def _report_rate_limit_from_api(self, r, uid):
|
||||||
|
"""Tries to log the current rate limit reported from API"""
|
||||||
|
if (
|
||||||
|
logger.getEffectiveLevel() <= logging.DEBUG
|
||||||
|
and 'x-ratelimit-limit' in r.headers
|
||||||
|
and 'x-ratelimit-remaining' in r.headers
|
||||||
|
and 'x-ratelimit-reset-after' in r.headers
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
limit = int(r.headers['x-ratelimit-limit'])
|
||||||
|
remaining = int(r.headers['x-ratelimit-remaining'])
|
||||||
|
reset_after = float(r.headers['x-ratelimit-reset-after']) * 1000
|
||||||
|
if remaining + 1 == limit:
|
||||||
|
logger.debug(
|
||||||
|
'%s: Rate limit reported from API: %d requests per %s ms',
|
||||||
|
uid,
|
||||||
|
limit,
|
||||||
|
reset_after
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _redis_decode(value: str) -> str:
|
||||||
|
"""Decodes a string from Redis and passes through None and Booleans"""
|
||||||
|
if value is not None and not isinstance(value, bool):
|
||||||
|
return value.decode('utf-8')
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_hash(key: str) -> str:
|
||||||
|
return md5(key.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sanitize_role_ids(role_ids: list) -> list:
|
||||||
|
"""make sure its a list of integers"""
|
||||||
|
return [int(role_id) for role_id in list(role_ids)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _sanitize_nick(cls, nick: str) -> str:
|
||||||
|
"""shortens too long strings if necessary"""
|
||||||
|
return str(nick)[:cls._NICK_MAX_CHARS]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordClientException(Exception):
|
||||||
|
"""Base Exception for the Discord client"""
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordApiBackoff(DiscordClientException):
|
||||||
|
"""Exception signaling we need to backoff from sending requests to the API for now
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, retry_after: int):
|
||||||
|
"""
|
||||||
|
:param retry_after: int time to retry after in milliseconds
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.retry_after = int(retry_after)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def retry_after_seconds(self):
|
||||||
|
return math.ceil(self.retry_after / 1000)
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordRateLimitExhausted(DiscordApiBackoff):
|
||||||
|
"""Exception signaling that the total number of requests allowed under the
|
||||||
|
current rate limit have been exhausted and weed to wait until next reset.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordTooManyRequestsError(DiscordApiBackoff):
|
||||||
|
"""API has responded with a 429 Too Many Requests Error.
|
||||||
|
Need to backoff for now.
|
||||||
|
"""
|
||||||
132
allianceauth/services/modules/discord/discord_client/helpers.py
Normal file
132
allianceauth/services/modules/discord/discord_client/helpers.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from copy import copy
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordRoles:
|
||||||
|
"""Container class that helps dealing with Discord roles.
|
||||||
|
|
||||||
|
Objects of this class are immutable and work in many ways like sets.
|
||||||
|
|
||||||
|
Ideally objects are initialized from raw API responses,
|
||||||
|
e.g. from DiscordClient.guild.roles()
|
||||||
|
"""
|
||||||
|
_ROLE_NAME_MAX_CHARS = 100
|
||||||
|
|
||||||
|
def __init__(self, roles_lst: list) -> None:
|
||||||
|
"""roles_lst must be a list of dict, each defining a role"""
|
||||||
|
if not isinstance(roles_lst, (list, set, tuple)):
|
||||||
|
raise TypeError('roles_lst must be of type list, set or tuple')
|
||||||
|
self._roles = dict()
|
||||||
|
self._roles_by_name = dict()
|
||||||
|
for role in list(roles_lst):
|
||||||
|
self._assert_valid_role(role)
|
||||||
|
self._roles[int(role['id'])] = role
|
||||||
|
self._roles_by_name[self.sanitize_role_name(role['name'])] = role
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, type(self)):
|
||||||
|
return self.ids() == other.ids()
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(tuple(sorted(self._roles.keys())))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for role in self._roles.values():
|
||||||
|
yield role
|
||||||
|
|
||||||
|
def __contains__(self, item) -> bool:
|
||||||
|
return int(item) in self._roles
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._roles.keys())
|
||||||
|
|
||||||
|
def has_roles(self, role_ids: set) -> bool:
|
||||||
|
"""returns true if this objects contains all roles defined by given role_ids
|
||||||
|
incl. managed roles
|
||||||
|
"""
|
||||||
|
role_ids = {int(id) for id in role_ids}
|
||||||
|
all_role_ids = self._roles.keys()
|
||||||
|
return role_ids.issubset(all_role_ids)
|
||||||
|
|
||||||
|
def ids(self) -> set:
|
||||||
|
"""return a set of all role IDs"""
|
||||||
|
return set(self._roles.keys())
|
||||||
|
|
||||||
|
def subset(self, role_ids: set = None, managed_only: bool = False) -> object:
|
||||||
|
"""returns a new object containing the subset of roles as defined
|
||||||
|
by given role IDs and/or including managed roles only
|
||||||
|
"""
|
||||||
|
if role_ids is not None:
|
||||||
|
role_ids = {int(id) for id in role_ids}
|
||||||
|
|
||||||
|
if role_ids is not None and not managed_only:
|
||||||
|
return type(self)([
|
||||||
|
role for role_id, role in self._roles.items() if role_id in role_ids
|
||||||
|
])
|
||||||
|
|
||||||
|
elif role_ids is None and managed_only:
|
||||||
|
return type(self)([
|
||||||
|
role for _, role in self._roles.items() if role['managed']
|
||||||
|
])
|
||||||
|
|
||||||
|
elif role_ids is not None and managed_only:
|
||||||
|
return type(self)([
|
||||||
|
role for role_id, role in self._roles.items()
|
||||||
|
if role_id in role_ids and role['managed']
|
||||||
|
])
|
||||||
|
|
||||||
|
else:
|
||||||
|
return copy(self)
|
||||||
|
|
||||||
|
def union(self, other: object) -> object:
|
||||||
|
"""returns a new roles object that is the union of this roles object
|
||||||
|
with other"""
|
||||||
|
return type(self)(list(self) + list(other))
|
||||||
|
|
||||||
|
def difference(self, other: object) -> object:
|
||||||
|
"""returns a new roles object that only contains the roles
|
||||||
|
that exist in the current objects, but not in other
|
||||||
|
"""
|
||||||
|
new_ids = self.ids().difference(other.ids())
|
||||||
|
return self.subset(role_ids=new_ids)
|
||||||
|
|
||||||
|
def role_by_name(self, role_name: str) -> dict:
|
||||||
|
"""returns role if one with matching name is found else an empty dict"""
|
||||||
|
role_name = self.sanitize_role_name(role_name)
|
||||||
|
if role_name in self._roles_by_name:
|
||||||
|
return self._roles_by_name[role_name]
|
||||||
|
else:
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_matched_roles(cls, matched_roles: list) -> None:
|
||||||
|
"""returns a new object created from the given list of matches roles
|
||||||
|
|
||||||
|
matches_roles must be a list of tuples in the form: (role, created)
|
||||||
|
"""
|
||||||
|
raw_roles = [x[0] for x in matched_roles]
|
||||||
|
return cls(raw_roles)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _assert_valid_role(role: dict):
|
||||||
|
if not isinstance(role, dict):
|
||||||
|
raise TypeError('Roles must be of type dict: %s' % role)
|
||||||
|
|
||||||
|
if 'id' not in role or 'name' not in role or 'managed' not in role:
|
||||||
|
raise ValueError('This role is not valid: %s' % role)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sanitize_role_name(cls, role_name: str) -> str:
|
||||||
|
"""shortens too long strings if necessary"""
|
||||||
|
return str(role_name)[:cls._ROLE_NAME_MAX_CHARS]
|
||||||
|
|
||||||
|
|
||||||
|
def match_or_create_roles_from_names(
|
||||||
|
client: object, guild_id: int, role_names: list
|
||||||
|
) -> DiscordRoles:
|
||||||
|
"""Shortcut for getting the result of matching role names as DiscordRoles object"""
|
||||||
|
return DiscordRoles.create_from_matched_roles(
|
||||||
|
client.match_or_create_roles_from_names(
|
||||||
|
guild_id=guild_id, role_names=role_names
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
TEST_GUILD_ID = 123456789012345678
|
||||||
|
TEST_USER_ID = 198765432012345678
|
||||||
|
TEST_USER_NAME = 'Peter Parker'
|
||||||
|
TEST_USER_DISCRIMINATOR = '1234'
|
||||||
|
TEST_BOT_TOKEN = 'abcdefhijlkmnopqastzvwxyz1234567890ABCDEFGHOJKLMNOPQRSTUVWXY'
|
||||||
|
TEST_ROLE_ID = 654321012345678912
|
||||||
|
|
||||||
|
|
||||||
|
def create_role(id: int, name: str, managed=False):
|
||||||
|
return {
|
||||||
|
'id': int(id),
|
||||||
|
'name': str(name),
|
||||||
|
'managed': bool(managed)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_matched_role(role, created=False) -> tuple:
|
||||||
|
return role, created
|
||||||
|
|
||||||
|
|
||||||
|
ROLE_ALPHA = create_role(1, 'alpha')
|
||||||
|
ROLE_BRAVO = create_role(2, 'bravo')
|
||||||
|
ROLE_CHARLIE = create_role(3, 'charlie')
|
||||||
|
ROLE_MIKE = create_role(13, 'mike', True)
|
||||||
|
|
||||||
|
ALL_ROLES = [ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE]
|
||||||
|
|
||||||
|
|
||||||
|
def create_user_info(
|
||||||
|
id: int = TEST_USER_ID,
|
||||||
|
username: str = TEST_USER_NAME,
|
||||||
|
discriminator: str = TEST_USER_DISCRIMINATOR
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
'id': str(id),
|
||||||
|
'username': str(username[:32]),
|
||||||
|
'discriminator': str(discriminator[:4])
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""This is script is for concurrency testing the Discord client with a Discord server.
|
||||||
|
|
||||||
|
It will run multiple requests against Discord with multiple workers in parallel.
|
||||||
|
The results can be analysed in a special log file.
|
||||||
|
|
||||||
|
This script is design to be run manually as unit test, e.g. by running the following:
|
||||||
|
|
||||||
|
python manage.py test
|
||||||
|
allianceauth.services.modules.discord.discord_client.tests.piloting_concurrency
|
||||||
|
|
||||||
|
To make it work please set the below mentioned environment variables for your server.
|
||||||
|
Since this may cause lots of 429s we'd recommend NOT to use your
|
||||||
|
alliance Discord server for this.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from random import random
|
||||||
|
import threading
|
||||||
|
from time import sleep
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from .. import DiscordClient, DiscordApiBackoff
|
||||||
|
|
||||||
|
from ...utils import set_logger_to_file
|
||||||
|
|
||||||
|
logger = set_logger_to_file(
|
||||||
|
'allianceauth.services.modules.discord.discord_client.client', __file__
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure to set these environnement variables for your Discord server and user
|
||||||
|
DISCORD_GUILD_ID = os.environ['DISCORD_GUILD_ID']
|
||||||
|
DISCORD_BOT_TOKEN = os.environ['DISCORD_BOT_TOKEN']
|
||||||
|
DISCORD_USER_ID = os.environ['DISCORD_USER_ID']
|
||||||
|
NICK = 'Dummy'
|
||||||
|
|
||||||
|
# Configure these settings to adjust the load profile
|
||||||
|
NUMBER_OF_WORKERS = 5
|
||||||
|
NUMBER_OF_RUNS = 10
|
||||||
|
|
||||||
|
# max seconds a worker waits before starting a new run
|
||||||
|
# set to near 0 for max load preassure
|
||||||
|
MAX_JITTER_PER_RUN_SECS = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def worker(num: int):
|
||||||
|
"""worker function"""
|
||||||
|
worker_info = 'worker %d' % num
|
||||||
|
logger.info('%s: started', worker_info)
|
||||||
|
client = DiscordClient(DISCORD_BOT_TOKEN)
|
||||||
|
try:
|
||||||
|
runs = 0
|
||||||
|
while runs < NUMBER_OF_RUNS:
|
||||||
|
run_info = '%s: run %d' % (worker_info, runs + 1)
|
||||||
|
my_jitter_secs = random() * MAX_JITTER_PER_RUN_SECS
|
||||||
|
logger.info('%s - waiting %s secs', run_info, f'{my_jitter_secs:.3f}')
|
||||||
|
sleep(my_jitter_secs)
|
||||||
|
logger.info('%s - started', run_info)
|
||||||
|
try:
|
||||||
|
client.modify_guild_member(
|
||||||
|
DISCORD_GUILD_ID, DISCORD_USER_ID, nick=NICK
|
||||||
|
)
|
||||||
|
runs += 1
|
||||||
|
except DiscordApiBackoff as bo:
|
||||||
|
message = '%s - waiting out API backoff for %d ms' % (
|
||||||
|
run_info, bo.retry_after
|
||||||
|
)
|
||||||
|
logger.info(message)
|
||||||
|
print()
|
||||||
|
print(message)
|
||||||
|
sleep(bo.retry_after / 1000)
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
logger.exception('%s: Processing aborted: %s', worker_info, ex)
|
||||||
|
|
||||||
|
logger.info('%s: finished', worker_info)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class TestMulti(TestCase):
|
||||||
|
|
||||||
|
def test_multi(self):
|
||||||
|
logger.info('Starting multi test')
|
||||||
|
for num in range(NUMBER_OF_WORKERS):
|
||||||
|
x = threading.Thread(target=worker, args=(num + 1,))
|
||||||
|
x.start()
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
"""This script is for functional testing of the Discord client with a Discord server
|
||||||
|
|
||||||
|
It will run single requests of the various functions to validate
|
||||||
|
that they actually work - excluding those that require Oauth, or does not work
|
||||||
|
with a bot token. The results can be also seen in a special log file.
|
||||||
|
|
||||||
|
This script is design to be run manually as unit test, e.g. by running the following:
|
||||||
|
|
||||||
|
python manage.py test
|
||||||
|
allianceauth.services.modules.discord.discord_self.client.tests.piloting_functionality
|
||||||
|
|
||||||
|
To make it work please set the below mentioned environment variables for your server.
|
||||||
|
Since this may cause lots of 429s we'd recommend NOT to use your
|
||||||
|
alliance Discord server for this.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from uuid import uuid1
|
||||||
|
import os
|
||||||
|
from unittest import TestCase
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from .. import DiscordClient
|
||||||
|
from ...utils import set_logger_to_file
|
||||||
|
|
||||||
|
logger = set_logger_to_file(
|
||||||
|
'allianceauth.services.modules.discord.discord_self.client.client', __file__
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure to set these environnement variables for your Discord server and user
|
||||||
|
DISCORD_GUILD_ID = os.environ['DISCORD_GUILD_ID']
|
||||||
|
DISCORD_BOT_TOKEN = os.environ['DISCORD_BOT_TOKEN']
|
||||||
|
DISCORD_USER_ID = os.environ['DISCORD_USER_ID']
|
||||||
|
|
||||||
|
RATE_LIMIT_DELAY_SECS = 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestDiscordApiLive(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
logger.info('Live demo of the Discord API Client')
|
||||||
|
cls.client = DiscordClient(DISCORD_BOT_TOKEN)
|
||||||
|
|
||||||
|
def test_run_other_features(self):
|
||||||
|
"""runs features that have not been run in any of the other tests"""
|
||||||
|
self.client.guild_infos(DISCORD_GUILD_ID)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
self.client.guild_name(DISCORD_GUILD_ID)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
self.client.match_or_create_role_from_name(DISCORD_GUILD_ID, 'Testrole')
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
self.client.match_or_create_roles_from_names(
|
||||||
|
DISCORD_GUILD_ID, ['Testrole A', 'Testrole B']
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
def test_create_and_remove_roles(self):
|
||||||
|
# get base
|
||||||
|
logger.info('guild_roles')
|
||||||
|
expected = {role['id'] for role in self.client.guild_roles(DISCORD_GUILD_ID)}
|
||||||
|
|
||||||
|
# add role
|
||||||
|
role_name = 'my test role 12345678'
|
||||||
|
logger.info('create_guild_role')
|
||||||
|
new_role = self.client.create_guild_role(
|
||||||
|
guild_id=DISCORD_GUILD_ID, role_name=role_name
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
self.assertEqual(new_role['name'], role_name)
|
||||||
|
|
||||||
|
# remove role again
|
||||||
|
logger.info('delete_guild_role')
|
||||||
|
self.client.delete_guild_role(
|
||||||
|
guild_id=DISCORD_GUILD_ID, role_id=new_role['id']
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
# verify it worked
|
||||||
|
logger.info('guild_roles')
|
||||||
|
role_ids = {role['id'] for role in self.client.guild_roles(DISCORD_GUILD_ID)}
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
self.assertSetEqual(role_ids, expected)
|
||||||
|
|
||||||
|
def test_change_member_nick(self):
|
||||||
|
# set new nick for user
|
||||||
|
logger.info('modify_guild_member')
|
||||||
|
new_nick = f'Testnick {uuid1().hex}'[:32]
|
||||||
|
self.assertTrue(
|
||||||
|
self.client.modify_guild_member(
|
||||||
|
guild_id=DISCORD_GUILD_ID, user_id=DISCORD_USER_ID, nick=new_nick
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
# verify it is saved
|
||||||
|
logger.info('guild_member')
|
||||||
|
user = self.client.guild_member(DISCORD_GUILD_ID, DISCORD_USER_ID)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
self.assertEqual(user['nick'], new_nick)
|
||||||
|
|
||||||
|
def test_member_add_remove_roles(self):
|
||||||
|
# create new guild role
|
||||||
|
logger.info('create_guild_role')
|
||||||
|
new_role = self.client.create_guild_role(
|
||||||
|
guild_id=DISCORD_GUILD_ID, role_name='Special role 98765'
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
new_role_id = new_role['id']
|
||||||
|
|
||||||
|
# add to member
|
||||||
|
logger.info('add_guild_member_role')
|
||||||
|
self.assertTrue(
|
||||||
|
self.client.add_guild_member_role(
|
||||||
|
guild_id=DISCORD_GUILD_ID, user_id=DISCORD_USER_ID, role_id=new_role_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
|
|
||||||
|
# remove again
|
||||||
|
logger.info('remove_guild_member_role')
|
||||||
|
self.assertTrue(
|
||||||
|
self.client.remove_guild_member_role(
|
||||||
|
guild_id=DISCORD_GUILD_ID, user_id=DISCORD_USER_ID, role_id=new_role_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sleep(RATE_LIMIT_DELAY_SECS)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Discord rate limits
|
||||||
|
|
||||||
|
The following table shows the rate limit as reported from the API for different routes.
|
||||||
|
|
||||||
|
method | limit | reset | rate / s | bucket
|
||||||
|
-- | -- | -- | -- | --
|
||||||
|
add_guild_member | 10 | 10,000 | 1 | self
|
||||||
|
create_guild_role | 250 | 180,000,000 | 0.001 | self
|
||||||
|
delete_guild_role | g | g | g | g
|
||||||
|
guild_member | 5 | 1,000 | 5 | self
|
||||||
|
guild_roles | g | g | g | g
|
||||||
|
add_guild_member_role | 10 | 10,000 | 1 | B1
|
||||||
|
remove_guild_member_role | 10 | 10,000 | 1 | B1
|
||||||
|
modify_guild_member | 10 | 10,000 | 1 | self
|
||||||
|
remove_guild_member | 5 | 1,000 | 5 | self
|
||||||
|
current_user | g | g | g | g
|
||||||
|
|
||||||
|
Legend:
|
||||||
|
|
||||||
|
- g: global rate limit. API does not provide any rate limit infos for those routes.
|
||||||
|
|
||||||
|
- reset: Values in milliseconds.
|
||||||
|
|
||||||
|
- bucket: "self" means the rate limit is only counted for that route, Bx means the same rate limit is counted for multiple routes.
|
||||||
|
|
||||||
|
- Data was collected on 2020-MAY-07 and is subject to change.
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user