mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-05 22:56:20 +01:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ce1939040 | ||
|
|
322131cd4f | ||
|
|
55e6e92da5 | ||
|
|
e5d29629a5 | ||
|
|
26e187e4c8 | ||
|
|
3480c4e0e8 | ||
|
|
1544f097e0 | ||
|
|
2477c31656 | ||
|
|
0dc631d69e | ||
|
|
2a9981cdb9 | ||
|
|
424246df26 | ||
|
|
563e2210ef | ||
|
|
02a1078005 | ||
|
|
30107de44e | ||
|
|
77a08cd218 | ||
|
|
e5a09027e5 | ||
|
|
52b6c5d341 | ||
|
|
8b895b76b5 | ||
|
|
babd71702f | ||
|
|
3ec3cbdff7 | ||
|
|
51611e1237 | ||
|
|
39519bab91 | ||
|
|
90dc6a4d4c | ||
|
|
53ffd7f885 | ||
|
|
efc7475228 | ||
|
|
380c41400b | ||
|
|
079c12a72e | ||
|
|
4f1ebedc44 | ||
|
|
66822107e3 | ||
|
|
7856cd5ce4 |
@@ -1,5 +1,5 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://app.transifex.com
|
||||||
lang_map = zh-Hans: zh_Hans
|
lang_map = zh-Hans: zh_Hans
|
||||||
|
|
||||||
[o:alliance-auth:p:alliance-auth:r:django-po]
|
[o:alliance-auth:p:alliance-auth:r:django-po]
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
lang_map = zh-Hans:zh_Hans
|
|
||||||
|
|
||||||
[alliance-auth.django-po]
|
|
||||||
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
|
||||||
minimum_perc = 0
|
|
||||||
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
|
|
||||||
source_lang = en
|
|
||||||
type = PO
|
|
||||||
10
.tx/transifex.yml
Normal file
10
.tx/transifex.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
filters:
|
||||||
|
- filter_type: file
|
||||||
|
file_format: PO
|
||||||
|
source_file: allianceauth/locale/en/LC_MESSAGES/django.po
|
||||||
|
source_language: en
|
||||||
|
translation_files_expression: allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
||||||
|
|
||||||
|
settings:
|
||||||
|
language_mapping:
|
||||||
|
zh-Hans: zh_Hans
|
||||||
@@ -5,7 +5,7 @@ manage online service access.
|
|||||||
# 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__ = '3.6.1'
|
__version__ = '3.8.0'
|
||||||
__title__ = 'Alliance Auth'
|
__title__ = 'Alliance Auth'
|
||||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||||
NAME = f'{__title__} v{__version__}'
|
NAME = f'{__title__} v{__version__}'
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class StateBackend(ModelBackend):
|
|||||||
# we've seen this character owner before. Re-attach to their old user account
|
# we've seen this character owner before. Re-attach to their old user account
|
||||||
user = records[0].user
|
user = records[0].user
|
||||||
if user.profile.main_character:
|
if user.profile.main_character:
|
||||||
if ownership.user.profile.main_character.character_id != token.character_id:
|
if user.profile.main_character.character_id != token.character_id:
|
||||||
## this is an alt, enforce main only due to trust issues in SSO.
|
## this is an alt, enforce main only due to trust issues in SSO.
|
||||||
if request:
|
if request:
|
||||||
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
|
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
|
||||||
|
|||||||
0
allianceauth/authentication/core/__init__.py
Normal file
0
allianceauth/authentication/core/__init__.py
Normal file
48
allianceauth/authentication/core/celery_workers.py
Normal file
48
allianceauth/authentication/core/celery_workers.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""API for interacting with celery workers."""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from amqp.exceptions import ChannelError
|
||||||
|
from celery import current_app
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def active_tasks_count() -> Optional[int]:
|
||||||
|
"""Return count of currently active tasks
|
||||||
|
or None if celery workers are not online.
|
||||||
|
"""
|
||||||
|
inspect = current_app.control.inspect()
|
||||||
|
return _tasks_count(inspect.active())
|
||||||
|
|
||||||
|
|
||||||
|
def _tasks_count(data: dict) -> Optional[int]:
|
||||||
|
"""Return count of tasks in data from celery inspect API."""
|
||||||
|
try:
|
||||||
|
tasks = itertools.chain(*data.values())
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
return len(list(tasks))
|
||||||
|
|
||||||
|
|
||||||
|
def queued_tasks_count() -> Optional[int]:
|
||||||
|
"""Return count of queued tasks. Return None if there was an error."""
|
||||||
|
try:
|
||||||
|
with current_app.connection_or_acquire() as conn:
|
||||||
|
result = conn.default_channel.queue_declare(
|
||||||
|
queue=getattr(settings, "CELERY_DEFAULT_QUEUE", "celery"), passive=True
|
||||||
|
)
|
||||||
|
return result.message_count
|
||||||
|
|
||||||
|
except ChannelError:
|
||||||
|
# Queue doesn't exist, probably empty
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to get celery queue length")
|
||||||
|
|
||||||
|
return None
|
||||||
@@ -4,13 +4,11 @@ import datetime as dt
|
|||||||
from typing import NamedTuple, Optional
|
from typing import NamedTuple, Optional
|
||||||
|
|
||||||
from .event_series import EventSeries
|
from .event_series import EventSeries
|
||||||
from .helpers import ItemCounter
|
|
||||||
|
|
||||||
# Global series for counting task events.
|
# Global series for counting task events.
|
||||||
succeeded_tasks = EventSeries("SUCCEEDED_TASKS")
|
succeeded_tasks = EventSeries("SUCCEEDED_TASKS")
|
||||||
retried_tasks = EventSeries("RETRIED_TASKS")
|
retried_tasks = EventSeries("RETRIED_TASKS")
|
||||||
failed_tasks = EventSeries("FAILED_TASKS")
|
failed_tasks = EventSeries("FAILED_TASKS")
|
||||||
running_tasks = ItemCounter("running_tasks")
|
|
||||||
|
|
||||||
|
|
||||||
class _TaskCounts(NamedTuple):
|
class _TaskCounts(NamedTuple):
|
||||||
@@ -20,7 +18,6 @@ class _TaskCounts(NamedTuple):
|
|||||||
total: int
|
total: int
|
||||||
earliest_task: Optional[dt.datetime]
|
earliest_task: Optional[dt.datetime]
|
||||||
hours: int
|
hours: int
|
||||||
running: int
|
|
||||||
|
|
||||||
|
|
||||||
def dashboard_results(hours: int) -> _TaskCounts:
|
def dashboard_results(hours: int) -> _TaskCounts:
|
||||||
@@ -38,7 +35,6 @@ def dashboard_results(hours: int) -> _TaskCounts:
|
|||||||
earliest_events += earliest_if_exists(retried_tasks, earliest)
|
earliest_events += earliest_if_exists(retried_tasks, earliest)
|
||||||
failed_count = failed_tasks.count(earliest=earliest)
|
failed_count = failed_tasks.count(earliest=earliest)
|
||||||
earliest_events += earliest_if_exists(failed_tasks, earliest)
|
earliest_events += earliest_if_exists(failed_tasks, earliest)
|
||||||
running_count = running_tasks.value()
|
|
||||||
return _TaskCounts(
|
return _TaskCounts(
|
||||||
succeeded=succeeded_count,
|
succeeded=succeeded_count,
|
||||||
retried=retried_count,
|
retried=retried_count,
|
||||||
@@ -46,5 +42,4 @@ def dashboard_results(hours: int) -> _TaskCounts:
|
|||||||
total=succeeded_count + retried_count + failed_count,
|
total=succeeded_count + retried_count + failed_count,
|
||||||
earliest_task=min(earliest_events) if earliest_events else None,
|
earliest_task=min(earliest_events) if earliest_events else None,
|
||||||
hours=hours,
|
hours=hours,
|
||||||
running=running_count,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
"""Helpers for Task Statistics."""
|
"""Helpers for Task Statistics."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from redis import Redis, RedisError
|
from redis import Redis, RedisError
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
from allianceauth.utils.cache import get_redis_client
|
from allianceauth.utils.cache import get_redis_client
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -37,62 +34,6 @@ class _RedisStub:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ItemCounter:
|
|
||||||
"""A process safe item counter.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
- name: Unique name for the counter
|
|
||||||
- minimum: Counter can not go below the minimum, when set
|
|
||||||
- redis: A Redis client. Will use AA's cache client by default
|
|
||||||
"""
|
|
||||||
|
|
||||||
CACHE_KEY_BASE = "allianceauth-item-counter"
|
|
||||||
DEFAULT_CACHE_TIMEOUT = 24 * 3600
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, name: str, minimum: Optional[int] = None, redis: Optional[Redis] = None
|
|
||||||
) -> None:
|
|
||||||
if not name:
|
|
||||||
raise ValueError("Must define a name")
|
|
||||||
|
|
||||||
self._name = str(name)
|
|
||||||
self._minimum = minimum
|
|
||||||
self._redis = get_redis_client_or_stub() if not redis else redis
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _cache_key(self) -> str:
|
|
||||||
return f"{self.CACHE_KEY_BASE}-{self._name}"
|
|
||||||
|
|
||||||
def reset(self, init_value: int = 0):
|
|
||||||
"""Reset counter to initial value."""
|
|
||||||
with self._redis.lock(f"{self.CACHE_KEY_BASE}-reset"):
|
|
||||||
if self._minimum is not None and init_value < self._minimum:
|
|
||||||
raise ValueError("Can not reset below minimum")
|
|
||||||
|
|
||||||
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
|
|
||||||
|
|
||||||
def incr(self, delta: int = 1):
|
|
||||||
"""Increment counter by delta."""
|
|
||||||
try:
|
|
||||||
cache.incr(self._cache_key, delta)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def decr(self, delta: int = 1):
|
|
||||||
"""Decrement counter by delta."""
|
|
||||||
with self._redis.lock(f"{self.CACHE_KEY_BASE}-decr"):
|
|
||||||
if self._minimum is not None and self.value() == self._minimum:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
cache.decr(self._cache_key, delta)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def value(self) -> Optional[int]:
|
|
||||||
"""Return current value or None if not yet initialized."""
|
|
||||||
return cache.get(self._cache_key)
|
|
||||||
|
|
||||||
|
|
||||||
def get_redis_client_or_stub() -> Redis:
|
def get_redis_client_or_stub() -> Redis:
|
||||||
"""Return AA's default cache client or a stub if Redis is not available."""
|
"""Return AA's default cache client or a stub if Redis is not available."""
|
||||||
redis = get_redis_client()
|
redis = get_redis_client()
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
"""Signals for Task Statistics."""
|
"""Signals for Task Statistics."""
|
||||||
|
|
||||||
from celery.signals import (
|
from celery.signals import (
|
||||||
task_failure, task_internal_error, task_postrun, task_prerun, task_retry,
|
task_failure, task_internal_error, task_retry, task_success, worker_ready,
|
||||||
task_success, worker_ready,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from .counters import (
|
from .counters import failed_tasks, retried_tasks, succeeded_tasks
|
||||||
failed_tasks, retried_tasks, running_tasks, succeeded_tasks,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def reset_counters():
|
def reset_counters():
|
||||||
@@ -17,7 +14,6 @@ def reset_counters():
|
|||||||
succeeded_tasks.clear()
|
succeeded_tasks.clear()
|
||||||
failed_tasks.clear()
|
failed_tasks.clear()
|
||||||
retried_tasks.clear()
|
retried_tasks.clear()
|
||||||
running_tasks.reset()
|
|
||||||
|
|
||||||
|
|
||||||
def is_enabled() -> bool:
|
def is_enabled() -> bool:
|
||||||
@@ -55,15 +51,3 @@ def record_task_failed(*args, **kwargs):
|
|||||||
def record_task_internal_error(*args, **kwargs):
|
def record_task_internal_error(*args, **kwargs):
|
||||||
if is_enabled():
|
if is_enabled():
|
||||||
failed_tasks.add()
|
failed_tasks.add()
|
||||||
|
|
||||||
|
|
||||||
@task_prerun.connect
|
|
||||||
def record_task_prerun(*args, **kwargs):
|
|
||||||
if is_enabled():
|
|
||||||
running_tasks.incr()
|
|
||||||
|
|
||||||
|
|
||||||
@task_postrun.connect
|
|
||||||
def record_task_postrun(*args, **kwargs):
|
|
||||||
if is_enabled():
|
|
||||||
running_tasks.decr()
|
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ from django.test import TestCase
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from allianceauth.authentication.task_statistics.counters import (
|
from allianceauth.authentication.task_statistics.counters import (
|
||||||
dashboard_results,
|
dashboard_results, failed_tasks, retried_tasks, succeeded_tasks,
|
||||||
succeeded_tasks,
|
|
||||||
retried_tasks,
|
|
||||||
failed_tasks,
|
|
||||||
running_tasks,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +28,6 @@ class TestDashboardResults(TestCase):
|
|||||||
failed_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
|
failed_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
|
||||||
failed_tasks.add()
|
failed_tasks.add()
|
||||||
|
|
||||||
running_tasks.reset(8)
|
|
||||||
# when
|
# when
|
||||||
results = dashboard_results(hours=1)
|
results = dashboard_results(hours=1)
|
||||||
# then
|
# then
|
||||||
@@ -41,14 +36,12 @@ class TestDashboardResults(TestCase):
|
|||||||
self.assertEqual(results.failed, 1)
|
self.assertEqual(results.failed, 1)
|
||||||
self.assertEqual(results.total, 6)
|
self.assertEqual(results.total, 6)
|
||||||
self.assertEqual(results.earliest_task, earliest_task)
|
self.assertEqual(results.earliest_task, earliest_task)
|
||||||
self.assertEqual(results.running, 8)
|
|
||||||
|
|
||||||
def test_should_work_with_no_data(self):
|
def test_should_work_with_no_data(self):
|
||||||
# given
|
# given
|
||||||
succeeded_tasks.clear()
|
succeeded_tasks.clear()
|
||||||
retried_tasks.clear()
|
retried_tasks.clear()
|
||||||
failed_tasks.clear()
|
failed_tasks.clear()
|
||||||
running_tasks.reset()
|
|
||||||
# when
|
# when
|
||||||
results = dashboard_results(hours=1)
|
results = dashboard_results(hours=1)
|
||||||
# then
|
# then
|
||||||
@@ -57,4 +50,3 @@ class TestDashboardResults(TestCase):
|
|||||||
self.assertEqual(results.failed, 0)
|
self.assertEqual(results.failed, 0)
|
||||||
self.assertEqual(results.total, 0)
|
self.assertEqual(results.total, 0)
|
||||||
self.assertIsNone(results.earliest_task)
|
self.assertIsNone(results.earliest_task)
|
||||||
self.assertEqual(results.running, 0)
|
|
||||||
|
|||||||
@@ -4,125 +4,11 @@ from unittest.mock import patch
|
|||||||
from redis import RedisError
|
from redis import RedisError
|
||||||
|
|
||||||
from allianceauth.authentication.task_statistics.helpers import (
|
from allianceauth.authentication.task_statistics.helpers import (
|
||||||
ItemCounter, _RedisStub, get_redis_client_or_stub,
|
_RedisStub, get_redis_client_or_stub,
|
||||||
)
|
)
|
||||||
|
|
||||||
MODULE_PATH = "allianceauth.authentication.task_statistics.helpers"
|
MODULE_PATH = "allianceauth.authentication.task_statistics.helpers"
|
||||||
|
|
||||||
COUNTER_NAME = "test-counter"
|
|
||||||
|
|
||||||
|
|
||||||
class TestItemCounter(TestCase):
|
|
||||||
def test_can_create_counter(self):
|
|
||||||
# when
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
# then
|
|
||||||
self.assertIsInstance(counter, ItemCounter)
|
|
||||||
|
|
||||||
def test_can_reset_counter_to_default(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
# when
|
|
||||||
counter.reset()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 0)
|
|
||||||
|
|
||||||
def test_can_reset_counter_to_custom_value(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
# when
|
|
||||||
counter.reset(42)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 42)
|
|
||||||
|
|
||||||
def test_can_increment_counter_by_default(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.incr()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 1)
|
|
||||||
|
|
||||||
def test_can_increment_counter_by_custom_value(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.incr(8)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 8)
|
|
||||||
|
|
||||||
def test_can_decrement_counter_by_default(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(9)
|
|
||||||
# when
|
|
||||||
counter.decr()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 8)
|
|
||||||
|
|
||||||
def test_can_decrement_counter_by_custom_value(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(9)
|
|
||||||
# when
|
|
||||||
counter.decr(8)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 1)
|
|
||||||
|
|
||||||
def test_can_decrement_counter_below_zero(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.decr(1)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), -1)
|
|
||||||
|
|
||||||
def test_can_not_decrement_counter_below_minimum(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME, minimum=0)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.decr(1)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 0)
|
|
||||||
|
|
||||||
def test_can_not_reset_counter_below_minimum(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME, minimum=0)
|
|
||||||
# when/then
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
counter.reset(-1)
|
|
||||||
|
|
||||||
def test_can_not_init_without_name(self):
|
|
||||||
# when/then
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
ItemCounter(name="")
|
|
||||||
|
|
||||||
def test_can_ignore_invalid_values_when_incrementing(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
with patch(MODULE_PATH + ".cache.incr") as m:
|
|
||||||
m.side_effect = ValueError
|
|
||||||
counter.incr()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 0)
|
|
||||||
|
|
||||||
def test_can_ignore_invalid_values_when_decrementing(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(1)
|
|
||||||
# when
|
|
||||||
with patch(MODULE_PATH + ".cache.decr") as m:
|
|
||||||
m.side_effect = ValueError
|
|
||||||
counter.decr()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetRedisClient(TestCase):
|
class TestGetRedisClient(TestCase):
|
||||||
def test_should_return_mock_if_redis_not_available_1(self):
|
def test_should_return_mock_if_redis_not_available_1(self):
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||||
{% get_language_info_list for LANGUAGES as languages %}
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
{% for language in languages %}
|
{% for language in languages %}
|
||||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
<option lang="{{ language.code }}" value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||||
{{ language.name_local|capfirst }} ({{ language.code }})
|
{{ language.name_local|capfirst }} ({{ language.code }})
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
0
allianceauth/authentication/tests/core/__init__.py
Normal file
0
allianceauth/authentication/tests/core/__init__.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from amqp.exceptions import ChannelError
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from allianceauth.authentication.core.celery_workers import (
|
||||||
|
active_tasks_count, queued_tasks_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
MODULE_PATH = "allianceauth.authentication.core.celery_workers"
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + ".current_app")
|
||||||
|
class TestActiveTasksCount(TestCase):
|
||||||
|
def test_should_return_correct_count_when_no_active_tasks(self, mock_current_app):
|
||||||
|
# given
|
||||||
|
mock_current_app.control.inspect.return_value.active.return_value = {
|
||||||
|
"queue": []
|
||||||
|
}
|
||||||
|
# when
|
||||||
|
result = active_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
|
||||||
|
def test_should_return_correct_task_count_for_active_tasks(self, mock_current_app):
|
||||||
|
# given
|
||||||
|
mock_current_app.control.inspect.return_value.active.return_value = {
|
||||||
|
"queue": [1, 2, 3]
|
||||||
|
}
|
||||||
|
# when
|
||||||
|
result = active_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertEqual(result, 3)
|
||||||
|
|
||||||
|
def test_should_return_correct_task_count_for_multiple_queues(
|
||||||
|
self, mock_current_app
|
||||||
|
):
|
||||||
|
# given
|
||||||
|
mock_current_app.control.inspect.return_value.active.return_value = {
|
||||||
|
"queue_1": [1, 2],
|
||||||
|
"queue_2": [3, 4],
|
||||||
|
}
|
||||||
|
# when
|
||||||
|
result = active_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertEqual(result, 4)
|
||||||
|
|
||||||
|
def test_should_return_none_when_celery_not_available(self, mock_current_app):
|
||||||
|
# given
|
||||||
|
mock_current_app.control.inspect.return_value.active.return_value = None
|
||||||
|
# when
|
||||||
|
result = active_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + ".current_app")
|
||||||
|
class TestQueuedTasksCount(TestCase):
|
||||||
|
def test_should_return_queue_length_when_queue_exists(self, mock_current_app):
|
||||||
|
# given
|
||||||
|
mock_conn = (
|
||||||
|
mock_current_app.connection_or_acquire.return_value.__enter__.return_value
|
||||||
|
)
|
||||||
|
mock_conn.default_channel.queue_declare.return_value.message_count = 7
|
||||||
|
# when
|
||||||
|
result = queued_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertEqual(result, 7)
|
||||||
|
|
||||||
|
def test_should_return_0_when_queue_does_not_exists(self, mock_current_app):
|
||||||
|
# given
|
||||||
|
mock_current_app.connection_or_acquire.side_effect = ChannelError
|
||||||
|
# when
|
||||||
|
result = queued_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
|
||||||
|
def test_should_return_None_on_other_errors(self, mock_current_app):
|
||||||
|
# given
|
||||||
|
mock_current_app.connection_or_acquire.side_effect = RuntimeError
|
||||||
|
# when
|
||||||
|
result = queued_tasks_count()
|
||||||
|
# then
|
||||||
|
self.assertIsNone(result)
|
||||||
@@ -9,12 +9,8 @@ from django.core.cache import cache
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from allianceauth.templatetags.admin_status import (
|
from allianceauth.templatetags.admin_status import (
|
||||||
status_overview,
|
_current_notifications, _current_version_summary, _fetch_list_from_gitlab,
|
||||||
_fetch_list_from_gitlab,
|
_fetch_notification_issues_from_gitlab, _latests_versions, status_overview,
|
||||||
_current_notifications,
|
|
||||||
_current_version_summary,
|
|
||||||
_fetch_notification_issues_from_gitlab,
|
|
||||||
_latests_versions
|
|
||||||
)
|
)
|
||||||
|
|
||||||
MODULE_PATH = 'allianceauth.templatetags'
|
MODULE_PATH = 'allianceauth.templatetags'
|
||||||
@@ -56,14 +52,10 @@ TEST_VERSION = '2.6.5'
|
|||||||
|
|
||||||
class TestStatusOverviewTag(TestCase):
|
class TestStatusOverviewTag(TestCase):
|
||||||
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
@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_version_summary')
|
||||||
@patch(MODULE_PATH + '.admin_status._current_notifications')
|
@patch(MODULE_PATH + '.admin_status._current_notifications')
|
||||||
def test_status_overview(
|
def test_status_overview(
|
||||||
self,
|
self, mock_current_notifications, mock_current_version_info
|
||||||
mock_current_notifications,
|
|
||||||
mock_current_version_info,
|
|
||||||
mock_fetch_celery_queue_length
|
|
||||||
):
|
):
|
||||||
# given
|
# given
|
||||||
notifications = {
|
notifications = {
|
||||||
@@ -82,7 +74,6 @@ class TestStatusOverviewTag(TestCase):
|
|||||||
'latest_beta_version': '2.4.4a1',
|
'latest_beta_version': '2.4.4a1',
|
||||||
}
|
}
|
||||||
mock_current_version_info.return_value = version_info
|
mock_current_version_info.return_value = version_info
|
||||||
mock_fetch_celery_queue_length.return_value = 3
|
|
||||||
# when
|
# when
|
||||||
result = status_overview()
|
result = status_overview()
|
||||||
# then
|
# then
|
||||||
@@ -96,7 +87,6 @@ class TestStatusOverviewTag(TestCase):
|
|||||||
self.assertEqual(result["latest_minor_version"], '2.4.0')
|
self.assertEqual(result["latest_minor_version"], '2.4.0')
|
||||||
self.assertEqual(result["latest_patch_version"], '2.4.5')
|
self.assertEqual(result["latest_patch_version"], '2.4.5')
|
||||||
self.assertEqual(result["latest_beta_version"], '2.4.4a1')
|
self.assertEqual(result["latest_beta_version"], '2.4.4a1')
|
||||||
self.assertEqual(result["task_queue_length"], 3)
|
|
||||||
|
|
||||||
|
|
||||||
class TestNotifications(TestCase):
|
class TestNotifications(TestCase):
|
||||||
|
|||||||
39
allianceauth/authentication/tests/test_views.py
Normal file
39
allianceauth/authentication/tests/test_views.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
|
from allianceauth.authentication.views import task_counts
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
MODULE_PATH = "allianceauth.authentication.views"
|
||||||
|
|
||||||
|
|
||||||
|
def jsonresponse_to_dict(response) -> dict:
|
||||||
|
return json.loads(response.content)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + ".queued_tasks_count")
|
||||||
|
@patch(MODULE_PATH + ".active_tasks_count")
|
||||||
|
class TestRunningTasksCount(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
super().setUpClass()
|
||||||
|
cls.factory = RequestFactory()
|
||||||
|
cls.user = AuthUtils.create_user("bruce_wayne")
|
||||||
|
|
||||||
|
def test_should_return_data(
|
||||||
|
self, mock_active_tasks_count, mock_queued_tasks_count
|
||||||
|
):
|
||||||
|
# given
|
||||||
|
mock_active_tasks_count.return_value = 2
|
||||||
|
mock_queued_tasks_count.return_value = 3
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = task_counts(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertDictEqual(
|
||||||
|
jsonresponse_to_dict(response), {"tasks_running": 2, "tasks_queued": 3}
|
||||||
|
)
|
||||||
@@ -38,4 +38,5 @@ urlpatterns = [
|
|||||||
name='token_refresh'
|
name='token_refresh'
|
||||||
),
|
),
|
||||||
path('dashboard/', views.dashboard, name='dashboard'),
|
path('dashboard/', views.dashboard, name='dashboard'),
|
||||||
|
path('task-counts/', views.task_counts, name='task_counts'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django_registration.backends.activation.views import (
|
||||||
|
REGISTRATION_SALT, ActivationView as BaseActivationView,
|
||||||
|
RegistrationView as BaseRegistrationView,
|
||||||
|
)
|
||||||
|
from django_registration.signals import user_registered
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import login, authenticate
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
|
||||||
from esi.decorators import token_required
|
from esi.decorators import token_required
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
|
||||||
from django_registration.backends.activation.views import (
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
RegistrationView as BaseRegistrationView,
|
|
||||||
ActivationView as BaseActivationView,
|
|
||||||
REGISTRATION_SALT
|
|
||||||
)
|
|
||||||
from django_registration.signals import user_registered
|
|
||||||
|
|
||||||
from .models import CharacterOwnership
|
from .core.celery_workers import active_tasks_count, queued_tasks_count
|
||||||
from .forms import RegistrationForm
|
from .forms import RegistrationForm
|
||||||
|
from .models import CharacterOwnership
|
||||||
|
|
||||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||||
_has_auto_groups = True
|
_has_auto_groups = True
|
||||||
@@ -61,6 +61,7 @@ def dashboard(request):
|
|||||||
}
|
}
|
||||||
return render(request, 'authentication/dashboard.html', context)
|
return render(request, 'authentication/dashboard.html', context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def token_management(request):
|
def token_management(request):
|
||||||
tokens = request.user.token_set.all()
|
tokens = request.user.token_set.all()
|
||||||
@@ -70,6 +71,7 @@ def token_management(request):
|
|||||||
}
|
}
|
||||||
return render(request, 'authentication/tokens.html', context)
|
return render(request, 'authentication/tokens.html', context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def token_delete(request, token_id=None):
|
def token_delete(request, token_id=None):
|
||||||
try:
|
try:
|
||||||
@@ -83,6 +85,7 @@ def token_delete(request, token_id=None):
|
|||||||
messages.warning(request, "Token does not exist")
|
messages.warning(request, "Token does not exist")
|
||||||
return redirect('authentication:token_management')
|
return redirect('authentication:token_management')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def token_refresh(request, token_id=None):
|
def token_refresh(request, token_id=None):
|
||||||
try:
|
try:
|
||||||
@@ -127,7 +130,7 @@ def main_character_change(request, token):
|
|||||||
def add_character(request, token):
|
def add_character(request, token):
|
||||||
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
|
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
|
||||||
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
|
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
|
||||||
messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name})))
|
messages.success(request, _('Added %(name)s to your account.' % ({'name': token.character_name})))
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
|
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
|
||||||
return redirect('authentication:dashboard')
|
return redirect('authentication:dashboard')
|
||||||
@@ -268,8 +271,11 @@ class ActivationView(BaseActivationView):
|
|||||||
|
|
||||||
def validate_key(self, activation_key):
|
def validate_key(self, activation_key):
|
||||||
try:
|
try:
|
||||||
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
|
dump = signing.loads(
|
||||||
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
|
activation_key,
|
||||||
|
salt=REGISTRATION_SALT,
|
||||||
|
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400
|
||||||
|
)
|
||||||
return dump
|
return dump
|
||||||
except signing.BadSignature:
|
except signing.BadSignature:
|
||||||
return None
|
return None
|
||||||
@@ -299,3 +305,12 @@ def activation_complete(request):
|
|||||||
def registration_closed(request):
|
def registration_closed(request):
|
||||||
messages.error(request, _('Registration of new accounts is not allowed at this time.'))
|
messages.error(request, _('Registration of new accounts is not allowed at this time.'))
|
||||||
return redirect('authentication:login')
|
return redirect('authentication:login')
|
||||||
|
|
||||||
|
|
||||||
|
def task_counts(request) -> JsonResponse:
|
||||||
|
"""Return task counts as JSON for an AJAX call."""
|
||||||
|
data = {
|
||||||
|
"tasks_running": active_tasks_count(),
|
||||||
|
"tasks_queued": queued_tasks_count()
|
||||||
|
}
|
||||||
|
return JsonResponse(data)
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
|
from django.contrib.auth.models import Group, User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db.models.functions import Lower
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@@ -8,6 +12,39 @@ from .models import ReservedGroupName
|
|||||||
|
|
||||||
|
|
||||||
class GroupAdminForm(forms.ModelForm):
|
class GroupAdminForm(forms.ModelForm):
|
||||||
|
users = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=User.objects.order_by(Lower('username')),
|
||||||
|
required=False,
|
||||||
|
widget=FilteredSelectMultiple(verbose_name=_("Users"), is_stacked=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if self.instance and self.instance.pk:
|
||||||
|
self.fields["users"].initial = self.instance.user_set.all()
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
group: Group = super().save(commit=False)
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
group.save()
|
||||||
|
|
||||||
|
users = self.cleaned_data["users"]
|
||||||
|
if group.pk:
|
||||||
|
self._save_m2m_and_users(group, users)
|
||||||
|
else:
|
||||||
|
self.save_m2m = functools.partial(
|
||||||
|
self._save_m2m_and_users, group=group, users=users
|
||||||
|
)
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
def _save_m2m_and_users(self, group, users):
|
||||||
|
"""Save m2m relations incl. users."""
|
||||||
|
group.user_set.set(users)
|
||||||
|
self._save_m2m()
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
my_name = self.cleaned_data['name']
|
my_name = self.cleaned_data['name']
|
||||||
if ReservedGroupName.objects.filter(name__iexact=my_name).exists():
|
if ReservedGroupName.objects.filter(name__iexact=my_name).exists():
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group, User
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -14,7 +13,7 @@ from allianceauth.notifications import notify
|
|||||||
class GroupRequest(models.Model):
|
class GroupRequest(models.Model):
|
||||||
"""Request from a user for joining or leaving a group."""
|
"""Request from a user for joining or leaving a group."""
|
||||||
|
|
||||||
leave_request = models.BooleanField(default=0)
|
leave_request = models.BooleanField(default=False)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ class RequestLog(models.Model):
|
|||||||
request_type = models.BooleanField(null=True)
|
request_type = models.BooleanField(null=True)
|
||||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
request_info = models.CharField(max_length=254)
|
request_info = models.CharField(max_length=254)
|
||||||
action = models.BooleanField(default=0)
|
action = models.BooleanField(default=False)
|
||||||
request_actor = models.ForeignKey(User, on_delete=models.CASCADE)
|
request_actor = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
date = models.DateTimeField(auto_now_add=True)
|
date = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if not auto_leave %}
|
{% if not show_leave_tab %}
|
||||||
<li>
|
<li>
|
||||||
<a data-toggle="tab" href="#leave">
|
<a data-toggle="tab" href="#leave">
|
||||||
{% translate "Leave Requests" %}
|
{% translate "Leave Requests" %}
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not auto_leave %}
|
{% if not show_leave_tab %}
|
||||||
<div id="leave" class="tab-pane">
|
<div id="leave" class="tab-pane">
|
||||||
{% if leaverequests %}
|
{% if leaverequests %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ from django.conf import settings
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import TestCase, RequestFactory, Client, override_settings
|
from django.test import Client, RequestFactory, TestCase, override_settings
|
||||||
|
|
||||||
from allianceauth.authentication.models import CharacterOwnership, State
|
from allianceauth.authentication.models import CharacterOwnership, State
|
||||||
from allianceauth.eveonline.models import (
|
from allianceauth.eveonline.models import (
|
||||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
EveAllianceInfo, EveCharacter, EveCorporationInfo,
|
||||||
)
|
)
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from . import get_admin_change_view_url
|
from ..admin import Group, GroupAdmin, HasLeaderFilter
|
||||||
from ..admin import HasLeaderFilter, GroupAdmin, Group
|
|
||||||
from ..models import ReservedGroupName
|
from ..models import ReservedGroupName
|
||||||
|
from . import get_admin_change_view_url
|
||||||
|
|
||||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||||
_has_auto_groups = True
|
_has_auto_groups = True
|
||||||
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
|
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
|
||||||
|
|
||||||
from ..admin import IsAutoGroupFilter
|
from ..admin import IsAutoGroupFilter
|
||||||
else:
|
else:
|
||||||
_has_auto_groups = False
|
_has_auto_groups = False
|
||||||
@@ -621,21 +621,16 @@ class TestGroupAdmin2(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
f"/admin/groupmanagement/group/{group.pk}/change/",
|
f"/admin/groupmanagement/group/{group.pk}/change/",
|
||||||
data={
|
data={
|
||||||
"name": f"{group.name}",
|
"name": group.name,
|
||||||
"authgroup-TOTAL_FORMS": "1",
|
"users": [user_member.pk, user_guest.pk],
|
||||||
"authgroup-INITIAL_FORMS": "1",
|
"authgroup-TOTAL_FORMS": 1,
|
||||||
"authgroup-MIN_NUM_FORMS": "0",
|
"authgroup-INITIAL_FORMS": 1,
|
||||||
"authgroup-MAX_NUM_FORMS": "1",
|
"authgroup-MIN_NUM_FORMS": 0,
|
||||||
"authgroup-0-description": "",
|
"authgroup-MAX_NUM_FORMS": 1,
|
||||||
"authgroup-0-states": f"{member_state.pk}",
|
"authgroup-0-states": member_state.pk,
|
||||||
"authgroup-0-internal": "on",
|
"authgroup-0-internal": "on",
|
||||||
"authgroup-0-hidden": "on",
|
"authgroup-0-hidden": "on",
|
||||||
"authgroup-0-group": f"{group.pk}",
|
"authgroup-0-group": group.pk,
|
||||||
"authgroup-__prefix__-description": "",
|
|
||||||
"authgroup-__prefix__-internal": "on",
|
|
||||||
"authgroup-__prefix__-hidden": "on",
|
|
||||||
"authgroup-__prefix__-group": f"{group.pk}",
|
|
||||||
"_save": "Save"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# then
|
# then
|
||||||
@@ -644,6 +639,85 @@ class TestGroupAdmin2(TestCase):
|
|||||||
self.assertIn(group, user_member.groups.all())
|
self.assertIn(group, user_member.groups.all())
|
||||||
self.assertNotIn(group, user_guest.groups.all())
|
self.assertNotIn(group, user_guest.groups.all())
|
||||||
|
|
||||||
|
def test_should_add_user_to_existing_group(self):
|
||||||
|
# given
|
||||||
|
user_bruce = AuthUtils.create_user("Bruce Wayne")
|
||||||
|
user_lex = AuthUtils.create_user("Lex Luthor")
|
||||||
|
group = Group.objects.create(name="dummy")
|
||||||
|
user_bruce.groups.add(group)
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
# when
|
||||||
|
response = self.client.post(
|
||||||
|
f"/admin/groupmanagement/group/{group.pk}/change/",
|
||||||
|
data={
|
||||||
|
"name": group.name,
|
||||||
|
"users": [user_bruce.pk, user_lex.pk],
|
||||||
|
"authgroup-TOTAL_FORMS": 1,
|
||||||
|
"authgroup-INITIAL_FORMS": 1,
|
||||||
|
"authgroup-MIN_NUM_FORMS": 0,
|
||||||
|
"authgroup-MAX_NUM_FORMS": 1,
|
||||||
|
"authgroup-0-internal": "on",
|
||||||
|
"authgroup-0-hidden": "on",
|
||||||
|
"authgroup-0-group": group.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/admin/groupmanagement/group/")
|
||||||
|
self.assertIn(group, user_bruce.groups.all())
|
||||||
|
self.assertIn(group, user_lex.groups.all())
|
||||||
|
|
||||||
|
def test_should_remove_user_from_existing_group(self):
|
||||||
|
# given
|
||||||
|
user_bruce = AuthUtils.create_user("Bruce Wayne")
|
||||||
|
user_lex = AuthUtils.create_user("Lex Luthor")
|
||||||
|
group = Group.objects.create(name="dummy")
|
||||||
|
user_bruce.groups.add(group)
|
||||||
|
user_lex.groups.add(group)
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
# when
|
||||||
|
response = self.client.post(
|
||||||
|
f"/admin/groupmanagement/group/{group.pk}/change/",
|
||||||
|
data={
|
||||||
|
"name": group.name,
|
||||||
|
"users": user_bruce.pk,
|
||||||
|
"authgroup-TOTAL_FORMS": 1,
|
||||||
|
"authgroup-INITIAL_FORMS": 1,
|
||||||
|
"authgroup-MIN_NUM_FORMS": 0,
|
||||||
|
"authgroup-MAX_NUM_FORMS": 1,
|
||||||
|
"authgroup-0-internal": "on",
|
||||||
|
"authgroup-0-hidden": "on",
|
||||||
|
"authgroup-0-group": group.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/admin/groupmanagement/group/")
|
||||||
|
self.assertIn(group, user_bruce.groups.all())
|
||||||
|
self.assertNotIn(group, user_lex.groups.all())
|
||||||
|
|
||||||
|
def test_should_include_user_when_creating_group(self):
|
||||||
|
# given
|
||||||
|
user_bruce = AuthUtils.create_user("Bruce Wayne")
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
# when
|
||||||
|
response = self.client.post(
|
||||||
|
"/admin/groupmanagement/group/add/",
|
||||||
|
data={
|
||||||
|
"name": "new group",
|
||||||
|
"users": user_bruce.pk,
|
||||||
|
"authgroup-TOTAL_FORMS": 1,
|
||||||
|
"authgroup-INITIAL_FORMS": 0,
|
||||||
|
"authgroup-MIN_NUM_FORMS": 0,
|
||||||
|
"authgroup-MAX_NUM_FORMS": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/admin/groupmanagement/group/")
|
||||||
|
group = Group.objects.get(name="new group")
|
||||||
|
self.assertIn(group, user_bruce.groups.all())
|
||||||
|
|
||||||
|
|
||||||
class TestReservedGroupNameAdmin(TestCase):
|
class TestReservedGroupNameAdmin(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.test import RequestFactory, TestCase, override_settings
|
from django.test import RequestFactory, TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from allianceauth.groupmanagement.models import Group, GroupRequest
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from .. import views
|
from .. import views
|
||||||
@@ -16,6 +17,7 @@ class TestViews(TestCase):
|
|||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.user = AuthUtils.create_user('Peter Parker')
|
self.user = AuthUtils.create_user('Peter Parker')
|
||||||
self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne')
|
self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne')
|
||||||
|
self.group = Group.objects.create(name="Example group")
|
||||||
|
|
||||||
# set permissions
|
# set permissions
|
||||||
AuthUtils.add_permission_to_user_by_name(
|
AuthUtils.add_permission_to_user_by_name(
|
||||||
@@ -83,3 +85,19 @@ class TestViews(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertNotIn('<a data-toggle="tab" href="#leave">', content)
|
self.assertNotIn('<a data-toggle="tab" href="#leave">', content)
|
||||||
self.assertNotIn('<div id="leave" class="tab-pane">', content)
|
self.assertNotIn('<div id="leave" class="tab-pane">', content)
|
||||||
|
|
||||||
|
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
|
||||||
|
def test_should_not_hide_leave_requests_tab_when_there_are_open_requests(self):
|
||||||
|
# given
|
||||||
|
request = self.factory.get(reverse('groupmanagement:management'))
|
||||||
|
request.user = self.user_with_manage_permission
|
||||||
|
GroupRequest.objects.create(user=self.user, group=self.group, leave_request=True)
|
||||||
|
|
||||||
|
# when
|
||||||
|
response = views.group_management(request)
|
||||||
|
|
||||||
|
# then
|
||||||
|
content = response_content_to_str(response)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertIn('<a data-toggle="tab" href="#leave">', content)
|
||||||
|
self.assertIn('<div id="leave" class="tab-pane">', content)
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import logging
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required, 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.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 get_object_or_404, redirect, render
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
@@ -16,7 +15,6 @@ from allianceauth.notifications import notify
|
|||||||
from .managers import GroupManager
|
from .managers import GroupManager
|
||||||
from .models import GroupRequest, RequestLog
|
from .models import GroupRequest, RequestLog
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -45,10 +43,15 @@ def group_management(request):
|
|||||||
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
|
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
|
||||||
request.user, len(acceptrequests), len(leaverequests)))
|
request.user, len(acceptrequests), len(leaverequests)))
|
||||||
|
|
||||||
|
show_leave_tab = (
|
||||||
|
getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False)
|
||||||
|
and not GroupRequest.objects.filter(leave_request=True).exists()
|
||||||
|
)
|
||||||
|
|
||||||
render_items = {
|
render_items = {
|
||||||
'acceptrequests': acceptrequests,
|
'acceptrequests': acceptrequests,
|
||||||
'leaverequests': leaverequests,
|
'leaverequests': leaverequests,
|
||||||
'auto_leave': getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False),
|
'show_leave_tab': show_leave_tab,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'groupmanagement/index.html', context=render_items)
|
return render(request, 'groupmanagement/index.html', context=render_items)
|
||||||
|
|||||||
Binary file not shown.
@@ -4,9 +4,9 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2020
|
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2023
|
||||||
# Joel Falknau <ozirascal@gmail.com>, 2021
|
# Joel Falknau <ozirascal@gmail.com>, 2023
|
||||||
# Peter Pfeufer, 2022
|
# Peter Pfeufer, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -14,8 +14,8 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: Peter Pfeufer, 2022\n"
|
"Last-Translator: Peter Pfeufer, 2023\n"
|
||||||
"Language-Team: German (https://app.transifex.com/alliance-auth/teams/107430/de/)\n"
|
"Language-Team: German (https://app.transifex.com/alliance-auth/teams/107430/de/)\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"
|
||||||
@@ -34,7 +34,8 @@ msgstr "Google Analytics V4"
|
|||||||
#: allianceauth/authentication/decorators.py:37
|
#: allianceauth/authentication/decorators.py:37
|
||||||
msgid "A main character is required to perform that action. Add one below."
|
msgid "A main character is required to perform that action. Add one below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Für diese Aktion wird ein Hauptcharacter benötigt. Bitte füge einen hinzu."
|
"Zur Ausführung dieser Aktion ist ein Hauptcharakter erforderlich. Füge unten"
|
||||||
|
" einen hinzu."
|
||||||
|
|
||||||
#: allianceauth/authentication/forms.py:12
|
#: allianceauth/authentication/forms.py:12
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@@ -131,7 +132,7 @@ msgstr "Hauptcharakter ändern"
|
|||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
||||||
msgid "Group Memberships"
|
msgid "Group Memberships"
|
||||||
msgstr "Gruppen"
|
msgstr "Gruppenmitgliedschaften"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:145
|
#: allianceauth/authentication/templates/authentication/dashboard.html:145
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:21
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:21
|
||||||
@@ -206,7 +207,7 @@ msgstr ""
|
|||||||
#: allianceauth/authentication/views.py:83
|
#: allianceauth/authentication/views.py:83
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Changed main character to %(char)s"
|
msgid "Changed main character to %(char)s"
|
||||||
msgstr "Haupcharakter geändert zu %(char)s"
|
msgstr "Haupcharakter zu %(char)s geändert"
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:92
|
#: allianceauth/authentication/views.py:92
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -233,13 +234,12 @@ 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 ""
|
||||||
"Bestätigungsmail gesendet. Bitte folge dem Link in der E-Mail zur "
|
"Bestätigungs-E-Mail gesendet. Bitte folge dem Link, um Deine E-Mail-Adresse "
|
||||||
"Bestätigung."
|
"zu bestätigen."
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:257
|
#: allianceauth/authentication/views.py:257
|
||||||
msgid "Confirmed your email address. Please login to continue."
|
msgid "Confirmed your email address. Please login to continue."
|
||||||
msgstr ""
|
msgstr "Deine E-Mail Adresse wurde bestätigt. Bitte einloggen zum Fortfahren."
|
||||||
"Deine E-Mail Adresse wurde bestätigt. Bitte log Dich ein um fortzufahren."
|
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:262
|
#: allianceauth/authentication/views.py:262
|
||||||
msgid "Registration of new accounts is not allowed at this time."
|
msgid "Registration of new accounts is not allowed at this time."
|
||||||
@@ -274,7 +274,7 @@ msgstr "Hauptcharaktere"
|
|||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:22
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:22
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:14
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:14
|
||||||
msgid "Members"
|
msgid "Members"
|
||||||
msgstr "Mitgliederzahl"
|
msgstr "Mitglieder"
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:35
|
#: allianceauth/corputils/templates/corputils/corpstats.html:35
|
||||||
msgid "Unregistered"
|
msgid "Unregistered"
|
||||||
@@ -282,7 +282,7 @@ msgstr "Nicht registriert"
|
|||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:38
|
#: allianceauth/corputils/templates/corputils/corpstats.html:38
|
||||||
msgid "Last update:"
|
msgid "Last update:"
|
||||||
msgstr "Letzes Update:"
|
msgstr "Letzte Aktualisierung:"
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:74
|
#: allianceauth/corputils/templates/corputils/corpstats.html:74
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:112
|
#: allianceauth/corputils/templates/corputils/corpstats.html:112
|
||||||
@@ -382,11 +382,11 @@ msgstr "Charakter nicht registriert!"
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
|
||||||
msgid "This character is not associated with an auth account."
|
msgid "This character is not associated with an auth account."
|
||||||
msgstr "Dieser Charakter ist mit keinen Auth Konto verbunden."
|
msgstr "Dieser Charakter ist keinem Auth Konto zugeordnet."
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
|
||||||
msgid "Add it here"
|
msgid "Add it here"
|
||||||
msgstr "Füge es hier hinzu"
|
msgstr "Füge ihn hier hinzu"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
|
||||||
msgid "before attempting to click fleet attendance links."
|
msgid "before attempting to click fleet attendance links."
|
||||||
@@ -394,7 +394,7 @@ msgstr "bevor Du versuchst auf FAT-Links zu klicken."
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:5
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:5
|
||||||
msgid "Create Fatlink"
|
msgid "Create Fatlink"
|
||||||
msgstr "Erstelle FAT-Link"
|
msgstr "FAT-Link erstellen"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:9
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:9
|
||||||
#: allianceauth/optimer/templates/optimer/add.html:13
|
#: allianceauth/optimer/templates/optimer/add.html:13
|
||||||
@@ -409,20 +409,20 @@ msgstr "Fehlerhafte Anfrage!"
|
|||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:24
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:24
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:63
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:63
|
||||||
msgid "Create fatlink"
|
msgid "Create fatlink"
|
||||||
msgstr "Erstelle FAT-Link"
|
msgstr "FAT-Link erstellen"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:3
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:3
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:4
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:4
|
||||||
msgid "Fatlink view"
|
msgid "Fatlink view"
|
||||||
msgstr "FAT-Link sehen"
|
msgstr "FAT-Link ansehen"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:7
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:7
|
||||||
msgid "Edit fatlink"
|
msgid "Edit fatlink"
|
||||||
msgstr "Editiere FAT-Link"
|
msgstr "FAT-Link bearbeiten"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:11
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:11
|
||||||
msgid "Delete fat"
|
msgid "Delete fat"
|
||||||
msgstr "Lösche FAT"
|
msgstr "FAT löschen"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:17
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:17
|
||||||
msgid "Registered characters"
|
msgid "Registered characters"
|
||||||
@@ -497,7 +497,7 @@ msgstr[1] "%(user)s hat diesen Monat %(links)s FAT-Links eingesammelt."
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:26
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:26
|
||||||
msgid "Times used"
|
msgid "Times used"
|
||||||
msgstr "male genutzt"
|
msgstr "Wie oft genutzt"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:37
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:37
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -570,7 +570,7 @@ msgstr "FAT-Link Statistik"
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:20
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:20
|
||||||
msgid "Ticker"
|
msgid "Ticker"
|
||||||
msgstr "Ticker: "
|
msgstr "Ticker"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:8
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:8
|
||||||
msgid "Participation data"
|
msgid "Participation data"
|
||||||
@@ -594,7 +594,7 @@ msgstr "Letzter FAT-Link"
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:58
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:58
|
||||||
msgid "View statistics"
|
msgid "View statistics"
|
||||||
msgstr "Statistik"
|
msgstr "Statistiken ansehen"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:95
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:95
|
||||||
msgid "No created fatlinks on record."
|
msgid "No created fatlinks on record."
|
||||||
@@ -713,8 +713,8 @@ msgid ""
|
|||||||
"States listed here will have the ability to join this group provided they "
|
"States listed here will have the ability to join this group provided they "
|
||||||
"have the proper permissions.<br>"
|
"have the proper permissions.<br>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Hier gelistete Ränge können dieser Gruppe beitreten, vorausgesetzt sie haben"
|
"Die hier aufgeführten Status können dieser Gruppe beitreten, sofern sie über"
|
||||||
" die entsprechenden Berechtigungen.<br>"
|
" die entsprechenden Berechtigungen verfügen.<br>"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:171
|
#: allianceauth/groupmanagement/models.py:171
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -814,7 +814,7 @@ msgstr "Gruppenmitglieder"
|
|||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:113
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:113
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:21
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:21
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr "Organization"
|
msgstr "Organisation"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:49
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:49
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:75
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:75
|
||||||
@@ -933,18 +933,18 @@ msgstr "Gruppenverwaltung"
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
|
||||||
msgid "Join Requests"
|
msgid "Join Requests"
|
||||||
msgstr "Beitrittsgesuche"
|
msgstr "Beitrittsanfragen"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:35
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:35
|
||||||
msgid "Leave Requests"
|
msgid "Leave Requests"
|
||||||
msgstr "Austrittsgesuche"
|
msgstr "Austrittsanfragen"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:57
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:57
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:114
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:114
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:18
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:18
|
||||||
#: allianceauth/services/modules/openfire/forms.py:6
|
#: allianceauth/services/modules/openfire/forms.py:6
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr "Gruppen"
|
msgstr "Gruppe"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:145
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:145
|
||||||
@@ -968,7 +968,7 @@ msgstr "Keine Gruppenaustrittsanfragen"
|
|||||||
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:8
|
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:8
|
||||||
#: allianceauth/templates/allianceauth/top-menu.html:8
|
#: allianceauth/templates/allianceauth/top-menu.html:8
|
||||||
msgid "Toggle navigation"
|
msgid "Toggle navigation"
|
||||||
msgstr "Toggle Navigation"
|
msgstr "Navigation umschalten"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:19
|
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:19
|
||||||
msgid "Group Requests"
|
msgid "Group Requests"
|
||||||
@@ -994,7 +994,7 @@ msgstr "Gruppe existiert nicht"
|
|||||||
#: allianceauth/groupmanagement/views.py:195
|
#: allianceauth/groupmanagement/views.py:195
|
||||||
#, 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 "Beitrittsanfrage von %(mainchar)s zur Gruppe %(group)s akzeptiert."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:201
|
#: allianceauth/groupmanagement/views.py:201
|
||||||
#: allianceauth/groupmanagement/views.py:232
|
#: allianceauth/groupmanagement/views.py:232
|
||||||
@@ -1003,18 +1003,18 @@ 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 ""
|
||||||
"Bei der Bearbeitung des Beitrittsgesuchs von %(mainchar)s zur Gruppe "
|
"Bei der Bearbeitung des Beitrittsanfrage von %(mainchar)s zur Gruppe "
|
||||||
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:226
|
#: allianceauth/groupmanagement/views.py:226
|
||||||
#, 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 "Beitrittsanfrage von %(mainchar)s zur Gruppe %(group)s abgelehnt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:261
|
#: allianceauth/groupmanagement/views.py:261
|
||||||
#, 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 "Austrittsanfrage von %(mainchar)s für Gruppe %(group)s akzeptiert."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:266
|
#: allianceauth/groupmanagement/views.py:266
|
||||||
#: allianceauth/groupmanagement/views.py:298
|
#: allianceauth/groupmanagement/views.py:298
|
||||||
@@ -1023,13 +1023,13 @@ 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 ""
|
||||||
"Bei der Bearbeitung des Austrittsgesuchs von %(mainchar)s für Gruppe "
|
"Bei der Bearbeitung des Austrittsanfrage von %(mainchar)s für Gruppe "
|
||||||
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:292
|
#: allianceauth/groupmanagement/views.py:292
|
||||||
#, 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 "Austrittsanfrage von %(mainchar)s für Gruppe %(group)s abgelehnt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:336
|
#: allianceauth/groupmanagement/views.py:336
|
||||||
#: allianceauth/groupmanagement/views.py:346
|
#: allianceauth/groupmanagement/views.py:346
|
||||||
@@ -1042,7 +1042,7 @@ msgstr "Du bist bereits Mitglied dieser Gruppe."
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:358
|
#: allianceauth/groupmanagement/views.py:358
|
||||||
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 bereits für diese Gruppe angefragt."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:367
|
#: allianceauth/groupmanagement/views.py:367
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1059,12 +1059,12 @@ msgstr "Du bist kein Mitglied dieser Gruppe"
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:393
|
#: allianceauth/groupmanagement/views.py:393
|
||||||
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 eine ausstehendes Austrittsanfrage für diese Gruppe."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:409
|
#: allianceauth/groupmanagement/views.py:409
|
||||||
#, 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 "Austrittsanfrage für Gruppe %(group)s gesendet."
|
||||||
|
|
||||||
#: allianceauth/hrapplications/auth_hooks.py:14
|
#: allianceauth/hrapplications/auth_hooks.py:14
|
||||||
msgid "Applications"
|
msgid "Applications"
|
||||||
@@ -1086,11 +1086,11 @@ msgstr "Wähle eine Corporation"
|
|||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/corpchoice.html:10
|
#: allianceauth/hrapplications/templates/hrapplications/corpchoice.html:10
|
||||||
msgid "Available Corps"
|
msgid "Available Corps"
|
||||||
msgstr "Zur Auswahl stehende Corporations"
|
msgstr "Verfügbare Corporationen"
|
||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/corpchoice.html:22
|
#: allianceauth/hrapplications/templates/hrapplications/corpchoice.html:22
|
||||||
msgid "No corps are accepting applications at this time."
|
msgid "No corps are accepting applications at this time."
|
||||||
msgstr "Zur Zeit nimmt keine Corp Bewerbungen entgegen."
|
msgstr "Zur Zeit nimmt keine Corp Bewerbungen an."
|
||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/create.html:4
|
#: allianceauth/hrapplications/templates/hrapplications/create.html:4
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/create.html:7
|
#: allianceauth/hrapplications/templates/hrapplications/create.html:7
|
||||||
@@ -1186,7 +1186,7 @@ msgstr "Keine angesehenen Bewerbungen"
|
|||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:62
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:62
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:134
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:134
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Geschlossen"
|
msgstr "Schließen"
|
||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:177
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:177
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:63
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:63
|
||||||
@@ -1200,7 +1200,7 @@ msgstr "Suche"
|
|||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:11
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:11
|
||||||
msgid "Application Search Results"
|
msgid "Application Search Results"
|
||||||
msgstr "Bewerbungen Suchergebnisse"
|
msgstr "Ergebnisse der Bewerbungssuche"
|
||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:22
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:22
|
||||||
msgid "Application ID"
|
msgid "Application ID"
|
||||||
@@ -1342,12 +1342,12 @@ msgstr "Operationsart"
|
|||||||
#: allianceauth/optimer/form.py:17
|
#: allianceauth/optimer/form.py:17
|
||||||
#: allianceauth/srp/templates/srp/management.html:38
|
#: allianceauth/srp/templates/srp/management.html:38
|
||||||
msgid "Fleet Commander"
|
msgid "Fleet Commander"
|
||||||
msgstr "Flottenkommandeur"
|
msgstr "Flottenkommandant"
|
||||||
|
|
||||||
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
|
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
|
||||||
#: allianceauth/srp/templates/srp/data.html:91
|
#: allianceauth/srp/templates/srp/data.html:91
|
||||||
msgid "Additional Info"
|
msgid "Additional Info"
|
||||||
msgstr "Zusätzliche Info"
|
msgstr "Zusätzliche Informationen"
|
||||||
|
|
||||||
#: allianceauth/optimer/form.py:23
|
#: allianceauth/optimer/form.py:23
|
||||||
msgid "(Optional) Describe the operation with a couple of short words."
|
msgid "(Optional) Describe the operation with a couple of short words."
|
||||||
@@ -1360,7 +1360,7 @@ msgstr "Operation erstellen"
|
|||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/fleetoptable.html:12
|
#: allianceauth/optimer/templates/optimer/fleetoptable.html:12
|
||||||
msgid "Form Up System"
|
msgid "Form Up System"
|
||||||
msgstr "Form Up System"
|
msgstr "Startsystem"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/fleetoptable.html:14
|
#: allianceauth/optimer/templates/optimer/fleetoptable.html:14
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:36
|
#: allianceauth/timerboard/templates/timerboard/view.html:36
|
||||||
@@ -1384,20 +1384,20 @@ msgstr "Flottenoperationen Zeiten"
|
|||||||
#: allianceauth/optimer/templates/optimer/management.html:20
|
#: allianceauth/optimer/templates/optimer/management.html:20
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:22
|
#: allianceauth/timerboard/templates/timerboard/view.html:22
|
||||||
msgid "Current Eve Time:"
|
msgid "Current Eve Time:"
|
||||||
msgstr "Momentane Eve Zeit"
|
msgstr "Aktuelle Eve Zeit"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:26
|
#: allianceauth/optimer/templates/optimer/management.html:26
|
||||||
msgid "Next Fleet Operations"
|
msgid "Next Fleet Operations"
|
||||||
msgstr "Anstehende Flottenoperationen"
|
msgstr "Anstehende Flotten"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:30
|
#: allianceauth/optimer/templates/optimer/management.html:30
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:362
|
#: allianceauth/timerboard/templates/timerboard/view.html:362
|
||||||
msgid "No upcoming timers."
|
msgid "No upcoming timers."
|
||||||
msgstr "Keine kommenden Timer."
|
msgstr "Keine bevorstehenden Timer."
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:33
|
#: allianceauth/optimer/templates/optimer/management.html:33
|
||||||
msgid "Past Fleet Operations"
|
msgid "Past Fleet Operations"
|
||||||
msgstr "Vergangene Flottenoperationen"
|
msgstr "Vergangene Flotten"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:37
|
#: allianceauth/optimer/templates/optimer/management.html:37
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:535
|
#: allianceauth/timerboard/templates/timerboard/view.html:535
|
||||||
@@ -1408,7 +1408,7 @@ msgstr "Keine vergangenen Timer."
|
|||||||
#: allianceauth/optimer/templates/optimer/update.html:15
|
#: allianceauth/optimer/templates/optimer/update.html:15
|
||||||
#: allianceauth/optimer/templates/optimer/update.html:27
|
#: allianceauth/optimer/templates/optimer/update.html:27
|
||||||
msgid "Update Fleet Operation"
|
msgid "Update Fleet Operation"
|
||||||
msgstr "Aktualisiere Flottenoperationen"
|
msgstr "Aktualisiere Flottenoperation"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/update.html:21
|
#: allianceauth/optimer/templates/optimer/update.html:21
|
||||||
msgid "Fleet Operation Does Not Exist"
|
msgid "Fleet Operation Does Not Exist"
|
||||||
@@ -1432,7 +1432,7 @@ msgstr "Änderungen für Operation timer %(opname)s gespeichert."
|
|||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:4
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:4
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:8
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:8
|
||||||
msgid "Permissions Audit"
|
msgid "Permissions Audit"
|
||||||
msgstr "Berechtigungsübersicht"
|
msgstr "Berechtigungsprüfung"
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
|
||||||
msgid "User / Character"
|
msgid "User / Character"
|
||||||
@@ -1494,11 +1494,11 @@ msgstr "Dienste"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:6
|
#: allianceauth/services/forms.py:6
|
||||||
msgid "Name of Fleet:"
|
msgid "Name of Fleet:"
|
||||||
msgstr "SRP Flotte erstellen:"
|
msgstr "Name der Flotte:"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:7
|
#: allianceauth/services/forms.py:7
|
||||||
msgid "Fleet Commander:"
|
msgid "Fleet Commander:"
|
||||||
msgstr "Flottenkommandeur:"
|
msgstr "Flottenkommandant:"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:8
|
#: allianceauth/services/forms.py:8
|
||||||
msgid "Fleet Comms:"
|
msgid "Fleet Comms:"
|
||||||
@@ -1514,11 +1514,11 @@ msgstr "Schiffspriorität:"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:11
|
#: allianceauth/services/forms.py:11
|
||||||
msgid "Formup Location:"
|
msgid "Formup Location:"
|
||||||
msgstr "Formup Location:"
|
msgstr "Startsystem:"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:12
|
#: allianceauth/services/forms.py:12
|
||||||
msgid "Formup Time:"
|
msgid "Formup Time:"
|
||||||
msgstr "Formup Zeit:"
|
msgstr "Startzeit:"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:13
|
#: allianceauth/services/forms.py:13
|
||||||
msgid "Expected Duration:"
|
msgid "Expected Duration:"
|
||||||
@@ -1530,7 +1530,7 @@ msgstr "Grund:"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:15
|
#: allianceauth/services/forms.py:15
|
||||||
msgid "Reimbursable?*"
|
msgid "Reimbursable?*"
|
||||||
msgstr "Erstattungsfähig?"
|
msgstr "Erstattungsfähig?*"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:15 allianceauth/services/forms.py:16
|
#: allianceauth/services/forms.py:15 allianceauth/services/forms.py:16
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
@@ -1542,7 +1542,7 @@ msgstr "Nein"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:16
|
#: allianceauth/services/forms.py:16
|
||||||
msgid "Important?*"
|
msgid "Important?*"
|
||||||
msgstr "Wichtig?"
|
msgstr "Wichtig?*"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:21 allianceauth/services/forms.py:31
|
#: allianceauth/services/forms.py:21 allianceauth/services/forms.py:31
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
@@ -1550,7 +1550,7 @@ msgstr "Passwort"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:26 allianceauth/services/forms.py:36
|
#: allianceauth/services/forms.py:26 allianceauth/services/forms.py:36
|
||||||
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 "Das Passwort muss mindestens 8 Zeichen lang sein"
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/models.py:187
|
#: allianceauth/services/modules/discord/models.py:187
|
||||||
msgid "Discord Account Disabled"
|
msgid "Discord Account Disabled"
|
||||||
@@ -1591,7 +1591,7 @@ msgstr "Discord Konto deaktiviert."
|
|||||||
#: allianceauth/services/modules/discord/views.py:36
|
#: allianceauth/services/modules/discord/views.py:36
|
||||||
#: allianceauth/services/modules/discord/views.py:59
|
#: allianceauth/services/modules/discord/views.py:59
|
||||||
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 während der Verarbeitung Deines Discord Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discord/views.py:102
|
#: allianceauth/services/modules/discord/views.py:102
|
||||||
msgid "Your Discord account has been successfully activated."
|
msgid "Your Discord account has been successfully activated."
|
||||||
@@ -1607,7 +1607,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:29
|
#: allianceauth/services/modules/discourse/views.py:29
|
||||||
msgid "You are not authorized to access Discourse."
|
msgid "You are not authorized to access Discourse."
|
||||||
msgstr "Du bist nicht autorisiert auf Discorse zuzugreifen."
|
msgstr "Du bist nicht autorisiert auf Discourse zuzugreifen."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:34
|
#: allianceauth/services/modules/discourse/views.py:34
|
||||||
msgid "You must have a main character set to access Discourse."
|
msgid "You must have a main character set to access Discourse."
|
||||||
@@ -1619,14 +1619,14 @@ 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 ""
|
||||||
"Keine SSO-Nutzdaten oder Signaturen. Bitte wenden Sie sich an den Support, "
|
"Keine SSO-Nutzdaten oder Signaturen. Bitte wende Dich an den Support, wenn "
|
||||||
"wenn das Problem weiterhin besteht."
|
"das Problem weiterhin besteht."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:54
|
#: allianceauth/services/modules/discourse/views.py:54
|
||||||
#: allianceauth/services/modules/discourse/views.py:62
|
#: allianceauth/services/modules/discourse/views.py:62
|
||||||
msgid "Invalid payload. Please contact support if this problem persists."
|
msgid "Invalid payload. Please contact support if this problem persists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Ungültige Nutzdaten. Bitte wenden Sie sich an den Support, wenn das Problem "
|
"Ungültige Nutzdaten. Bitte wenden Dich an den Support, wenn das Problem "
|
||||||
"weiterhin besteht."
|
"weiterhin besteht."
|
||||||
|
|
||||||
#: allianceauth/services/modules/ips4/views.py:31
|
#: allianceauth/services/modules/ips4/views.py:31
|
||||||
@@ -1638,7 +1638,7 @@ msgstr "IP4Suite Konto aktiviert."
|
|||||||
#: allianceauth/services/modules/ips4/views.py:81
|
#: allianceauth/services/modules/ips4/views.py:81
|
||||||
#: allianceauth/services/modules/ips4/views.py:101
|
#: allianceauth/services/modules/ips4/views.py:101
|
||||||
msgid "An error occurred while processing your IPSuite4 account."
|
msgid "An error occurred while processing your IPSuite4 account."
|
||||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines IPSuite4 Kontos."
|
msgstr "Es gab einen Fehler während der Verarbeitung Deines IPSuite4 Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/ips4/views.py:52
|
#: allianceauth/services/modules/ips4/views.py:52
|
||||||
msgid "Reset IPSuite4 password."
|
msgid "Reset IPSuite4 password."
|
||||||
@@ -1660,7 +1660,7 @@ msgstr "Jabber"
|
|||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:5
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:5
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:10
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:10
|
||||||
msgid "Jabber Broadcast"
|
msgid "Jabber Broadcast"
|
||||||
msgstr "Jabber Übertragung"
|
msgstr "Jabber Ankündigung"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/auth_hooks.py:94
|
#: allianceauth/services/modules/openfire/auth_hooks.py:94
|
||||||
msgid "Fleet Broadcast Formatter"
|
msgid "Fleet Broadcast Formatter"
|
||||||
@@ -1672,11 +1672,11 @@ msgstr "Nachricht"
|
|||||||
|
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:16
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:16
|
||||||
msgid "Broadcast Sent!!"
|
msgid "Broadcast Sent!!"
|
||||||
msgstr "Übertragung gesendet!!"
|
msgstr "Ankündigung gesendet!!"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:22
|
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:22
|
||||||
msgid "Broadcast"
|
msgid "Broadcast"
|
||||||
msgstr "Übertragungen"
|
msgstr "Ankündigung"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:35
|
#: allianceauth/services/modules/openfire/views.py:35
|
||||||
msgid "Activated jabber account."
|
msgid "Activated jabber account."
|
||||||
@@ -1687,7 +1687,7 @@ msgstr "Jabber Konto aktiviert."
|
|||||||
#: allianceauth/services/modules/openfire/views.py:76
|
#: allianceauth/services/modules/openfire/views.py:76
|
||||||
#: allianceauth/services/modules/openfire/views.py:147
|
#: allianceauth/services/modules/openfire/views.py:147
|
||||||
msgid "An error occurred while processing your jabber account."
|
msgid "An error occurred while processing your jabber account."
|
||||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Jabber Kontos."
|
msgstr "Es gab einen Fehler während der Verarbeitung Deines Jabber Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:69
|
#: allianceauth/services/modules/openfire/views.py:69
|
||||||
msgid "Reset jabber password."
|
msgid "Reset jabber password."
|
||||||
@@ -1696,7 +1696,7 @@ msgstr "Jabber Passwort zurücksetzen."
|
|||||||
#: allianceauth/services/modules/openfire/views.py:115
|
#: allianceauth/services/modules/openfire/views.py:115
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Sent jabber broadcast to %s"
|
msgid "Sent jabber broadcast to %s"
|
||||||
msgstr "Sende Jabber Durchsage an %s"
|
msgstr "Sende Jabber Ankündigung an %s"
|
||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:144
|
#: allianceauth/services/modules/openfire/views.py:144
|
||||||
msgid "Set jabber password."
|
msgid "Set jabber password."
|
||||||
@@ -1711,7 +1711,7 @@ msgstr "Forum Konto aktiviert."
|
|||||||
#: allianceauth/services/modules/phpbb3/views.py:78
|
#: allianceauth/services/modules/phpbb3/views.py:78
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:101
|
#: allianceauth/services/modules/phpbb3/views.py:101
|
||||||
msgid "An error occurred while processing your forum account."
|
msgid "An error occurred while processing your forum account."
|
||||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Forum Kontos."
|
msgstr "Es gab einen Fehler während der Verarbeitung Deines Forum Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:53
|
#: allianceauth/services/modules/phpbb3/views.py:53
|
||||||
msgid "Deactivated forum account."
|
msgid "Deactivated forum account."
|
||||||
@@ -1734,7 +1734,7 @@ msgstr "SMF Konto aktiviert."
|
|||||||
#: allianceauth/services/modules/smf/views.py:102
|
#: allianceauth/services/modules/smf/views.py:102
|
||||||
#: allianceauth/services/modules/smf/views.py:124
|
#: allianceauth/services/modules/smf/views.py:124
|
||||||
msgid "An error occurred while processing your SMF account."
|
msgid "An error occurred while processing your SMF account."
|
||||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines SMF Kontos."
|
msgstr "Es gab einen Fehler während der Verarbeitung Deines SMF Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/smf/views.py:78
|
#: allianceauth/services/modules/smf/views.py:78
|
||||||
msgid "Deactivated SMF account."
|
msgid "Deactivated SMF account."
|
||||||
@@ -1751,7 +1751,7 @@ msgstr "Setze SMF Passwort."
|
|||||||
#: 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"
|
||||||
msgstr "Kann den Benutzer %s auf dem Server nicht finden"
|
msgstr "Der Benutzer %s konnte auf dem Server nicht gefunden werden"
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/templates/admin/teamspeak3/authts/change_list.html:8
|
#: allianceauth/services/modules/teamspeak3/templates/admin/teamspeak3/authts/change_list.html:8
|
||||||
msgid "Update TS3 groups"
|
msgid "Update TS3 groups"
|
||||||
@@ -1783,7 +1783,8 @@ msgstr "TeamSpeak3 Konto aktiviert."
|
|||||||
#: 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 "Es gab einen Fehler bei der Verarbeitung Deines TeamSpeak3 Kontos."
|
msgstr ""
|
||||||
|
"Es gab einen Fehler während der Verarbeitung Deines TeamSpeak3 Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/views.py:71
|
#: allianceauth/services/modules/teamspeak3/views.py:71
|
||||||
msgid "Deactivated TeamSpeak3 account."
|
msgid "Deactivated TeamSpeak3 account."
|
||||||
@@ -1802,7 +1803,7 @@ msgstr "XenForo Konto aktiviert."
|
|||||||
#: allianceauth/services/modules/xenforo/views.py:73
|
#: allianceauth/services/modules/xenforo/views.py:73
|
||||||
#: allianceauth/services/modules/xenforo/views.py:94
|
#: allianceauth/services/modules/xenforo/views.py:94
|
||||||
msgid "An error occurred while processing your XenForo account."
|
msgid "An error occurred while processing your XenForo account."
|
||||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines XenForo Kontos."
|
msgstr "Es gab einen Fehler während der Verarbeitung Deines XenForo Kontos."
|
||||||
|
|
||||||
#: allianceauth/services/modules/xenforo/views.py:50
|
#: allianceauth/services/modules/xenforo/views.py:50
|
||||||
msgid "Deactivated XenForo account."
|
msgid "Deactivated XenForo account."
|
||||||
@@ -1832,7 +1833,7 @@ msgstr "Formatieren"
|
|||||||
#: allianceauth/services/templates/services/service_confirm_delete.html:12
|
#: allianceauth/services/templates/services/service_confirm_delete.html:12
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Delete %(service_name)s Account?"
|
msgid "Delete %(service_name)s Account?"
|
||||||
msgstr "Konto %(service_name)s löschen?"
|
msgstr " %(service_name)s Konto löschen?"
|
||||||
|
|
||||||
#: allianceauth/services/templates/services/service_confirm_delete.html:20
|
#: allianceauth/services/templates/services/service_confirm_delete.html:20
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1856,7 +1857,7 @@ msgstr "%(service_name)s Passwort ändern"
|
|||||||
#: allianceauth/services/templates/services/service_password.html:9
|
#: allianceauth/services/templates/services/service_password.html:9
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Set %(service_name)s Password"
|
msgid "Set %(service_name)s Password"
|
||||||
msgstr "%(service_name)s Passwort"
|
msgstr "%(service_name)s Passwort setzen"
|
||||||
|
|
||||||
#: allianceauth/services/templates/services/service_password.html:17
|
#: allianceauth/services/templates/services/service_password.html:17
|
||||||
msgid "Set Password"
|
msgid "Set Password"
|
||||||
@@ -1927,7 +1928,7 @@ msgstr "SRP Flotten Daten"
|
|||||||
|
|
||||||
#: allianceauth/srp/templates/srp/data.html:50
|
#: allianceauth/srp/templates/srp/data.html:50
|
||||||
msgid "SRP Fleet Data"
|
msgid "SRP Fleet Data"
|
||||||
msgstr "SRP-Flotte Daten"
|
msgstr "SRP Flotte Daten"
|
||||||
|
|
||||||
#: allianceauth/srp/templates/srp/data.html:55
|
#: allianceauth/srp/templates/srp/data.html:55
|
||||||
msgid "Mark Incomplete"
|
msgid "Mark Incomplete"
|
||||||
@@ -2005,7 +2006,7 @@ msgstr "Füge SRP Flotte hinzu"
|
|||||||
|
|
||||||
#: allianceauth/srp/templates/srp/management.html:39
|
#: allianceauth/srp/templates/srp/management.html:39
|
||||||
msgid "Fleet AAR"
|
msgid "Fleet AAR"
|
||||||
msgstr "Flotten AAR"
|
msgstr "Flottenbericht"
|
||||||
|
|
||||||
#: allianceauth/srp/templates/srp/management.html:40
|
#: allianceauth/srp/templates/srp/management.html:40
|
||||||
msgid "Fleet SRP Code"
|
msgid "Fleet SRP Code"
|
||||||
@@ -2033,7 +2034,7 @@ msgstr "Deaktiviert"
|
|||||||
|
|
||||||
#: allianceauth/srp/templates/srp/management.html:83
|
#: allianceauth/srp/templates/srp/management.html:83
|
||||||
msgid "Completed"
|
msgid "Completed"
|
||||||
msgstr "Fertig"
|
msgstr "Abgeschlossen"
|
||||||
|
|
||||||
#: allianceauth/srp/templates/srp/management.html:101
|
#: allianceauth/srp/templates/srp/management.html:101
|
||||||
msgid "Are you sure you want to delete this SRP code and its contents?"
|
msgid "Are you sure you want to delete this SRP code and its contents?"
|
||||||
@@ -2086,7 +2087,7 @@ msgstr "SRP Link für %(fleetname)s aktiviert."
|
|||||||
#: allianceauth/srp/views.py:140
|
#: allianceauth/srp/views.py:140
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Marked SRP fleet %(fleetname)s as completed."
|
msgid "Marked SRP fleet %(fleetname)s as completed."
|
||||||
msgstr "SRP Flotte %(fleetname)s als vollständig markiert."
|
msgstr "SRP Flotte %(fleetname)s als abgeschlossen markiert."
|
||||||
|
|
||||||
#: allianceauth/srp/views.py:153
|
#: allianceauth/srp/views.py:153
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -2204,7 +2205,7 @@ msgstr "Testversion verfügbar"
|
|||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:78
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:78
|
||||||
msgid "Task Queue"
|
msgid "Task Queue"
|
||||||
msgstr "Warteschlange"
|
msgstr "Task-Warteschlange"
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:81
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:81
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -2249,7 +2250,7 @@ msgstr "Ausloggen"
|
|||||||
|
|
||||||
#: allianceauth/timerboard/form.py:53
|
#: allianceauth/timerboard/form.py:53
|
||||||
msgid "Other"
|
msgid "Other"
|
||||||
msgstr "anderes"
|
msgstr "Anderes"
|
||||||
|
|
||||||
#: allianceauth/timerboard/form.py:54
|
#: allianceauth/timerboard/form.py:54
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:62
|
#: allianceauth/timerboard/templates/timerboard/view.html:62
|
||||||
@@ -2353,7 +2354,7 @@ msgstr "Timer löschen"
|
|||||||
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:19
|
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:19
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Are you sure you want to delete timer \"%(object)s\"?"
|
msgid "Are you sure you want to delete timer \"%(object)s\"?"
|
||||||
msgstr "Bist Du sicher das Du Timer \"%(object)s\" löschen möchtest?"
|
msgstr "Bist Du sicher das Du Timer „%(object)s“ löschen möchtest?"
|
||||||
|
|
||||||
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:5
|
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:5
|
||||||
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:13
|
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:13
|
||||||
|
|||||||
@@ -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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2023-11-08 23:55+1000\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,7 +26,7 @@ msgstr ""
|
|||||||
msgid "Google Analytics V4"
|
msgid "Google Analytics V4"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/decorators.py:37
|
#: allianceauth/authentication/decorators.py:49
|
||||||
msgid "A main character is required to perform that action. Add one below."
|
msgid "A main character is required to perform that action. Add one below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -39,63 +39,68 @@ msgstr ""
|
|||||||
msgid "You are not allowed to add or remove these restricted groups: %s"
|
msgid "You are not allowed to add or remove these restricted groups: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:80
|
#: allianceauth/authentication/models.py:71
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:81
|
#: allianceauth/authentication/models.py:72
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:82
|
#: allianceauth/authentication/models.py:73
|
||||||
msgid "Spanish"
|
msgid "Spanish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:83
|
#: allianceauth/authentication/models.py:74
|
||||||
msgid "Chinese Simplified"
|
msgid "Chinese Simplified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:84
|
#: allianceauth/authentication/models.py:75
|
||||||
msgid "Russian"
|
msgid "Russian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:85
|
#: allianceauth/authentication/models.py:76
|
||||||
msgid "Korean"
|
msgid "Korean"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:86
|
#: allianceauth/authentication/models.py:77
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:87
|
#: allianceauth/authentication/models.py:78
|
||||||
msgid "Japanese"
|
msgid "Japanese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:88
|
#: allianceauth/authentication/models.py:79
|
||||||
msgid "Italian"
|
msgid "Italian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:91
|
#: allianceauth/authentication/models.py:80
|
||||||
msgid "Language"
|
msgid "Ukrainian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:96
|
#: allianceauth/authentication/models.py:96
|
||||||
|
msgid "Language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/models.py:101
|
||||||
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
||||||
msgid "Night Mode"
|
msgid "Night Mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:110
|
#: allianceauth/authentication/models.py:115
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "State changed to: %s"
|
msgid "State changed to: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:111
|
#: allianceauth/authentication/models.py:116
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your user's state is now: %(state)s"
|
msgid "Your user's state is now: %(state)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:4
|
#: allianceauth/authentication/templates/authentication/dashboard.html:4
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:7
|
#: allianceauth/authentication/templates/authentication/dashboard.html:7
|
||||||
|
#: allianceauth/authentication/templates/authentication/tokens.html:4
|
||||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
#: allianceauth/templates/allianceauth/side-menu.html:10
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -151,8 +156,49 @@ msgstr ""
|
|||||||
msgid "Alliance"
|
msgid "Alliance"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/templates/authentication/tokens.html:7
|
||||||
|
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:62
|
||||||
|
msgid "Token Management"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/templates/authentication/tokens.html:12
|
||||||
|
msgid "Scopes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/templates/authentication/tokens.html:13
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:28
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:127
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:27
|
||||||
|
#: allianceauth/hrapplications/templates/hrapplications/view.html:73
|
||||||
|
#: allianceauth/srp/templates/srp/data.html:101
|
||||||
|
#: allianceauth/srp/templates/srp/management.html:44
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/templates/authentication/tokens.html:14
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:74
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:112
|
||||||
|
#: allianceauth/corputils/templates/corputils/corpstats.html:156
|
||||||
|
#: allianceauth/corputils/templates/corputils/search.html:13
|
||||||
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:22
|
||||||
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:26
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:55
|
||||||
|
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112
|
||||||
|
msgid "Character"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/authentication/templates/authentication/tokens.html:28
|
||||||
|
msgid ""
|
||||||
|
"This page is a best attempt, but backups or database logs can still contain "
|
||||||
|
"your tokens. Always revoke tokens on https://community.eveonline.com/support/"
|
||||||
|
"third-party-applications/ where possible."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/public/login.html:6
|
#: allianceauth/authentication/templates/public/login.html:6
|
||||||
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:58
|
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:69
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -184,47 +230,47 @@ msgstr ""
|
|||||||
msgid "Invalid or expired activation link."
|
msgid "Invalid or expired activation link."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:77
|
#: allianceauth/authentication/views.py:118
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
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 ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:83
|
#: allianceauth/authentication/views.py:124
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Changed main character to %(char)s"
|
msgid "Changed main character to %(char)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:92
|
#: allianceauth/authentication/views.py:133
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Added %(name)s to your account."
|
msgid "Added %(name)s to your account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:94
|
#: allianceauth/authentication/views.py:135
|
||||||
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:133
|
#: allianceauth/authentication/views.py:174
|
||||||
msgid "Unable to authenticate as the selected character."
|
msgid "Unable to authenticate as the selected character."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:197
|
#: allianceauth/authentication/views.py:238
|
||||||
msgid "Registration token has expired."
|
msgid "Registration token has expired."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:252
|
#: allianceauth/authentication/views.py:296
|
||||||
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:257
|
#: allianceauth/authentication/views.py:301
|
||||||
msgid "Confirmed your email address. Please login to continue."
|
msgid "Confirmed your email address. Please login to continue."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/authentication/views.py:262
|
#: allianceauth/authentication/views.py:306
|
||||||
msgid "Registration of new accounts is not allowed at this time."
|
msgid "Registration of new accounts is not allowed at this time."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -267,19 +313,6 @@ msgstr ""
|
|||||||
msgid "Last update:"
|
msgid "Last update:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:74
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:112
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:156
|
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:13
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:22
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:26
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:55
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112
|
|
||||||
msgid "Character"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: allianceauth/corputils/templates/corputils/corpstats.html:75
|
#: allianceauth/corputils/templates/corputils/corpstats.html:75
|
||||||
#: allianceauth/corputils/templates/corputils/search.html:14
|
#: allianceauth/corputils/templates/corputils/search.html:14
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
|
||||||
@@ -611,36 +644,41 @@ msgstr ""
|
|||||||
msgid "Group Management"
|
msgid "Group Management"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/forms.py:15
|
#: allianceauth/groupmanagement/forms.py:18
|
||||||
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
|
||||||
|
msgid "Users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/groupmanagement/forms.py:52
|
||||||
msgid "This name has been reserved and can not be used for groups."
|
msgid "This name has been reserved and can not be used for groups."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/forms.py:25
|
#: allianceauth/groupmanagement/forms.py:62
|
||||||
msgid "(auto)"
|
msgid "(auto)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/forms.py:34
|
#: allianceauth/groupmanagement/forms.py:71
|
||||||
msgid "There already exists a group with that name."
|
msgid "There already exists a group with that name."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:105
|
#: allianceauth/groupmanagement/models.py:104
|
||||||
msgid ""
|
msgid ""
|
||||||
"Internal group, users cannot see, join or request to join this group."
|
"Internal group, users cannot see, join or request to join this group."
|
||||||
"<br>Used for groups such as Members, Corp_*, Alliance_* etc.<br><b>Overrides "
|
"<br>Used for groups such as Members, Corp_*, Alliance_* etc.<br><b>Overrides "
|
||||||
"Hidden and Open options when selected.</b>"
|
"Hidden and Open options when selected.</b>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:113
|
#: allianceauth/groupmanagement/models.py:112
|
||||||
msgid "Group is hidden from users but can still join with the correct link."
|
msgid "Group is hidden from users but can still join with the correct link."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:119
|
#: allianceauth/groupmanagement/models.py:118
|
||||||
msgid ""
|
msgid ""
|
||||||
"Group is open and users will be automatically added upon request.<br>If the "
|
"Group is open and users will be automatically added upon request.<br>If the "
|
||||||
"group is not open users will need their request manually approved."
|
"group is not open users will need their request manually approved."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:126
|
#: allianceauth/groupmanagement/models.py:125
|
||||||
msgid ""
|
msgid ""
|
||||||
"Group is public. Any registered user is able to join this group, with "
|
"Group is public. Any registered user is able to join this group, with "
|
||||||
"visibility based on the other options set for this group.<br>Auth will not "
|
"visibility based on the other options set for this group.<br>Auth will not "
|
||||||
@@ -648,65 +686,65 @@ msgid ""
|
|||||||
"authenticated."
|
"authenticated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:135
|
#: allianceauth/groupmanagement/models.py:134
|
||||||
msgid ""
|
msgid ""
|
||||||
"Group is restricted. This means that adding or removing users for this group "
|
"Group is restricted. This means that adding or removing users for this group "
|
||||||
"requires a superuser admin."
|
"requires a superuser admin."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:144
|
#: allianceauth/groupmanagement/models.py:143
|
||||||
msgid ""
|
msgid ""
|
||||||
"Group leaders can process requests for this group. Use the <code>auth."
|
"Group leaders can process requests for this group. Use the <code>auth."
|
||||||
"group_management</code> permission to allow a user to manage all groups.<br>"
|
"group_management</code> permission to allow a user to manage all groups.<br>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:154
|
#: allianceauth/groupmanagement/models.py:153
|
||||||
msgid ""
|
msgid ""
|
||||||
"Members of leader groups can process requests for this group. Use the "
|
"Members of leader groups can process requests for this group. Use the "
|
||||||
"<code>auth.group_management</code> permission to allow a user to manage all "
|
"<code>auth.group_management</code> permission to allow a user to manage all "
|
||||||
"groups.<br>"
|
"groups.<br>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:163
|
#: allianceauth/groupmanagement/models.py:162
|
||||||
msgid ""
|
msgid ""
|
||||||
"States listed here will have the ability to join this group provided they "
|
"States listed here will have the ability to join this group provided they "
|
||||||
"have the proper permissions.<br>"
|
"have the proper permissions.<br>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:171
|
#: allianceauth/groupmanagement/models.py:170
|
||||||
msgid ""
|
msgid ""
|
||||||
"Short description <i>(max. 512 characters)</i> of the group shown to users."
|
"Short description <i>(max. 512 characters)</i> of the group shown to users."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:178
|
#: allianceauth/groupmanagement/models.py:177
|
||||||
msgid "Can request non-public groups"
|
msgid "Can request non-public groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:209
|
#: allianceauth/groupmanagement/models.py:208
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:212
|
#: allianceauth/groupmanagement/models.py:211
|
||||||
msgid "Name that can not be used for groups."
|
msgid "Name that can not be used for groups."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:215
|
#: allianceauth/groupmanagement/models.py:214
|
||||||
msgid "reason"
|
msgid "reason"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:215
|
#: allianceauth/groupmanagement/models.py:214
|
||||||
msgid "Reason why this name is reserved."
|
msgid "Reason why this name is reserved."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:218
|
#: allianceauth/groupmanagement/models.py:217
|
||||||
msgid "created by"
|
msgid "created by"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:223
|
#: allianceauth/groupmanagement/models.py:222
|
||||||
msgid "created at"
|
msgid "created at"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:223
|
#: allianceauth/groupmanagement/models.py:222
|
||||||
msgid "Date when this entry was created"
|
msgid "Date when this entry was created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -933,86 +971,86 @@ msgstr ""
|
|||||||
msgid "Group Membership"
|
msgid "Group Membership"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:163
|
#: allianceauth/groupmanagement/views.py:166
|
||||||
#, 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:165
|
#: allianceauth/groupmanagement/views.py:168
|
||||||
msgid "User does not exist in that group"
|
msgid "User does not exist in that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:168
|
#: allianceauth/groupmanagement/views.py:171
|
||||||
msgid "Group does not exist"
|
msgid "Group does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:195
|
#: allianceauth/groupmanagement/views.py:198
|
||||||
#, 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:201
|
#: allianceauth/groupmanagement/views.py:204
|
||||||
#: allianceauth/groupmanagement/views.py:232
|
#: allianceauth/groupmanagement/views.py:235
|
||||||
#, 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:226
|
#: allianceauth/groupmanagement/views.py:229
|
||||||
#, 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:261
|
#: allianceauth/groupmanagement/views.py:264
|
||||||
#, 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:266
|
#: allianceauth/groupmanagement/views.py:269
|
||||||
#: allianceauth/groupmanagement/views.py:298
|
#: allianceauth/groupmanagement/views.py:301
|
||||||
#, 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:292
|
#: allianceauth/groupmanagement/views.py:295
|
||||||
#, 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:336
|
#: allianceauth/groupmanagement/views.py:339
|
||||||
#: allianceauth/groupmanagement/views.py:346
|
#: allianceauth/groupmanagement/views.py:349
|
||||||
msgid "You cannot join that group"
|
msgid "You cannot join that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:341
|
#: allianceauth/groupmanagement/views.py:344
|
||||||
msgid "You are already a member of that group."
|
msgid "You are already a member of that group."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:358
|
#: allianceauth/groupmanagement/views.py:361
|
||||||
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:367
|
#: allianceauth/groupmanagement/views.py:370
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to group %(group)s."
|
msgid "Applied to group %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:377
|
#: allianceauth/groupmanagement/views.py:380
|
||||||
msgid "You cannot leave that group"
|
msgid "You cannot leave that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:381
|
#: allianceauth/groupmanagement/views.py:384
|
||||||
msgid "You are not a member of that group"
|
msgid "You are not a member of that group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:393
|
#: allianceauth/groupmanagement/views.py:396
|
||||||
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:409
|
#: allianceauth/groupmanagement/views.py:412
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Applied to leave group %(group)s."
|
msgid "Applied to leave group %(group)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1074,16 +1112,6 @@ msgstr ""
|
|||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:28
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:127
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:27
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:73
|
|
||||||
#: allianceauth/srp/templates/srp/data.html:101
|
|
||||||
#: allianceauth/srp/templates/srp/management.html:44
|
|
||||||
msgid "Actions"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:38
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:38
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:143
|
#: allianceauth/hrapplications/templates/hrapplications/management.html:143
|
||||||
@@ -1422,10 +1450,6 @@ msgstr ""
|
|||||||
msgid "Code Name"
|
msgid "Code Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
|
|
||||||
msgid "Users"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41
|
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41
|
||||||
msgid "States"
|
msgid "States"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -2146,11 +2170,11 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:95
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:95
|
||||||
#, python-format
|
msgid "running"
|
||||||
msgid ""
|
msgstr ""
|
||||||
"\n"
|
|
||||||
" %(queue_length)s queued tasks\n"
|
#: allianceauth/templates/allianceauth/admin-status/overview.html:96
|
||||||
" "
|
msgid "queued"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/top-menu-admin.html:9
|
#: allianceauth/templates/allianceauth/top-menu-admin.html:9
|
||||||
@@ -2166,11 +2190,11 @@ msgid "AA Support Discord"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:10
|
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:10
|
||||||
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:14
|
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:16
|
||||||
msgid "User Menu"
|
msgid "User Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:56
|
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:67
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2226,22 +2250,30 @@ msgid "Objective"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/timerboard/form.py:64
|
#: allianceauth/timerboard/form.py:64
|
||||||
msgid "Days Remaining"
|
msgid "Absolute Timer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/timerboard/form.py:65
|
#: allianceauth/timerboard/form.py:65
|
||||||
msgid "Hours Remaining"
|
msgid "Date and Time"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/timerboard/form.py:66
|
||||||
|
msgid "Days Remaining"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/timerboard/form.py:67
|
#: allianceauth/timerboard/form.py:67
|
||||||
msgid "Minutes Remaining"
|
msgid "Hours Remaining"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/timerboard/form.py:69
|
#: allianceauth/timerboard/form.py:69
|
||||||
|
msgid "Minutes Remaining"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: allianceauth/timerboard/form.py:71
|
||||||
msgid "Important"
|
msgid "Important"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: allianceauth/timerboard/form.py:70
|
#: allianceauth/timerboard/form.py:72
|
||||||
msgid "Corp-Restricted"
|
msgid "Corp-Restricted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -4,11 +4,11 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# frank1210 <francolopez_16@hotmail.com>, 2021
|
|
||||||
# Joel Falknau <ozirascal@gmail.com>, 2021
|
|
||||||
# Young Anexo, 2023
|
|
||||||
# Fegpawn Kaundur, 2023
|
# Fegpawn Kaundur, 2023
|
||||||
|
# frank1210 <francolopez_16@hotmail.com>, 2023
|
||||||
# trenus, 2023
|
# trenus, 2023
|
||||||
|
# Joel Falknau <ozirascal@gmail.com>, 2023
|
||||||
|
# Young Anexo, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -16,8 +16,8 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: trenus, 2023\n"
|
"Last-Translator: Young Anexo, 2023\n"
|
||||||
"Language-Team: Spanish (https://app.transifex.com/alliance-auth/teams/107430/es/)\n"
|
"Language-Team: Spanish (https://app.transifex.com/alliance-auth/teams/107430/es/)\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"
|
||||||
|
|||||||
Binary file not shown.
@@ -4,14 +4,14 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# François LACROIX-DURANT <umbre@fallenstarscreations.com>, 2020
|
# Keven D. <theenarki@gmail.com>, 2023
|
||||||
# Philippe Querin-Laporte <philippe.querin@hotmail.com>, 2020
|
# rockclodbuster, 2023
|
||||||
# Keven D. <theenarki@gmail.com>, 2020
|
# Geoffrey Fabbro, 2023
|
||||||
# Idea ., 2021
|
|
||||||
# Mickael PATTE, 2021
|
|
||||||
# Geoffrey Fabbro, 2021
|
|
||||||
# Mohssine Daghghar, 2023
|
# Mohssine Daghghar, 2023
|
||||||
# Ludovick Fortin, 2023
|
# François LACROIX-DURANT <umbre@fallenstarscreations.com>, 2023
|
||||||
|
# Mickael PATTE, 2023
|
||||||
|
# Philippe Querin-Laporte <philippe.querin@hotmail.com>, 2023
|
||||||
|
# Idea ., 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -19,8 +19,8 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: Ludovick Fortin, 2023\n"
|
"Last-Translator: Idea ., 2023\n"
|
||||||
"Language-Team: French (France) (https://app.transifex.com/alliance-auth/teams/107430/fr_FR/)\n"
|
"Language-Team: French (France) (https://app.transifex.com/alliance-auth/teams/107430/fr_FR/)\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"
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -4,8 +4,9 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Foch Petain <brigadier.rockforward@gmail.com>, 2020
|
# Foch Petain <brigadier.rockforward@gmail.com>, 2023
|
||||||
# kotaneko, 2023
|
# kotaneko, 2023
|
||||||
|
# Joel Falknau <ozirascal@gmail.com>, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -13,8 +14,8 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: kotaneko, 2023\n"
|
"Last-Translator: Joel Falknau <ozirascal@gmail.com>, 2023\n"
|
||||||
"Language-Team: Japanese (https://app.transifex.com/alliance-auth/teams/107430/ja/)\n"
|
"Language-Team: Japanese (https://app.transifex.com/alliance-auth/teams/107430/ja/)\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"
|
||||||
|
|||||||
Binary file not shown.
@@ -4,13 +4,13 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# None None <khd1226543@gmail.com>, 2020
|
# None None <khd1226543@gmail.com>, 2023
|
||||||
# Seowon Jung <seowon@hawaii.edu>, 2020
|
# Joel Falknau <ozirascal@gmail.com>, 2023
|
||||||
# Olgeda Choi <undead.choi@gmail.com>, 2020
|
# Seowon Jung <seowon@hawaii.edu>, 2023
|
||||||
# Lahty <js03js70@gmail.com>, 2020
|
# Olgeda Choi <undead.choi@gmail.com>, 2023
|
||||||
# Joel Falknau <ozirascal@gmail.com>, 2020
|
# ThatRagingKid, 2023
|
||||||
# ThatRagingKid, 2022
|
# Lahty <js03js70@gmail.com>, 2023
|
||||||
# jackfrost, 2022
|
# jackfrost, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -18,8 +18,8 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: jackfrost, 2022\n"
|
"Last-Translator: jackfrost, 2023\n"
|
||||||
"Language-Team: Korean (Korea) (https://app.transifex.com/alliance-auth/teams/107430/ko_KR/)\n"
|
"Language-Team: Korean (Korea) (https://app.transifex.com/alliance-auth/teams/107430/ko_KR/)\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"
|
||||||
|
|||||||
Binary file not shown.
@@ -4,9 +4,9 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Alexander Gess <de.alex.gess@gmail.com>, 2020
|
# Yuriy K <thedjcooltv@gmail.com>, 2023
|
||||||
# Yuriy K <thedjcooltv@gmail.com>, 2020
|
# Андрей Зубков <and.vareba81@gmail.com>, 2023
|
||||||
# Андрей Зубков <and.vareba81@gmail.com>, 2020
|
# Alexander Gess <de.alex.gess@gmail.com>, 2023
|
||||||
# Filipp Chertiev <f@fzfx.ru>, 2023
|
# Filipp Chertiev <f@fzfx.ru>, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -15,7 +15,7 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: Filipp Chertiev <f@fzfx.ru>, 2023\n"
|
"Last-Translator: Filipp Chertiev <f@fzfx.ru>, 2023\n"
|
||||||
"Language-Team: Russian (https://app.transifex.com/alliance-auth/teams/107430/ru/)\n"
|
"Language-Team: Russian (https://app.transifex.com/alliance-auth/teams/107430/ru/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
|||||||
Binary file not shown.
@@ -4,6 +4,7 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
|
# Denys Ivchenko, 2023
|
||||||
# Kristof Swensen, 2023
|
# Kristof Swensen, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
@@ -12,7 +13,7 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: Kristof Swensen, 2023\n"
|
"Last-Translator: Kristof Swensen, 2023\n"
|
||||||
"Language-Team: Ukrainian (https://app.transifex.com/alliance-auth/teams/107430/uk/)\n"
|
"Language-Team: Ukrainian (https://app.transifex.com/alliance-auth/teams/107430/uk/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@@ -32,7 +33,7 @@ msgstr "Google Analytics V4"
|
|||||||
#: allianceauth/authentication/decorators.py:37
|
#: allianceauth/authentication/decorators.py:37
|
||||||
msgid "A main character is required to perform that action. Add one below."
|
msgid "A main character is required to perform that action. Add one below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Для виконання цієї дії потрібен головний персонаж. Додайте його нижче."
|
"Для виконання цієї дії потрібен основний персонаж. Додайте його нижче."
|
||||||
|
|
||||||
#: allianceauth/authentication/forms.py:12
|
#: allianceauth/authentication/forms.py:12
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@@ -124,7 +125,7 @@ msgstr "Додати персонажа"
|
|||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:115
|
#: allianceauth/authentication/templates/authentication/dashboard.html:115
|
||||||
msgid "Change Main"
|
msgid "Change Main"
|
||||||
msgstr "Змінити головного персонажа"
|
msgstr "Змінити основного персонажа"
|
||||||
|
|
||||||
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
#: allianceauth/authentication/templates/authentication/dashboard.html:125
|
||||||
msgid "Group Memberships"
|
msgid "Group Memberships"
|
||||||
@@ -352,7 +353,7 @@ msgstr "Не вдалося зібрати статистику корпорац
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/auth_hooks.py:9
|
#: allianceauth/fleetactivitytracking/auth_hooks.py:9
|
||||||
msgid "Fleet Activity Tracking"
|
msgid "Fleet Activity Tracking"
|
||||||
msgstr "Відстеження активності флоту"
|
msgstr "Відстеження активності флотів"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/forms.py:6 allianceauth/srp/form.py:8
|
#: allianceauth/fleetactivitytracking/forms.py:6 allianceauth/srp/form.py:8
|
||||||
#: allianceauth/srp/templates/srp/management.html:35
|
#: allianceauth/srp/templates/srp/management.html:35
|
||||||
@@ -456,7 +457,7 @@ msgstr "Корабель"
|
|||||||
#: allianceauth/timerboard/templates/timerboard/view.html:202
|
#: allianceauth/timerboard/templates/timerboard/view.html:202
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:375
|
#: allianceauth/timerboard/templates/timerboard/view.html:375
|
||||||
msgid "Eve Time"
|
msgid "Eve Time"
|
||||||
msgstr "Час в грі"
|
msgstr "Ігровий час"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:33
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:33
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:36
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:36
|
||||||
@@ -560,16 +561,16 @@ msgstr "Fats"
|
|||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:4
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:4
|
||||||
msgid "Fatlink Corp Statistics"
|
msgid "Fatlink Corp Statistics"
|
||||||
msgstr "Статистика корпорації Fatlink"
|
msgstr "Статистика фатів корпорації"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:24
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:24
|
||||||
msgid "Average fats"
|
msgid "Average fats"
|
||||||
msgstr "Середній показник fats"
|
msgstr "Середній показник фатів"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:4
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:4
|
||||||
msgid "Fatlink statistics"
|
msgid "Fatlink statistics"
|
||||||
msgstr "Статистика Fatlink"
|
msgstr "Статистика фатів"
|
||||||
|
|
||||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:20
|
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:20
|
||||||
msgid "Ticker"
|
msgid "Ticker"
|
||||||
@@ -625,7 +626,7 @@ msgid ""
|
|||||||
"Cannot register the fleet participation for {character.character_name}. The "
|
"Cannot register the fleet participation for {character.character_name}. The "
|
||||||
"character needs to be online."
|
"character needs to be online."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Не можна зареєструвати участь в флоті для {character.character_name}. "
|
"Не вдалося зареєструвати участь в флоті для {character.character_name}. "
|
||||||
"Персонаж повинен бути в мережі."
|
"Персонаж повинен бути в мережі."
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/auth_hooks.py:17
|
#: allianceauth/groupmanagement/auth_hooks.py:17
|
||||||
@@ -659,8 +660,7 @@ msgstr ""
|
|||||||
#: allianceauth/groupmanagement/models.py:113
|
#: allianceauth/groupmanagement/models.py:113
|
||||||
msgid "Group is hidden from users but can still join with the correct link."
|
msgid "Group is hidden from users but can still join with the correct link."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Група прихована від користувачів, але можна приєднатися з правильним "
|
"Група прихована від користувачів, але можна приєднатися за посиланням."
|
||||||
"посиланням."
|
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:119
|
#: allianceauth/groupmanagement/models.py:119
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -1045,7 +1045,7 @@ msgstr "Ви вже є членом цієї групи."
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:358
|
#: allianceauth/groupmanagement/views.py:358
|
||||||
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:367
|
#: allianceauth/groupmanagement/views.py:367
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1062,7 +1062,7 @@ msgstr "Ви не є учасником цієї групи"
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/views.py:393
|
#: allianceauth/groupmanagement/views.py:393
|
||||||
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:409
|
#: allianceauth/groupmanagement/views.py:409
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1321,7 +1321,7 @@ msgstr "Всі прочитані повідомлення видалено."
|
|||||||
|
|
||||||
#: allianceauth/optimer/auth_hooks.py:10
|
#: allianceauth/optimer/auth_hooks.py:10
|
||||||
msgid "Fleet Operations"
|
msgid "Fleet Operations"
|
||||||
msgstr "Операції флоту"
|
msgstr "Флотові операції"
|
||||||
|
|
||||||
#: allianceauth/optimer/form.py:12
|
#: allianceauth/optimer/form.py:12
|
||||||
#: allianceauth/optimer/templates/optimer/fleetoptable.html:11
|
#: allianceauth/optimer/templates/optimer/fleetoptable.html:11
|
||||||
@@ -1345,7 +1345,7 @@ msgstr "Тип операції"
|
|||||||
#: allianceauth/optimer/form.py:17
|
#: allianceauth/optimer/form.py:17
|
||||||
#: allianceauth/srp/templates/srp/management.html:38
|
#: allianceauth/srp/templates/srp/management.html:38
|
||||||
msgid "Fleet Commander"
|
msgid "Fleet Commander"
|
||||||
msgstr "Командувач флоту"
|
msgstr "Командир флоту"
|
||||||
|
|
||||||
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
|
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
|
||||||
#: allianceauth/srp/templates/srp/data.html:91
|
#: allianceauth/srp/templates/srp/data.html:91
|
||||||
@@ -1400,7 +1400,7 @@ msgstr "Немає наступних таймерів."
|
|||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:33
|
#: allianceauth/optimer/templates/optimer/management.html:33
|
||||||
msgid "Past Fleet Operations"
|
msgid "Past Fleet Operations"
|
||||||
msgstr "Минулі флотові операції"
|
msgstr "Завершені флотові операції"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:37
|
#: allianceauth/optimer/templates/optimer/management.html:37
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:535
|
#: allianceauth/timerboard/templates/timerboard/view.html:535
|
||||||
@@ -1484,7 +1484,7 @@ 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:103
|
#: allianceauth/services/abstract.py:103
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -1505,7 +1505,7 @@ msgstr "Командир флоту:"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:8
|
#: allianceauth/services/forms.py:8
|
||||||
msgid "Fleet Comms:"
|
msgid "Fleet Comms:"
|
||||||
msgstr "Комунікації флоту:"
|
msgstr "Голосовий канал флоту:"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:9
|
#: allianceauth/services/forms.py:9
|
||||||
msgid "Fleet Type:"
|
msgid "Fleet Type:"
|
||||||
@@ -1545,7 +1545,7 @@ msgstr "Ні"
|
|||||||
|
|
||||||
#: allianceauth/services/forms.py:16
|
#: allianceauth/services/forms.py:16
|
||||||
msgid "Important?*"
|
msgid "Important?*"
|
||||||
msgstr "Важливо?*"
|
msgstr "Важливий?*"
|
||||||
|
|
||||||
#: allianceauth/services/forms.py:21 allianceauth/services/forms.py:31
|
#: allianceauth/services/forms.py:21 allianceauth/services/forms.py:31
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
@@ -1614,7 +1614,7 @@ msgstr "Ви не маєте прав на доступ до Discourse."
|
|||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:34
|
#: allianceauth/services/modules/discourse/views.py:34
|
||||||
msgid "You must have a main character set to access Discourse."
|
msgid "You must have a main character set to access Discourse."
|
||||||
msgstr "Ви повинні мати головний персонаж, щоб отримати доступ до Discourse."
|
msgstr "Ви повинні мати основний персонаж, щоб отримати доступ до Discourse."
|
||||||
|
|
||||||
#: allianceauth/services/modules/discourse/views.py:44
|
#: allianceauth/services/modules/discourse/views.py:44
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -1702,7 +1702,7 @@ msgstr "Відправлено трансляцію Jabber на %s"
|
|||||||
|
|
||||||
#: allianceauth/services/modules/openfire/views.py:144
|
#: allianceauth/services/modules/openfire/views.py:144
|
||||||
msgid "Set jabber password."
|
msgid "Set jabber password."
|
||||||
msgstr "Встановлення пароля Jabber."
|
msgstr "Встановити пароль Jabber."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:34
|
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||||
msgid "Activated forum account."
|
msgid "Activated forum account."
|
||||||
@@ -1713,7 +1713,7 @@ msgstr "Активований обліковий запис форуму."
|
|||||||
#: allianceauth/services/modules/phpbb3/views.py:78
|
#: allianceauth/services/modules/phpbb3/views.py:78
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:101
|
#: allianceauth/services/modules/phpbb3/views.py:101
|
||||||
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:53
|
#: allianceauth/services/modules/phpbb3/views.py:53
|
||||||
msgid "Deactivated forum account."
|
msgid "Deactivated forum account."
|
||||||
@@ -1721,11 +1721,11 @@ msgstr "Деактивований обліковий запис форуму."
|
|||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:70
|
#: allianceauth/services/modules/phpbb3/views.py:70
|
||||||
msgid "Reset forum password."
|
msgid "Reset forum password."
|
||||||
msgstr "Скидання пароля форуму."
|
msgstr "Скинути пароль форуму."
|
||||||
|
|
||||||
#: allianceauth/services/modules/phpbb3/views.py:98
|
#: allianceauth/services/modules/phpbb3/views.py:98
|
||||||
msgid "Set forum password."
|
msgid "Set forum password."
|
||||||
msgstr "Встановлення пароля форуму."
|
msgstr "Встановити пароль форуму."
|
||||||
|
|
||||||
#: allianceauth/services/modules/smf/views.py:52
|
#: allianceauth/services/modules/smf/views.py:52
|
||||||
msgid "Activated SMF account."
|
msgid "Activated SMF account."
|
||||||
@@ -1744,11 +1744,11 @@ msgstr "Деактивований обліковий запис SMF."
|
|||||||
|
|
||||||
#: allianceauth/services/modules/smf/views.py:95
|
#: allianceauth/services/modules/smf/views.py:95
|
||||||
msgid "Reset SMF password."
|
msgid "Reset SMF password."
|
||||||
msgstr "Скидання пароля SMF."
|
msgstr "Скинути пароль SMF."
|
||||||
|
|
||||||
#: allianceauth/services/modules/smf/views.py:121
|
#: allianceauth/services/modules/smf/views.py:121
|
||||||
msgid "Set SMF password."
|
msgid "Set SMF password."
|
||||||
msgstr "Встановлення пароля SMF."
|
msgstr "Встановити пароль SMF."
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1761,7 +1761,7 @@ msgstr "Оновити групи TS3"
|
|||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:5
|
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:5
|
||||||
msgid "Verify Teamspeak"
|
msgid "Verify Teamspeak"
|
||||||
msgstr "Перевірте Teamspeak"
|
msgstr "Перевірити Teamspeak"
|
||||||
|
|
||||||
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:10
|
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:10
|
||||||
msgid "Verify Teamspeak Identity"
|
msgid "Verify Teamspeak Identity"
|
||||||
@@ -1869,11 +1869,11 @@ msgstr "Керування послугами"
|
|||||||
|
|
||||||
#: allianceauth/services/templates/services/services.html:9
|
#: allianceauth/services/templates/services/services.html:9
|
||||||
msgid "Available Services"
|
msgid "Available Services"
|
||||||
msgstr "Доступні послуги"
|
msgstr "Доступні сервіси"
|
||||||
|
|
||||||
#: allianceauth/services/templates/services/services.html:14
|
#: allianceauth/services/templates/services/services.html:14
|
||||||
msgid "Service"
|
msgid "Service"
|
||||||
msgstr "Послуга"
|
msgstr "Сервіс"
|
||||||
|
|
||||||
#: allianceauth/services/templates/services/services.html:16
|
#: allianceauth/services/templates/services/services.html:16
|
||||||
msgid "Domain"
|
msgid "Domain"
|
||||||
@@ -1881,7 +1881,7 @@ msgstr "Домен"
|
|||||||
|
|
||||||
#: allianceauth/srp/auth_hooks.py:13
|
#: allianceauth/srp/auth_hooks.py:13
|
||||||
msgid "Ship Replacement"
|
msgid "Ship Replacement"
|
||||||
msgstr "Компенсація за корабель"
|
msgstr "Компенсації"
|
||||||
|
|
||||||
#: allianceauth/srp/form.py:9
|
#: allianceauth/srp/form.py:9
|
||||||
#: allianceauth/srp/templates/srp/management.html:36
|
#: allianceauth/srp/templates/srp/management.html:36
|
||||||
|
|||||||
Binary file not shown.
@@ -4,9 +4,10 @@
|
|||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
# Translators:
|
# Translators:
|
||||||
# Joel Falknau <ozirascal@gmail.com>, 2020
|
# Jesse . <sgeine@hotmail.com>, 2023
|
||||||
# Jesse . <sgeine@hotmail.com>, 2020
|
# Aaron BuBu <351793078@qq.com>, 2023
|
||||||
# Aaron BuBu <351793078@qq.com>, 2020
|
# Joel Falknau <ozirascal@gmail.com>, 2023
|
||||||
|
# Shen Yang, 2023
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -14,8 +15,8 @@ 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: 2022-10-09 18:20+1000\n"
|
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
|
||||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
|
||||||
"Last-Translator: Aaron BuBu <351793078@qq.com>, 2020\n"
|
"Last-Translator: Shen Yang, 2023\n"
|
||||||
"Language-Team: Chinese Simplified (https://app.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n"
|
"Language-Team: Chinese Simplified (https://app.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"
|
||||||
@@ -46,48 +47,48 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/authentication/models.py:80
|
#: allianceauth/authentication/models.py:80
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr ""
|
msgstr "英语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:81
|
#: allianceauth/authentication/models.py:81
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr "德语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:82
|
#: allianceauth/authentication/models.py:82
|
||||||
msgid "Spanish"
|
msgid "Spanish"
|
||||||
msgstr ""
|
msgstr "西班牙语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:83
|
#: allianceauth/authentication/models.py:83
|
||||||
msgid "Chinese Simplified"
|
msgid "Chinese Simplified"
|
||||||
msgstr ""
|
msgstr "简体中文"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:84
|
#: allianceauth/authentication/models.py:84
|
||||||
msgid "Russian"
|
msgid "Russian"
|
||||||
msgstr ""
|
msgstr "俄语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:85
|
#: allianceauth/authentication/models.py:85
|
||||||
msgid "Korean"
|
msgid "Korean"
|
||||||
msgstr ""
|
msgstr "韩语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:86
|
#: allianceauth/authentication/models.py:86
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr "法语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:87
|
#: allianceauth/authentication/models.py:87
|
||||||
msgid "Japanese"
|
msgid "Japanese"
|
||||||
msgstr ""
|
msgstr "日语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:88
|
#: allianceauth/authentication/models.py:88
|
||||||
msgid "Italian"
|
msgid "Italian"
|
||||||
msgstr ""
|
msgstr "意大利语"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:91
|
#: allianceauth/authentication/models.py:91
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr ""
|
msgstr "语言"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:96
|
#: allianceauth/authentication/models.py:96
|
||||||
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
||||||
msgid "Night Mode"
|
msgid "Night Mode"
|
||||||
msgstr ""
|
msgstr "夜间模式"
|
||||||
|
|
||||||
#: allianceauth/authentication/models.py:110
|
#: allianceauth/authentication/models.py:110
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -696,7 +697,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:215
|
#: allianceauth/groupmanagement/models.py:215
|
||||||
msgid "reason"
|
msgid "reason"
|
||||||
msgstr ""
|
msgstr "原因"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/models.py:215
|
#: allianceauth/groupmanagement/models.py:215
|
||||||
msgid "Reason why this name is reserved."
|
msgid "Reason why this name is reserved."
|
||||||
@@ -754,7 +755,7 @@ msgstr "操作者"
|
|||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:48
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:48
|
||||||
msgid "Removed"
|
msgid "Removed"
|
||||||
msgstr ""
|
msgstr "已移除"
|
||||||
|
|
||||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:60
|
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:60
|
||||||
msgid "All times displayed are EVE/UTC."
|
msgid "All times displayed are EVE/UTC."
|
||||||
@@ -1198,11 +1199,11 @@ msgstr "添加评论"
|
|||||||
|
|
||||||
#: allianceauth/notifications/models.py:21
|
#: allianceauth/notifications/models.py:21
|
||||||
msgid "danger"
|
msgid "danger"
|
||||||
msgstr ""
|
msgstr "危险"
|
||||||
|
|
||||||
#: allianceauth/notifications/models.py:22
|
#: allianceauth/notifications/models.py:22
|
||||||
msgid "warning"
|
msgid "warning"
|
||||||
msgstr ""
|
msgstr "警告"
|
||||||
|
|
||||||
#: allianceauth/notifications/models.py:23
|
#: allianceauth/notifications/models.py:23
|
||||||
msgid "info"
|
msgid "info"
|
||||||
@@ -1343,7 +1344,7 @@ msgstr "当前EVE游戏内时间"
|
|||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:26
|
#: allianceauth/optimer/templates/optimer/management.html:26
|
||||||
msgid "Next Fleet Operations"
|
msgid "Next Fleet Operations"
|
||||||
msgstr ""
|
msgstr "下一个舰队任务"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:30
|
#: allianceauth/optimer/templates/optimer/management.html:30
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:362
|
#: allianceauth/timerboard/templates/timerboard/view.html:362
|
||||||
@@ -1352,7 +1353,7 @@ msgstr "没有快到的时间节点,歇一会吧"
|
|||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:33
|
#: allianceauth/optimer/templates/optimer/management.html:33
|
||||||
msgid "Past Fleet Operations"
|
msgid "Past Fleet Operations"
|
||||||
msgstr ""
|
msgstr "过去的舰队任务"
|
||||||
|
|
||||||
#: allianceauth/optimer/templates/optimer/management.html:37
|
#: allianceauth/optimer/templates/optimer/management.html:37
|
||||||
#: allianceauth/timerboard/templates/timerboard/view.html:535
|
#: allianceauth/timerboard/templates/timerboard/view.html:535
|
||||||
@@ -2257,15 +2258,15 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/timerboard/models.py:15
|
#: allianceauth/timerboard/models.py:15
|
||||||
msgid "Shield"
|
msgid "Shield"
|
||||||
msgstr ""
|
msgstr "护盾"
|
||||||
|
|
||||||
#: allianceauth/timerboard/models.py:16
|
#: allianceauth/timerboard/models.py:16
|
||||||
msgid "Armor"
|
msgid "Armor"
|
||||||
msgstr ""
|
msgstr "装甲"
|
||||||
|
|
||||||
#: allianceauth/timerboard/models.py:17
|
#: allianceauth/timerboard/models.py:17
|
||||||
msgid "Hull"
|
msgid "Hull"
|
||||||
msgstr ""
|
msgstr "结构"
|
||||||
|
|
||||||
#: allianceauth/timerboard/models.py:18
|
#: allianceauth/timerboard/models.py:18
|
||||||
msgid "Final"
|
msgid "Final"
|
||||||
@@ -2273,11 +2274,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: allianceauth/timerboard/models.py:19
|
#: allianceauth/timerboard/models.py:19
|
||||||
msgid "Anchoring"
|
msgid "Anchoring"
|
||||||
msgstr ""
|
msgstr "铆钉"
|
||||||
|
|
||||||
#: allianceauth/timerboard/models.py:20
|
#: allianceauth/timerboard/models.py:20
|
||||||
msgid "Unanchoring"
|
msgid "Unanchoring"
|
||||||
msgstr ""
|
msgstr "解锚"
|
||||||
|
|
||||||
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:11
|
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:11
|
||||||
msgid "Delete Timer"
|
msgid "Delete Timer"
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ app = Celery('{{ project_name }}')
|
|||||||
# the configuration object to child processes.
|
# the configuration object to child processes.
|
||||||
app.config_from_object('django.conf:settings')
|
app.config_from_object('django.conf:settings')
|
||||||
|
|
||||||
|
# Automatically try to establish the connection to the AMQP broker on
|
||||||
|
# Celery startup if it is unavailable.
|
||||||
|
app.conf.broker_connection_retry_on_startup = True
|
||||||
|
|
||||||
# setup priorities ( 0 Highest, 9 Lowest )
|
# setup priorities ( 0 Highest, 9 Lowest )
|
||||||
app.conf.broker_transport_options = {
|
app.conf.broker_transport_options = {
|
||||||
'priority_steps': list(range(10)), # setup que to have 10 steps
|
'priority_steps': list(range(10)), # setup que to have 10 steps
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from ...admin import ServicesUserAdmin
|
|||||||
from . import __title__
|
from . import __title__
|
||||||
from .models import DiscordUser
|
from .models import DiscordUser
|
||||||
from .utils import LoggerAddTag
|
from .utils import LoggerAddTag
|
||||||
|
from .auth_hooks import DiscordService
|
||||||
|
|
||||||
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
||||||
|
|
||||||
@@ -27,6 +28,6 @@ class DiscordUserAdmin(ServicesUserAdmin):
|
|||||||
|
|
||||||
@admin.display(description='Discord Username', ordering='username')
|
@admin.display(description='Discord Username', ordering='username')
|
||||||
def _username(self, obj):
|
def _username(self, obj):
|
||||||
if obj.username and obj.discriminator:
|
return DiscordService.get_discord_username(
|
||||||
return f'{obj.username}#{obj.discriminator}'
|
username=obj.username, discriminator=obj.discriminator
|
||||||
return ''
|
)
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ from .models import DiscordUser
|
|||||||
from .urls import urlpatterns
|
from .urls import urlpatterns
|
||||||
from .utils import LoggerAddTag
|
from .utils import LoggerAddTag
|
||||||
from . import tasks, __title__
|
from . import tasks, __title__
|
||||||
|
from .app_settings import (
|
||||||
|
DISCORD_SYNC_NAMES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
||||||
@@ -30,6 +33,29 @@ 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}'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_discord_username(username:str, discriminator:str) -> str:
|
||||||
|
"""
|
||||||
|
Determine the Discord username (Old and new format)
|
||||||
|
:param username:
|
||||||
|
:type username:
|
||||||
|
:param discriminator:
|
||||||
|
:type discriminator:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if username and discriminator:
|
||||||
|
discord_username = f'{username}#{discriminator}'
|
||||||
|
|
||||||
|
# New Discord user name format
|
||||||
|
if discriminator == '0':
|
||||||
|
discord_username = f'@{username}'
|
||||||
|
else:
|
||||||
|
discord_username = ''
|
||||||
|
|
||||||
|
return discord_username
|
||||||
|
|
||||||
def delete_user(self, user: User, notify_user: bool = False) -> None:
|
def delete_user(self, user: User, notify_user: bool = False) -> None:
|
||||||
if self.user_has_account(user):
|
if self.user_has_account(user):
|
||||||
logger.debug('Deleting user %s %s account', user, self.name)
|
logger.debug('Deleting user %s %s account', user, self.name)
|
||||||
@@ -43,10 +69,19 @@ class DiscordService(ServicesHook):
|
|||||||
user_has_account = True
|
user_has_account = True
|
||||||
username = request.user.discord.username
|
username = request.user.discord.username
|
||||||
discriminator = request.user.discord.discriminator
|
discriminator = request.user.discord.discriminator
|
||||||
if username and discriminator:
|
|
||||||
discord_username = f'{username}#{discriminator}'
|
discord_username = self.get_discord_username(
|
||||||
else:
|
username=username, discriminator=discriminator
|
||||||
discord_username = ''
|
)
|
||||||
|
|
||||||
|
# if username and discriminator:
|
||||||
|
# discord_username = f'{username}#{discriminator}'
|
||||||
|
#
|
||||||
|
# # New Discord user name format
|
||||||
|
# if discriminator == '0':
|
||||||
|
# discord_username = f'@{username}'
|
||||||
|
# else:
|
||||||
|
# discord_username = ''
|
||||||
else:
|
else:
|
||||||
discord_username = ''
|
discord_username = ''
|
||||||
user_has_account = False
|
user_has_account = False
|
||||||
@@ -67,17 +102,18 @@ class DiscordService(ServicesHook):
|
|||||||
return has_perms
|
return has_perms
|
||||||
|
|
||||||
def sync_nickname(self, user):
|
def sync_nickname(self, user):
|
||||||
logger.debug('Syncing %s nickname for user %s', self.name, user)
|
if DISCORD_SYNC_NAMES:
|
||||||
if self.user_has_account(user):
|
logger.debug('Syncing %s nickname for user %s', self.name, user)
|
||||||
tasks.update_nickname.apply_async(
|
if self.user_has_account(user):
|
||||||
kwargs={
|
tasks.update_nickname.apply_async(
|
||||||
'user_pk': user.pk,
|
kwargs={
|
||||||
# since the new nickname is not yet in the DB we need to
|
'user_pk': user.pk,
|
||||||
# provide it manually to the task
|
# since the new nickname is not yet in the DB we need to
|
||||||
'nickname': user_formatted_nick(user)
|
# provide it manually to the task
|
||||||
},
|
'nickname': user_formatted_nick(user)
|
||||||
priority=SINGLE_TASK_PRIORITY
|
},
|
||||||
)
|
priority=SINGLE_TASK_PRIORITY
|
||||||
|
)
|
||||||
|
|
||||||
def sync_nicknames_bulk(self, users: list):
|
def sync_nicknames_bulk(self, users: list):
|
||||||
"""Sync nickname for a list of users in bulk.
|
"""Sync nickname for a list of users in bulk.
|
||||||
|
|||||||
@@ -81,11 +81,18 @@ class TestDiscordService(NoSocketsTestCase):
|
|||||||
self.assertFalse(DiscordUser.objects.filter(user=self.none_member).exists())
|
self.assertFalse(DiscordUser.objects.filter(user=self.none_member).exists())
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.tasks.update_nickname')
|
@patch(MODULE_PATH + '.tasks.update_nickname')
|
||||||
|
@patch(MODULE_PATH + '.auth_hooks.DISCORD_SYNC_NAMES', True)
|
||||||
def test_sync_nickname(self, mock_update_nickname):
|
def test_sync_nickname(self, mock_update_nickname):
|
||||||
service = self.service()
|
service = self.service()
|
||||||
service.sync_nickname(self.member)
|
service.sync_nickname(self.member)
|
||||||
self.assertTrue(mock_update_nickname.apply_async.called)
|
self.assertTrue(mock_update_nickname.apply_async.called)
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.tasks.update_nickname')
|
||||||
|
def test_sync_nickname_no_setting(self, mock_update_nickname):
|
||||||
|
service = self.service()
|
||||||
|
service.sync_nickname(self.member)
|
||||||
|
self.assertFalse(mock_update_nickname.apply_async.called)
|
||||||
|
|
||||||
@patch(MODULE_PATH + '.tasks.update_nicknames_bulk')
|
@patch(MODULE_PATH + '.tasks.update_nicknames_bulk')
|
||||||
def test_sync_nicknames_bulk(self, mock_update_nicknames_bulk):
|
def test_sync_nicknames_bulk(self, mock_update_nicknames_bulk):
|
||||||
service = self.service()
|
service = self.service()
|
||||||
@@ -150,3 +157,23 @@ class TestDiscordService(NoSocketsTestCase):
|
|||||||
self.assertTemplateUsed(service.service_ctrl_template)
|
self.assertTemplateUsed(service.service_ctrl_template)
|
||||||
self.assertIn('/discord/reset/', response)
|
self.assertIn('/discord/reset/', response)
|
||||||
self.assertIn('/discord/deactivate/', response)
|
self.assertIn('/discord/deactivate/', response)
|
||||||
|
|
||||||
|
def test_new_discord_username_format(self):
|
||||||
|
"""
|
||||||
|
Test if we get Discord's new username format
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
# given
|
||||||
|
username = 'william_riker'
|
||||||
|
discriminator = '0' # Seems to be returned as 0 for Discord's new username format
|
||||||
|
|
||||||
|
# when
|
||||||
|
discord_username = DiscordService.get_discord_username(
|
||||||
|
username=username, discriminator=discriminator
|
||||||
|
)
|
||||||
|
|
||||||
|
# then
|
||||||
|
expected_username = '@william_riker'
|
||||||
|
self.assertEqual(first=discord_username, second=expected_username)
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ class TestServiceFeatures(TransactionTestCase):
|
|||||||
self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
|
||||||
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.auth_hooks.DISCORD_SYNC_NAMES', True)
|
||||||
def test_when_name_of_main_changes_then_discord_nick_is_updated(
|
def test_when_name_of_main_changes_then_discord_nick_is_updated(
|
||||||
self, requests_mocker
|
self, requests_mocker
|
||||||
):
|
):
|
||||||
@@ -185,6 +186,7 @@ class TestServiceFeatures(TransactionTestCase):
|
|||||||
self.assertTrue(nick_updated)
|
self.assertTrue(nick_updated)
|
||||||
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
|
||||||
|
|
||||||
|
@patch(MODULE_PATH + '.auth_hooks.DISCORD_SYNC_NAMES', True)
|
||||||
def test_when_name_of_main_changes_and_user_deleted_then_account_is_deleted(
|
def test_when_name_of_main_changes_and_user_deleted_then_account_is_deleted(
|
||||||
self, requests_mocker
|
self, requests_mocker
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -92,12 +92,8 @@
|
|||||||
{% include "allianceauth/admin-status/celery_bar_partial.html" with label="failed" level="danger" tasks_count=tasks_failed %}
|
{% include "allianceauth/admin-status/celery_bar_partial.html" with label="failed" level="danger" tasks_count=tasks_failed %}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
{% blocktranslate with running_count=tasks_running|default_if_none:"?"|intcomma %}
|
<span id="task-counts">?</span> {% translate 'running' %} |
|
||||||
{{ running_count }} running |
|
<span id="queued-tasks-count">?</span> {% translate 'queued' %}
|
||||||
{% endblocktranslate %}
|
|
||||||
{% blocktranslate with queue_length=task_queue_length|default_if_none:"?"|intcomma %}
|
|
||||||
{{ queue_length }} queued
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,3 +101,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const elemRunning = document.getElementById("task-counts");
|
||||||
|
const elemQueued = document.getElementById("queued-tasks-count");
|
||||||
|
|
||||||
|
fetch('{% url "authentication:task_counts" %}')
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
throw new Error("Something went wrong");
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
const running = responseJson.tasks_running;
|
||||||
|
if (running == null) {
|
||||||
|
elemRunning.textContent = "N/A";
|
||||||
|
} else {
|
||||||
|
elemRunning.textContent = running.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const queued = responseJson.tasks_queued;
|
||||||
|
if (queued == null) {
|
||||||
|
elemQueued.textContent = "N/A";
|
||||||
|
} else {
|
||||||
|
elemQueued.textContent = queued.toLocaleString();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
elemRunning.textContent = "ERROR";
|
||||||
|
elemQueued.textContent = "ERROR";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{% load navactive %}
|
{% load navactive %}
|
||||||
{% load auth_notifications %}
|
{% load auth_notifications %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{{ LANGUAGE_CODE }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import amqp.exceptions
|
|
||||||
import requests
|
import requests
|
||||||
from celery.app import app_or_default
|
|
||||||
from packaging.version import InvalidVersion, Version as Pep440Version
|
from packaging.version import InvalidVersion, Version as Pep440Version
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
@@ -11,8 +8,9 @@ from django.conf import settings
|
|||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
from allianceauth import __version__
|
from allianceauth import __version__
|
||||||
|
from allianceauth.authentication.task_statistics.counters import (
|
||||||
from ..authentication.task_statistics.counters import dashboard_results
|
dashboard_results,
|
||||||
|
)
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@@ -48,18 +46,15 @@ def status_overview() -> dict:
|
|||||||
response = {
|
response = {
|
||||||
"notifications": list(),
|
"notifications": list(),
|
||||||
"current_version": __version__,
|
"current_version": __version__,
|
||||||
"task_queue_length": None,
|
|
||||||
"tasks_succeeded": 0,
|
"tasks_succeeded": 0,
|
||||||
"tasks_retried": 0,
|
"tasks_retried": 0,
|
||||||
"tasks_failed": 0,
|
"tasks_failed": 0,
|
||||||
"tasks_total": 0,
|
"tasks_total": 0,
|
||||||
"tasks_hours": 0,
|
"tasks_hours": 0,
|
||||||
"earliest_task": None,
|
"earliest_task": None,
|
||||||
"tasks_running": 0
|
|
||||||
}
|
}
|
||||||
response.update(_current_notifications())
|
response.update(_current_notifications())
|
||||||
response.update(_current_version_summary())
|
response.update(_current_version_summary())
|
||||||
response.update({'task_queue_length': _fetch_celery_queue_length()})
|
|
||||||
response.update(_celery_stats())
|
response.update(_celery_stats())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -74,27 +69,9 @@ def _celery_stats() -> dict:
|
|||||||
"tasks_total": results.total,
|
"tasks_total": results.total,
|
||||||
"tasks_hours": results.hours,
|
"tasks_hours": results.hours,
|
||||||
"earliest_task": results.earliest_task,
|
"earliest_task": results.earliest_task,
|
||||||
"tasks_running": results.running,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _fetch_celery_queue_length() -> Optional[int]:
|
|
||||||
try:
|
|
||||||
app = app_or_default(None)
|
|
||||||
with app.connection_or_acquire() as conn:
|
|
||||||
result = conn.default_channel.queue_declare(
|
|
||||||
queue=getattr(settings, 'CELERY_DEFAULT_QUEUE', 'celery'),
|
|
||||||
passive=True
|
|
||||||
)
|
|
||||||
return result.message_count
|
|
||||||
except amqp.exceptions.ChannelError:
|
|
||||||
# Queue doesn't exist, probably empty
|
|
||||||
return 0
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Failed to get celery queue length")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _current_notifications() -> dict:
|
def _current_notifications() -> dict:
|
||||||
"""returns the newest 5 announcement issues"""
|
"""returns the newest 5 announcement issues"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
65
allianceauth/utils/counters.py
Normal file
65
allianceauth/utils/counters.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""Counters."""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from redis import Redis
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
from .cache import get_redis_client
|
||||||
|
|
||||||
|
|
||||||
|
class ItemCounter:
|
||||||
|
"""A process safe item counter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- name: Unique name for the counter
|
||||||
|
- minimum: Counter can not go below the minimum, when set
|
||||||
|
- redis: A Redis client. Will use AA's cache client by default
|
||||||
|
"""
|
||||||
|
|
||||||
|
CACHE_KEY_BASE = "allianceauth-item-counter"
|
||||||
|
DEFAULT_CACHE_TIMEOUT = 24 * 3600
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, name: str, minimum: Optional[int] = None, redis: Optional[Redis] = None
|
||||||
|
) -> None:
|
||||||
|
if not name:
|
||||||
|
raise ValueError("Must define a name")
|
||||||
|
|
||||||
|
self._name = str(name)
|
||||||
|
self._minimum = minimum
|
||||||
|
self._redis = get_redis_client() if not redis else redis
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cache_key(self) -> str:
|
||||||
|
return f"{self.CACHE_KEY_BASE}-{self._name}"
|
||||||
|
|
||||||
|
def reset(self, init_value: int = 0):
|
||||||
|
"""Reset counter to initial value."""
|
||||||
|
with self._redis.lock(f"{self.CACHE_KEY_BASE}-reset"):
|
||||||
|
if self._minimum is not None and init_value < self._minimum:
|
||||||
|
raise ValueError("Can not reset below minimum")
|
||||||
|
|
||||||
|
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
|
||||||
|
|
||||||
|
def incr(self, delta: int = 1):
|
||||||
|
"""Increment counter by delta."""
|
||||||
|
try:
|
||||||
|
cache.incr(self._cache_key, delta)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def decr(self, delta: int = 1):
|
||||||
|
"""Decrement counter by delta."""
|
||||||
|
with self._redis.lock(f"{self.CACHE_KEY_BASE}-decr"):
|
||||||
|
if self._minimum is not None and self.value() == self._minimum:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cache.decr(self._cache_key, delta)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def value(self) -> Optional[int]:
|
||||||
|
"""Return current value or None if not yet initialized."""
|
||||||
|
return cache.get(self._cache_key)
|
||||||
120
allianceauth/utils/tests/test_counters.py
Normal file
120
allianceauth/utils/tests/test_counters.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from allianceauth.utils.counters import ItemCounter
|
||||||
|
|
||||||
|
MODULE_PATH = "allianceauth.utils.counters"
|
||||||
|
|
||||||
|
COUNTER_NAME = "test-counter"
|
||||||
|
|
||||||
|
|
||||||
|
class TestItemCounter(TestCase):
|
||||||
|
def test_can_create_counter(self):
|
||||||
|
# when
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
# then
|
||||||
|
self.assertIsInstance(counter, ItemCounter)
|
||||||
|
|
||||||
|
def test_can_reset_counter_to_default(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
# when
|
||||||
|
counter.reset()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 0)
|
||||||
|
|
||||||
|
def test_can_reset_counter_to_custom_value(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
# when
|
||||||
|
counter.reset(42)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 42)
|
||||||
|
|
||||||
|
def test_can_increment_counter_by_default(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.incr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 1)
|
||||||
|
|
||||||
|
def test_can_increment_counter_by_custom_value(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.incr(8)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 8)
|
||||||
|
|
||||||
|
def test_can_decrement_counter_by_default(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(9)
|
||||||
|
# when
|
||||||
|
counter.decr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 8)
|
||||||
|
|
||||||
|
def test_can_decrement_counter_by_custom_value(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(9)
|
||||||
|
# when
|
||||||
|
counter.decr(8)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 1)
|
||||||
|
|
||||||
|
def test_can_decrement_counter_below_zero(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.decr(1)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), -1)
|
||||||
|
|
||||||
|
def test_can_not_decrement_counter_below_minimum(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME, minimum=0)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.decr(1)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 0)
|
||||||
|
|
||||||
|
def test_can_not_reset_counter_below_minimum(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME, minimum=0)
|
||||||
|
# when/then
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
counter.reset(-1)
|
||||||
|
|
||||||
|
def test_can_not_init_without_name(self):
|
||||||
|
# when/then
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
ItemCounter(name="")
|
||||||
|
|
||||||
|
def test_can_ignore_invalid_values_when_incrementing(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
with patch(MODULE_PATH + ".cache.incr") as m:
|
||||||
|
m.side_effect = ValueError
|
||||||
|
counter.incr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 0)
|
||||||
|
|
||||||
|
def test_can_ignore_invalid_values_when_decrementing(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(1)
|
||||||
|
# when
|
||||||
|
with patch(MODULE_PATH + ".cache.decr") as m:
|
||||||
|
m.side_effect = ValueError
|
||||||
|
counter.decr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 1)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
PROTOCOL=https://
|
PROTOCOL=https://
|
||||||
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
||||||
DOMAIN=%DOMAIN%
|
DOMAIN=%DOMAIN%
|
||||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.6.1
|
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.8.0
|
||||||
|
|
||||||
# Nginx Proxy Manager
|
# Nginx Proxy Manager
|
||||||
PROXY_HTTP_PORT=80
|
PROXY_HTTP_PORT=80
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM python:3.9-slim
|
FROM python:3.9-slim
|
||||||
ARG AUTH_VERSION=v3.6.1
|
ARG AUTH_VERSION=v3.8.0
|
||||||
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
||||||
ENV VIRTUAL_ENV=/opt/venv
|
ENV VIRTUAL_ENV=/opt/venv
|
||||||
ENV AUTH_USER=allianceauth
|
ENV AUTH_USER=allianceauth
|
||||||
|
|||||||
49
docs/development/aa_core/code-style
Normal file
49
docs/development/aa_core/code-style
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Code Style
|
||||||
|
|
||||||
|
## Pre-Commit
|
||||||
|
|
||||||
|
Alliance Auth is a team effort with developers of various skill levels and background. To avoid significant drift or formatting changes between developers we use [pre-commit](https://pre-commit.com/) to apply a very minimal set of formatting checks to code contributed to the project.
|
||||||
|
|
||||||
|
Pre-commit is also very popular with our Community Apps and may be significantly more opinionated or looser depending on the project.
|
||||||
|
|
||||||
|
To get started, `pip install pre-commit`, then `pre-commit install` to add the git hooks.
|
||||||
|
|
||||||
|
Before any code is "git push"-ed, pre-commit will check it for uniformity and correct it if possible
|
||||||
|
|
||||||
|
```bash
|
||||||
|
check python ast.....................................(no files to check)Skipped
|
||||||
|
check yaml...........................................(no files to check)Skipped
|
||||||
|
check json...........................................(no files to check)Skipped
|
||||||
|
check toml...........................................(no files to check)Skipped
|
||||||
|
check xml............................................(no files to check)Skipped
|
||||||
|
check for merge conflicts............................(no files to check)Skipped
|
||||||
|
check for added large files..........................(no files to check)Skipped
|
||||||
|
detect private key...................................(no files to check)Skipped
|
||||||
|
check for case conflicts.............................(no files to check)Skipped
|
||||||
|
debug statements (python)............................(no files to check)Skipped
|
||||||
|
fix python encoding pragma...........................(no files to check)Skipped
|
||||||
|
fix utf-8 byte order marker..........................(no files to check)Skipped
|
||||||
|
mixed line ending....................................(no files to check)Skipped
|
||||||
|
trim trailing whitespace.............................(no files to check)Skipped
|
||||||
|
check that executables have shebangs.................(no files to check)Skipped
|
||||||
|
fix end of files.....................................(no files to check)Skipped
|
||||||
|
Check .editorconfig rules............................(no files to check)Skipped
|
||||||
|
django-upgrade.......................................(no files to check)Skipped
|
||||||
|
pyupgrade............................................(no files to check)Skipped
|
||||||
|
```
|
||||||
|
|
||||||
|
## Editorconfig
|
||||||
|
|
||||||
|
[Editorconfig](https://editorconfig.org/) is supported my most IDE's to streamline the most common editor disparities. While checked by our pre-commit file, using this in your IDE (Either automatically or via a plugin) will minimize the corrections that may need to be made.
|
||||||
|
|
||||||
|
## Doc Strings
|
||||||
|
|
||||||
|
We prefer either [PEP-287](https://peps.python.org/pep-0287/)/[reStructuredText](https://docutils.sourceforge.io/rst.html) or [Google](https://google.github.io/styleguide/pyguide.html#381-docstrings) Docstrings.
|
||||||
|
|
||||||
|
These can be used to automatically generate our Sphinx documentation in either format.
|
||||||
|
|
||||||
|
## Best Practice
|
||||||
|
|
||||||
|
It is advisable to avoid wide formatting changes on code that is not being modified by an MR. Further to this, automated code formatting should be kept to a minimal when modifying sections of existing files.
|
||||||
|
|
||||||
|
If you are contributing whole modules or rewriting large sections of code you may use any legible code formatting valid under Python.
|
||||||
@@ -7,4 +7,5 @@ This section contains important information on how to develop Alliance Auth itse
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
documentation
|
documentation
|
||||||
|
code-style
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -399,16 +399,10 @@ Update & install basic tools before installing further Python packages:
|
|||||||
pip install -U pip setuptools wheel
|
pip install -U pip setuptools wheel
|
||||||
```
|
```
|
||||||
|
|
||||||
You can install **Alliance Auth** with the following command. This will install AA and all its Python dependencies.
|
You can install **Alliance Auth** with the following command. This will install AA, AA's Python dependencies, superlance for memory monitoring and gunicorn as a wsgi server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install allianceauth
|
pip install allianceauth superlance gunicorn
|
||||||
```
|
|
||||||
|
|
||||||
You should also install Gunicorn now unless you want to use another WSGI server (see [Gunicorn](#gunicorn) for details):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install gunicorn
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Create Alliance Auth project
|
#### Create Alliance Auth project
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ You will need to have [Gunicorn](gunicorn.md) or some other WSGI server setup fo
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Ubuntu 1804, 2004. 2204:
|
Ubuntu 1804, 2004, 2204:
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install nginx
|
sudo apt-get install nginx
|
||||||
```
|
```
|
||||||
@@ -59,18 +59,13 @@ CentOS Stream 8, Stream 9:
|
|||||||
sudo dnf install nginx
|
sudo dnf install nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a config file in `/etc/nginx/sites-available` and call it `alliance-auth.conf` or whatever your preferred name is.
|
Create a config file in `/etc/nginx/sites-available` (`/etc/nginx/conf.d` on CentOS) and call it `alliance-auth.conf` or whatever your preferred name is.
|
||||||
|
|
||||||
Create a symbolic link to enable the site
|
Create a symbolic link to enable the site (not needed on CentOS):
|
||||||
```bash
|
```bash
|
||||||
ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/
|
ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/
|
||||||
```
|
```
|
||||||
|
|
||||||
### CentOS
|
|
||||||
|
|
||||||
Create a config file in `/etc/nginx/conf.d` and call it `alliance-auth.conf` or whatever your preferred name is.
|
|
||||||
|
|
||||||
|
|
||||||
### Basic config
|
### Basic config
|
||||||
|
|
||||||
Copy this basic config into your config file. Make whatever changes you feel are necessary.
|
Copy this basic config into your config file. Make whatever changes you feel are necessary.
|
||||||
|
|||||||
@@ -28,43 +28,11 @@ command=/home/allianceserver/venv/auth/bin/celery -A myauth worker -l info
|
|||||||
|
|
||||||
Celery workers often have memory leaks and will therefore grow in size over time. While the Alliance Auth team is working hard to ensure Auth is free of memory leaks some may still be cause by bugs in different versions of libraries or community apps. It is therefore good practice to enable features that protect against potential memory leaks.
|
Celery workers often have memory leaks and will therefore grow in size over time. While the Alliance Auth team is working hard to ensure Auth is free of memory leaks some may still be cause by bugs in different versions of libraries or community apps. It is therefore good practice to enable features that protect against potential memory leaks.
|
||||||
|
|
||||||
There are two ways to protect against memory leaks:
|
|
||||||
|
|
||||||
- Worker
|
|
||||||
- Supervisor
|
|
||||||
|
|
||||||
### Worker
|
|
||||||
|
|
||||||
Celery workers can be configured to automatically restart if they grow above a defined memory threshold. Restarts will be graceful, so current tasks will be allowed to complete before the restart happens.
|
|
||||||
|
|
||||||
To add protection against memory leaks add the following to the command configuration of your worker in the `supervisor.conf` file. This sets the upper limit to 256MB.
|
|
||||||
|
|
||||||
```text
|
|
||||||
--max-memory-per-child 262144
|
|
||||||
```
|
|
||||||
|
|
||||||
Full example:
|
|
||||||
|
|
||||||
```text
|
|
||||||
command=/home/allianceserver/venv/auth/bin/celery -A myauth worker --max-memory-per-child 262144
|
|
||||||
```
|
|
||||||
|
|
||||||
```eval_rst
|
```eval_rst
|
||||||
.. hint::
|
.. hint::
|
||||||
The 256 MB limit is just an example and should be adjusted to your system configuration. We would suggest to not go below 128MB though, since new workers start with around 80 MB already. Also take into consideration that this value is per worker and that you properly have more than one worker running in your system (if your workers run as processes, which is the default).
|
The 256 MB limit is just an example and should be adjusted to your system configuration. We would suggest to not go below 128MB though, since new workers start with around 80 MB already. Also take into consideration that this value is per worker and that you may have more than one worker running in your system.
|
||||||
```
|
```
|
||||||
|
|
||||||
```eval_rst
|
|
||||||
.. warning::
|
|
||||||
The ``max-memory-per-child`` parameter only works when workers run as processes (which is the default). It does not work for threads.
|
|
||||||
```
|
|
||||||
|
|
||||||
```eval_rst
|
|
||||||
.. note::
|
|
||||||
Alternatively, you can also limit the number of runs per worker until a restart is performed with the worker parameter ``max-tasks-per-child``. This can also protect against memory leaks if you set the threshold is low enough. However, it is less precise since than using ``max-memory-per-child``.
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the [official Celery documentation](https://docs.celeryproject.org/en/stable/userguide/workers.html#max-memory-per-child-setting) for more information about these two worker parameters.
|
|
||||||
|
|
||||||
### Supervisor
|
### Supervisor
|
||||||
|
|
||||||
@@ -78,35 +46,68 @@ To setup install superlance into your venv with:
|
|||||||
pip install superlance
|
pip install superlance
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then add `memmon` to your `supervisor.conf`. Here is an example setup with a worker that runs with gevent:
|
You can then add `memmon` to your `supervisor.conf`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[eventlistener:memmon]
|
[eventlistener:memmon]
|
||||||
command=/home/allianceserver/venv/auth/bin/memmon -p worker=512MB
|
command=/home/allianceserver/venv/auth/bin/memmon -p worker=256MB
|
||||||
directory=/home/allianceserver/myauth
|
directory=/home/allianceserver/myauth
|
||||||
events=TICK_60
|
events=TICK_60
|
||||||
```
|
```
|
||||||
|
|
||||||
This setup will check the memory consumption of the program "worker" every 60 secs and automatically restart it if is goes above 512 MB. Note that it will use the stop signal configured in supervisor, which is `TERM` by default. `TERM` will cause a "warm shutdown" of your worker, so all currently running tasks are completed before the restart.
|
This setup will check the memory consumption of the program "worker" every 60 secs and automatically restart it if is goes above 256 MB. Note that it will use the stop signal configured in supervisor, which is `TERM` by default. `TERM` will cause a "warm shutdown" of your worker, so all currently running tasks are completed before the restart.
|
||||||
|
|
||||||
Again, the 512 MB is just an example and should be adjusted to fit your system configuration.
|
Again, the 256 MB is just an example and should be adjusted to fit your system configuration.
|
||||||
|
|
||||||
## Increasing task throughput
|
## Increasing task throughput
|
||||||
|
|
||||||
Celery tasks are designed to run concurrently, so one obvious way to increase task throughput is run more tasks in parallel.
|
Celery tasks are designed to run concurrently, so one obvious way to increase task throughput is run more tasks in parallel. The default celery worker configuration will allow either of these options to be configured out of the box.
|
||||||
|
|
||||||
|
### Extra Worker Threads
|
||||||
|
|
||||||
|
The easiest way to increate throughput can be achieved by increasing the `numprocs` parameter of the suprvisor process. For example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[program:worker]
|
||||||
|
...
|
||||||
|
numprocs=2
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
This number will be multiplied by your concurrency setting,
|
||||||
|
|
||||||
|
```
|
||||||
|
numprocs * concurency = workers
|
||||||
|
```
|
||||||
|
|
||||||
|
increasing this number will require a modification to the memmon settings as each `numproc` worker will get a unique name for example with `numproc=3`
|
||||||
|
|
||||||
|
```text
|
||||||
|
[eventlistener:memmon]
|
||||||
|
...
|
||||||
|
command=... -p worker_00=256MB -p worker_01=256MB -p worker_02=256MB
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
```eval_rst
|
||||||
|
.. hint::
|
||||||
|
You will want to experiment with different settings to find the optimal. One way to generate task load and verify your configuration is to run a model update with the following command:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
celery -A myauth call allianceauth.eveonline.tasks.run_model_update
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Concurrency
|
### Concurrency
|
||||||
|
|
||||||
This can be achieved by the setting the concurrency parameter of the celery worker to a higher number. For example:
|
This can be achieved by the setting the concurrency parameter of the celery worker to a higher number. For example:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
--concurrency=4
|
--concurrency=10
|
||||||
```
|
```
|
||||||
|
|
||||||
However, there is a catch: In the default configuration each worker will spawn as it's own process. So increasing the number of workers will increase both CPU load and memory consumption in your system.
|
|
||||||
|
|
||||||
The recommended number of workers is one per core, which is what you get automatically with the default configuration. Going beyond that can quickly reduce you overall system performance. i.e. the response time for Alliance Auth or other apps running on the same system may take a hit while many tasks are running.
|
|
||||||
|
|
||||||
```eval_rst
|
```eval_rst
|
||||||
.. hint::
|
.. hint::
|
||||||
The optimal number will hugely depend on your individual system configuration and you may want to experiment with different settings to find the optimal. One way to generate task load and verify your configuration is to run a model update with the following command:
|
The optimal number will hugely depend on your individual system configuration and you may want to experiment with different settings to find the optimal. One way to generate task load and verify your configuration is to run a model update with the following command:
|
||||||
@@ -117,43 +118,6 @@ The recommended number of workers is one per core, which is what you get automat
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Processes vs. Threads
|
|
||||||
|
|
||||||
A better way to increase concurrency without impacting is to switch from processes to threads for celery workers. In general celery workers perform better with processes when tasks are primarily CPU bound. And they perform better with threads when tasks that are primarily I/O bound.
|
|
||||||
|
|
||||||
Alliance Auth tasks are primarily I/O bound (most tasks are fetching data from ESI and/or updating the local database), so threads are clearly the better choice for Alliance Auth. However, there is a catch. Celery's out-of-the-box support for threads is limited and additional packages and configurations is required to make it work. Nonetheless, the performance gain - especially in smaller systems - is significant, so it may well be worth the additional configuration complexity.
|
|
||||||
|
|
||||||
```eval_rst
|
|
||||||
.. warning::
|
|
||||||
One important feature that no longer works with threads is the worker parameter ``--max-memory-per-child`` that protects against memory leaks. But you can alternatively use supervisor_ to monitor and restart your workers.
|
|
||||||
```
|
|
||||||
|
|
||||||
See also the also [this guide](https://www.distributedpython.com/2018/10/26/celery-execution-pool/) on more information about how to configure the execution pool for workers.
|
|
||||||
|
|
||||||
### Setting up for threads
|
|
||||||
|
|
||||||
First, you need to install a threads packages. Celery supports both gevent and eventlet. We will go with gevent, since it's newer and better supported. Should you encounter any issues with gevent, you may want to try eventlet.
|
|
||||||
|
|
||||||
To install gevent make sure you are in your venv and install the following:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install gevent
|
|
||||||
```
|
|
||||||
|
|
||||||
Next we need to reconfigure the workers to use gevent threads. For that add the following parameters to your worker config:
|
|
||||||
|
|
||||||
```text
|
|
||||||
--pool=gevent --concurrency=10
|
|
||||||
```
|
|
||||||
|
|
||||||
Full example:
|
|
||||||
|
|
||||||
```text
|
|
||||||
command=/home/allianceserver/venv/auth/bin/celery -A myauth worker --pool=gevent --concurrency=10
|
|
||||||
```
|
|
||||||
|
|
||||||
Make sure to restart supervisor to activate the changes.
|
|
||||||
|
|
||||||
```eval_rst
|
```eval_rst
|
||||||
.. hint::
|
.. hint::
|
||||||
The optimal number of concurrent workers will be different for every system and we recommend experimenting with different figures to find the optimal for your system. Note, that the example of 10 threads is conservative and should work even with smaller systems.
|
The optimal number of concurrent workers will be different for every system and we recommend experimenting with different figures to find the optimal for your system. Note, that the example of 10 threads is conservative and should work even with smaller systems.
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ dependencies = [
|
|||||||
"celery>=5.2.0,<6",
|
"celery>=5.2.0,<6",
|
||||||
"django-bootstrap-form",
|
"django-bootstrap-form",
|
||||||
"django-celery-beat>=2.3.0",
|
"django-celery-beat>=2.3.0",
|
||||||
"django-esi>=4.0.1",
|
"django-esi>=5.0.0",
|
||||||
"django-redis>=5.2.0",
|
"django-redis>=5.2.0",
|
||||||
"django-registration>=3.3,<3.4",
|
"django-registration>=3.3,<3.4",
|
||||||
"django-sortedm2m",
|
"django-sortedm2m",
|
||||||
|
|||||||
13
thirdparty/Supervisor/auth-mumble.conf
vendored
13
thirdparty/Supervisor/auth-mumble.conf
vendored
@@ -1,13 +0,0 @@
|
|||||||
[program:auth-mumble]
|
|
||||||
command=python authenticator.py
|
|
||||||
directory=/home/allianceserver/allianceauth/thirdparty/Mumble
|
|
||||||
user=allianceserver
|
|
||||||
numprocs=1
|
|
||||||
stdout_logfile=/home/allianceserver/allianceauth/log/authenticator.log
|
|
||||||
stderr_logfile=/home/allianceserver/allianceauth/log/authenticator.log
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=10
|
|
||||||
stopwaitsecs = 600
|
|
||||||
killasgroup=true
|
|
||||||
priority=500
|
|
||||||
28
thirdparty/Supervisor/auth.conf
vendored
28
thirdparty/Supervisor/auth.conf
vendored
@@ -1,28 +0,0 @@
|
|||||||
[program:celerybeat]
|
|
||||||
command=celery -A alliance_auth beat
|
|
||||||
directory=/home/allianceserver/allianceauth
|
|
||||||
user=allianceserver
|
|
||||||
stdout_logfile=/home/allianceserver/allianceauth/log/beat.log
|
|
||||||
stderr_logfile=/home/allianceserver/allianceauth/log/beat.log
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=10
|
|
||||||
priority=998
|
|
||||||
|
|
||||||
[program:celeryd]
|
|
||||||
command=celery -A alliance_auth worker
|
|
||||||
directory=/home/allianceserver/allianceauth
|
|
||||||
user=allianceserver
|
|
||||||
numprocs=1
|
|
||||||
stdout_logfile=/home/allianceserver/allianceauth/log/worker.log
|
|
||||||
stderr_logfile=/home/allianceserver/allianceauth/log/worker.log
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=10
|
|
||||||
stopwaitsecs = 600
|
|
||||||
killasgroup=true
|
|
||||||
priority=998
|
|
||||||
|
|
||||||
[group:auth]
|
|
||||||
programs=celerybeat,celeryd
|
|
||||||
priority=999
|
|
||||||
Reference in New Issue
Block a user