Compare commits

..

82 Commits
v3.6.0 ... v3.x

Author SHA1 Message Date
Ariel Rin
054ef27fa4 Version Bump 3.8.1 2024-02-17 19:39:14 +10:00
Ariel Rin
97e224b8e6 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1593
2024-02-17 09:30:43 +00:00
Ariel Rin
3b8fa415bc Updates for project Alliance Auth 2024-02-17 09:30:43 +00:00
Ariel Rin
b94fd7ed19 I18N Maintenance 2024-02-17 18:52:42 +10:00
Ariel Rin
d1dac61135 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1563
2024-02-17 08:48:55 +00:00
Ariel Rin
d2a095217f Updates for project Alliance Auth 2024-02-17 08:48:55 +00:00
Ariel Rin
5e9b47cf79 Merge branch 'docs-improvements' into 'master'
Docs improvements

See merge request allianceauth/allianceauth!1574
2024-01-15 05:25:51 +00:00
Ariel Rin
853826c140 Merge branch 'add-email-timeout' into 'master'
Add email timeout

See merge request allianceauth/allianceauth!1580
2024-01-15 05:19:57 +00:00
ErikKalkoken
9c7de58989 Add email timeout 2024-01-02 11:48:27 +01:00
Ariel Rin
3de988369f Merge branch 'fix-orphan-tokens' into 'master'
Fix orphan tokens and remove unused messages from backends

Closes #1391

See merge request allianceauth/allianceauth!1570
2023-12-25 09:48:15 +00:00
Peter Pfeufer
6e3219fd1b [FIX] Grammar and spelling 2023-12-17 20:21:11 +01:00
Peter Pfeufer
8aeb061635 [ADD] Remark on how to stop Gunicorn when testing 2023-12-17 17:45:29 +01:00
Peter Pfeufer
84e2107b62 [CHANGE] Switch to adduser for Ubuntu
This will create the users' hoe directory and make it a no-login user in one command.
2023-12-17 17:42:08 +01:00
Ariel Rin
20fcf5efa4 Merge branch 'docs' into 'master'
Docs fixes

See merge request allianceauth/allianceauth!1569
2023-12-17 03:49:41 +00:00
colcrunch
c15b955d5e Make pre-commit happy 2023-12-11 18:37:35 -05:00
colcrunch
65e1545a66 Remove all references to messages as they are never relayed to the user. 2023-12-11 18:16:34 -05:00
colcrunch
c558a980e1 Add more detail to error message displayed on failed alt login. 2023-12-11 18:14:09 -05:00
colcrunch
bd8ef84862 Delete tokens that can not be used for logins. 2023-12-11 18:13:24 -05:00
Ariel Rin
42e96d2f14 close the python section to fix mysql tabs/steps 2023-12-11 22:15:12 +10:00
Ariel Rin
23a3dd1ab9 Merge branch 'fantabular' into 'master'
Doc Modernisation

Closes #1331

See merge request allianceauth/allianceauth!1550
2023-12-05 13:00:50 +00:00
Ariel Rin
81e5bc5337 Merge branch 'master' into 'fantabular'
# Conflicts:
#   docs/maintenance/tuning/index.md
2023-12-02 01:38:18 +00:00
Ariel Rin
a8ef844fe7 Merge branch 'tuning' into 'master'
Docs: Sql Tuning and python version comparison

See merge request allianceauth/allianceauth!1545
2023-12-02 01:31:52 +00:00
Ariel Rin
9ce1939040 Version Bump 3.8.0 2023-11-09 00:00:28 +10:00
Ariel Rin
322131cd4f Update source language strings 2023-11-08 23:56:16 +10:00
Ariel Rin
55e6e92da5 Merge branch 'transifex' into 'master'
Update from Transifex

See merge request allianceauth/allianceauth!1562
2023-11-08 13:48:26 +00:00
Ariel Rin
e5d29629a5 Update from Transifex 2023-11-08 13:48:26 +00:00
Ariel Rin
26e187e4c8 Merge branch 'assign-users-on-group-form' into 'master'
New Feature: Assign/remove users on group form

See merge request allianceauth/allianceauth!1543
2023-11-08 13:05:25 +00:00
Erik Kalkoken
3480c4e0e8 New Feature: Assign/remove users on group form 2023-11-08 13:05:24 +00:00
Ariel Rin
1544f097e0 Merge branch 'fix-leave-tab-for-autoleave' into 'master'
Fix leave tab for autoleave

See merge request allianceauth/allianceauth!1536
2023-11-08 13:04:27 +00:00
Erik Kalkoken
2477c31656 Fix leave tab for autoleave 2023-11-08 13:04:26 +00:00
Ariel Rin
0dc631d69e Merge branch 'master' into 'master'
Stop renaming discord nick when setting has it disabled

See merge request allianceauth/allianceauth!1540
2023-11-08 13:04:12 +00:00
Dusty Meg
2a9981cdb9 Stop renaming discord nick when setting has it disabled 2023-11-08 13:04:11 +00:00
Ariel Rin
004c48b8ad mild formatting changes 2023-11-08 13:45:43 +10:00
Ariel Rin
4d66b7d456 and gitlab ci update for new docs 2023-11-01 13:28:32 +10:00
Ariel Rin
77e5747a23 move tox tests to new dependency format 2023-11-01 13:15:18 +10:00
Ariel Rin
6118c0ddec override non compatible dark mode check css 2023-11-01 01:10:39 +10:00
Ariel Rin
ce25deeca1 minor formatting 2023-10-31 23:52:34 +10:00
Ariel Rin
60084de3db Pygments lexer name 'math' is not known 2023-10-31 23:43:52 +10:00
Ariel Rin
e16c68e255 increase anchor generation 2023-10-31 23:43:45 +10:00
Ariel Rin
bf14e9c7c3 merged back into sphinxcontrib-django 2023-10-31 23:36:42 +10:00
Ariel Rin
98e91fe207 MyST conversion 2023-10-31 23:31:41 +10:00
Ariel Rin
7024552c4e remove ubuntu 1804 2023-10-27 22:20:27 +10:00
Ariel Rin
a0719e4b86 tabify 2023-10-27 22:20:11 +10:00
Ariel Rin
906c589f14 more automated upgrades 2023-10-27 22:19:28 +10:00
Ariel Rin
ffb526ab0c find and replace fixes, will introduce errors 2023-10-27 16:37:53 +10:00
Ariel Rin
b9d128259e correct path 2023-10-27 16:37:30 +10:00
Ariel Rin
13d866bd0d RST to MyST, pyproject doc dependencies, add sphinx tabs 2023-10-27 16:37:09 +10:00
Ariel Rin
ea1887b9ec mild clarifications 2023-10-26 11:26:39 +10:00
Ariel Rin
d2f8c2a42f Merge branch 'master' of gitlab.com:allianceauth/allianceauth into tuning 2023-10-26 10:49:29 +10:00
Ariel Rin
424246df26 Version Bump 3.7.1 2023-10-19 14:00:29 +10:00
Ariel Rin
563e2210ef Bump Django-ESI to >=5.0.0 2023-10-19 13:11:35 +10:00
Ariel Rin
02a1078005 Merge branch 'remove-thirdparty' into 'master'
Remove outdated supervisor configs - refer to docs

See merge request allianceauth/allianceauth!1533
2023-10-19 03:03:35 +00:00
Ariel Rin
30107de44e Merge branch 'docs-precommit' into 'master'
Add code-style docs

Closes #1379

See merge request allianceauth/allianceauth!1534
2023-10-19 03:03:14 +00:00
Ariel Rin
200e8f2ff1 initial sql tuning and python version comparison 2023-10-11 12:33:17 +10:00
Ariel Rin
77a08cd218 Add code-style docs 2023-10-07 22:32:19 +10:00
Ariel Rin
e5a09027e5 Remove outdated supervisor configs - refer to docs 2023-10-07 21:53:03 +10:00
Ariel Rin
52b6c5d341 Rework default celery configuration and documentation 2023-10-07 19:31:53 +10:00
Ariel Rin
8b895b76b5 Version Bump 3.7.0 2023-10-07 15:27:11 +10:00
Ariel Rin
babd71702f Merge branch 'docs' into 'master'
Remove CentOS Section from NGINX docs

See merge request allianceauth/allianceauth!1531
2023-10-07 04:59:12 +00:00
Ariel Rin
3ec3cbdff7 Merge branch 'fix-tasks-running' into 'master'
Fix tasks running counter

See merge request allianceauth/allianceauth!1529
2023-10-07 04:52:04 +00:00
Erik Kalkoken
51611e1237 Fix tasks running counter 2023-10-07 04:52:03 +00:00
Ariel Rin
39519bab91 Merge branch 'new-discord-username-format' into 'master'
[ADD] Respect and display the new Discord username format when eligible

See merge request allianceauth/allianceauth!1526
2023-10-07 04:40:46 +00:00
Ariel Rin
90dc6a4d4c Merge branch 'fix-reference-before-assignment' into 'master'
[FIX] Reference before assignment

Closes #1369 and #1375

See merge request allianceauth/allianceauth!1530
2023-10-07 04:40:00 +00:00
Ariel Rin
53ffd7f885 Merge branch 'set-lang-attribute' into 'master'
[ADD] Language code to page and language selector

See merge request allianceauth/allianceauth!1528
2023-10-07 04:39:01 +00:00
Ariel Rin
efc7475228 Merge branch 'celery-broker-connection' into 'master'
Prepare our Celery config for Celery 6

See merge request allianceauth/allianceauth!1532
2023-10-07 04:38:21 +00:00
Peter Pfeufer
380c41400b [CHANGE] Updating celery.py to prevent deprecation warning
```
[2023-08-14 06:41:04,904: WARNING/MainProcess] /mnt/sda1/Development/Python/AllianceAuth/venv-3.11/lib/python3.11/site-packages/celery/worker/consumer/consumer.py:498: CPendingDeprecationWarning: The broker_connection_retry configuration setting will no longer determine
whether broker connection retries are made during startup in Celery 6.0 and above.
If you wish to retain the existing behavior for retrying connections on startup,
you should set broker_connection_retry_on_startup to True.
```
2023-09-15 11:42:47 +02:00
colcrunch
079c12a72e Remove CentOS heading and add notes about the differing config methods to the relevant lines in the install section. 2023-09-07 07:11:05 -04:00
Peter Pfeufer
4f1ebedc44 [FIX] Reference before assignment
`ownership` doesn't exist at this point.
To get the main character, `user` is used here.


```
Traceback (most recent call last):
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner
    response = get_response(request)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/esi/decorators.py", line 116, in _wrapped_view
    return view_func(request, token, *args, **kwargs)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/allianceauth/authentication/views.py", line 156, in sso_login
    user = authenticate(token=token)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/views/decorators/debug.py", line 42, in sensitive_variables_wrapper
    return func(*func_args, **func_kwargs)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/contrib/auth/__init__.py", line 77, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/allianceauth/authentication/backends.py", line 68, in authenticate
    if ownership.user.profile.main_character.character_id != token.character_id:
UnboundLocalError: local variable 'ownership' referenced before assignment
```
2023-09-01 20:46:00 +02:00
Peter Pfeufer
66822107e3 [ADD] Language code to page and language selector 2023-08-28 21:40:28 +02:00
Peter Pfeufer
7856cd5ce4 [ADD] Respect and display the new Discord username format when eligible 2023-08-26 00:47:08 +02:00
Ariel Rin
36b3077caa Version Bump 3.6.1 2023-08-10 14:37:27 +10:00
Ariel Rin
1786f3a642 Merge branch 'prepare-base-template-for-public-views' into 'master'
[FIX] Templates prepared for public views

See merge request allianceauth/allianceauth!1525
2023-08-10 04:23:48 +00:00
Peter Pfeufer
55927c6f15 [FIX] Allow messages also for non-logged-in user to be displayed 2023-08-08 23:20:43 +02:00
Peter Pfeufer
8fbe0ba45d [CHANGE] Comment 2023-08-07 07:23:21 +02:00
Peter Pfeufer
1563805ddb [ADD] CCP's "No Character" character image as user menu image for public 2023-08-05 18:51:12 +02:00
Ariel Rin
c58ed53369 Merge branch 'fix-negative-running-tasks' into 'master'
Fix negative running tasks

See merge request allianceauth/allianceauth!1524
2023-08-05 09:17:42 +00:00
Peter Pfeufer
32128ace1c [FIX] Better explanation in local.py project template 2023-08-02 23:35:25 +02:00
Peter Pfeufer
7290eaad7e [FIX] Notifications menu item removed in public views 2023-08-02 23:22:14 +02:00
Peter Pfeufer
f23d4f4dd1 [FIX] base.html prepared for public views
Certain things need to be behind `{% if user.is_authenticated %}` in order for the base template to play nice with public views.
2023-08-02 23:17:28 +02:00
ErikKalkoken
ab3f10e6f2 Add more tests for ItemCounter 2023-08-02 16:11:24 +02:00
ErikKalkoken
20187cc73e Add locks to ensure process safety 2023-08-02 15:58:36 +02:00
ErikKalkoken
1f55fbfccc Add minimum to ItemCounter and refactor redis client init 2023-08-02 15:41:47 +02:00
138 changed files with 5128 additions and 3369 deletions

View File

@@ -195,7 +195,7 @@ build-test:
test-docs: test-docs:
<<: *only-default <<: *only-default
image: python:3.10-bullseye image: python:3.11-bullseye
script: script:
- tox -e docs - tox -e docs

View File

@@ -7,11 +7,11 @@ version: 2
# Set the version of Python and other tools you might need # Set the version of Python and other tools you might need
build: build:
os: ubuntu-20.04 os: ubuntu-22.04
apt_packages: apt_packages:
- redis - redis
tools: tools:
python: "3.8" python: "3.11"
# Build documentation in the docs/ directory with Sphinx # Build documentation in the docs/ directory with Sphinx
sphinx: sphinx:
@@ -20,7 +20,10 @@ sphinx:
# Optionally build your docs in additional formats such as PDF and ePub # Optionally build your docs in additional formats such as PDF and ePub
formats: all formats: all
# Optionally set the version of Python and requirements required to build your docs # Python requirements required to build your docs
python: python:
install: install:
- requirements: docs/requirements.txt - method: pip
path: .
extra_requirements:
- docs

View File

@@ -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]

View File

@@ -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
View 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

View File

@@ -17,7 +17,7 @@ An auth system for EVE Online to help in-game organizations manage online servic
- [Documentation](http://allianceauth.rtfd.io) - [Documentation](http://allianceauth.rtfd.io)
- [Support](#support) - [Support](#support)
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases) - [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
- [Developer Team](#developer-team) - [Developer Team](#development-team)
- [Contributing](#contributing) - [Contributing](#contributing)
## Overview ## Overview

View File

@@ -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.0' __version__ = '3.8.1'
__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__}'

View File

@@ -2,7 +2,6 @@ import logging
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.contrib import messages
from .models import UserProfile, CharacterOwnership, OwnershipRecord from .models import UserProfile, CharacterOwnership, OwnershipRecord
@@ -41,9 +40,7 @@ class StateBackend(ModelBackend):
if ownership.user.profile.main_character: if ownership.user.profile.main_character:
if ownership.user.profile.main_character.character_id == token.character_id: if ownership.user.profile.main_character.character_id == token.character_id:
return ownership.user return ownership.user
else: ## this is an alt, enforce main only. else: # this is an alt, enforce main only.
if request:
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account.")
return None return None
else: else:
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.') logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
@@ -65,10 +62,8 @@ 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:
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.")
return None return None
token.user = user token.user = user

View 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

View File

@@ -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,
) )

View File

@@ -5,59 +5,27 @@ import logging
from typing import List, Optional from typing import List, Optional
from pytz import utc from pytz import utc
from redis import Redis, RedisError from redis import Redis
from allianceauth.utils.cache import get_redis_client from .helpers import get_redis_client_or_stub
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class _RedisStub:
"""Stub of a Redis client.
It's purpose is to prevent EventSeries objects from trying to access Redis
when it is not available. e.g. when the Sphinx docs are rendered by readthedocs.org.
"""
def delete(self, *args, **kwargs):
pass
def incr(self, *args, **kwargs):
return 0
def zadd(self, *args, **kwargs):
pass
def zcount(self, *args, **kwargs):
pass
def zrangebyscore(self, *args, **kwargs):
pass
class EventSeries: class EventSeries:
"""API for recording and analyzing a series of events.""" """API for recording and analyzing a series of events."""
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES" _ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
def __init__(self, key_id: str, redis: Redis = None) -> None: def __init__(self, key_id: str, redis: Optional[Redis] = None) -> None:
self._redis = get_redis_client() if not redis else redis self._redis = get_redis_client_or_stub() if not redis else redis
try:
if not self._redis.ping():
raise RuntimeError()
except (AttributeError, RedisError, RuntimeError):
logger.exception(
"Failed to establish a connection with Redis. "
"This EventSeries object is disabled.",
)
self._redis = _RedisStub()
self._key_id = str(key_id) self._key_id = str(key_id)
self.clear() self.clear()
@property @property
def is_disabled(self): def is_disabled(self):
"""True when this object is disabled, e.g. Redis was not available at startup.""" """True when this object is disabled, e.g. Redis was not available at startup."""
return isinstance(self._redis, _RedisStub) return hasattr(self._redis, "IS_STUB")
@property @property
def _key_counter(self): def _key_counter(self):
@@ -97,7 +65,7 @@ class EventSeries:
self._redis.delete(self._key_counter) self._redis.delete(self._key_counter)
def count(self, earliest: dt.datetime = None, latest: dt.datetime = None) -> int: def count(self, earliest: dt.datetime = None, latest: dt.datetime = None) -> int:
"""Count of events, can be restricted to given timeframe. """Count of events, can be restricted to given time frame.
Args: Args:
- earliest: Date of first events to count(inclusive), or -infinite if not specified - earliest: Date of first events to count(inclusive), or -infinite if not specified

View File

@@ -1,44 +1,49 @@
"""Helpers for Task Statistics.""" """Helpers for Task Statistics."""
from typing import Optional import logging
from django.core.cache import cache from redis import Redis, RedisError
from allianceauth.utils.cache import get_redis_client
logger = logging.getLogger(__name__)
class ItemCounter: class _RedisStub:
"""A process safe item counter.""" """Stub of a Redis client.
CACHE_KEY_BASE = "allianceauth-item-counter" It's purpose is to prevent EventSeries objects from trying to access Redis
DEFAULT_CACHE_TIMEOUT = 24 * 3600 when it is not available. e.g. when the Sphinx docs are rendered by readthedocs.org.
"""
def __init__(self, name: str) -> None: IS_STUB = True
if not name:
raise ValueError("Must define a name")
self._name = str(name) def delete(self, *args, **kwargs):
pass
@property def incr(self, *args, **kwargs):
def _cache_key(self) -> str: return 0
return f"{self.CACHE_KEY_BASE}-{self._name}"
def reset(self, init_value: int = 0): def zadd(self, *args, **kwargs):
"""Reset counter to initial value.""" pass
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
def incr(self, delta: int = 1): def zcount(self, *args, **kwargs):
"""Increment counter by delta.""" pass
try:
cache.incr(self._cache_key, delta)
except ValueError:
pass
def decr(self, delta: int = 1): def zrangebyscore(self, *args, **kwargs):
"""Decrement counter by delta.""" pass
try:
cache.decr(self._cache_key, delta)
except ValueError:
pass
def value(self) -> Optional[int]:
"""Return current value or None if not yet initialized.""" def get_redis_client_or_stub() -> Redis:
return cache.get(self._cache_key) """Return AA's default cache client or a stub if Redis is not available."""
redis = get_redis_client()
try:
if not redis.ping():
raise RuntimeError()
except (AttributeError, RedisError, RuntimeError):
logger.exception(
"Failed to establish a connection with Redis. "
"This EventSeries object is disabled.",
)
return _RedisStub()
return redis

View File

@@ -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()

View File

@@ -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)

View File

@@ -1,48 +1,19 @@
import datetime as dt import datetime as dt
from unittest.mock import patch
from pytz import utc from pytz import utc
from redis import RedisError
from django.test import TestCase from django.test import TestCase
from django.utils.timezone import now from django.utils.timezone import now
from allianceauth.authentication.task_statistics.event_series import ( from allianceauth.authentication.task_statistics.event_series import (
EventSeries, EventSeries,
_RedisStub,
) )
from allianceauth.authentication.task_statistics.helpers import _RedisStub
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series" MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
class TestEventSeries(TestCase): class TestEventSeries(TestCase):
def test_should_abort_without_redis_client(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock:
mock.return_value = None
events = EventSeries("dummy")
# then
self.assertTrue(events._redis, _RedisStub)
self.assertTrue(events.is_disabled)
def test_should_disable_itself_if_redis_not_available_1(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
mock_get_master_client.return_value.ping.side_effect = RedisError
events = EventSeries("dummy")
# then
self.assertIsInstance(events._redis, _RedisStub)
self.assertTrue(events.is_disabled)
def test_should_disable_itself_if_redis_not_available_2(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
mock_get_master_client.return_value.ping.return_value = False
events = EventSeries("dummy")
# then
self.assertIsInstance(events._redis, _RedisStub)
self.assertTrue(events.is_disabled)
def test_should_add_event(self): def test_should_add_event(self):
# given # given
events = EventSeries("dummy") events = EventSeries("dummy")
@@ -166,3 +137,15 @@ class TestEventSeries(TestCase):
results = events.all() results = events.all()
# then # then
self.assertEqual(len(results), 2) self.assertEqual(len(results), 2)
def test_should_not_report_as_disabled_when_initialized_normally(self):
# given
events = EventSeries("dummy")
# when/then
self.assertFalse(events.is_disabled)
def test_should_report_as_disabled_when_initialized_with_redis_stub(self):
# given
events = EventSeries("dummy", redis=_RedisStub())
# when/then
self.assertTrue(events.is_disabled)

View File

@@ -0,0 +1,28 @@
from unittest import TestCase
from unittest.mock import patch
from redis import RedisError
from allianceauth.authentication.task_statistics.helpers import (
_RedisStub, get_redis_client_or_stub,
)
MODULE_PATH = "allianceauth.authentication.task_statistics.helpers"
class TestGetRedisClient(TestCase):
def test_should_return_mock_if_redis_not_available_1(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
mock_get_master_client.return_value.ping.side_effect = RedisError
result = get_redis_client_or_stub()
# then
self.assertIsInstance(result, _RedisStub)
def test_should_return_mock_if_redis_not_available_2(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
mock_get_master_client.return_value.ping.return_value = False
result = get_redis_client_or_stub()
# then
self.assertIsInstance(result, _RedisStub)

View File

@@ -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 %}

View 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)

View File

@@ -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):

View 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}
)

View File

@@ -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'),
] ]

View File

@@ -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')
@@ -168,7 +171,13 @@ def sso_login(request, token):
request.session['registration_uid'] = user.pk request.session['registration_uid'] = user.pk
# Go to Step 2 # Go to Step 2
return redirect('registration_register') return redirect('registration_register')
messages.error(request, _('Unable to authenticate as the selected character.')) # Logging in with an alt is not allowed due to security concerns.
token.delete()
messages.error(
request,
_('Unable to authenticate as the selected character. '
'Please log in with the main character associated with this account.')
)
return redirect(settings.LOGIN_URL) return redirect(settings.LOGIN_URL)
@@ -268,8 +277,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 +311,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)

View File

@@ -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():

View File

@@ -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)

View File

@@ -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">

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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: 2024-02-17 18:50+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,49 @@ 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:178
msgid "Unable to authenticate as the selected character." msgid ""
"Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "" msgstr ""
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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:307
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:312
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 +315,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 +646,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 +688,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 +973,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 +1114,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 +1452,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 +2172,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 +2192,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 +2252,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 ""

View File

@@ -4,10 +4,10 @@
# 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
# Young Anexo, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# trenus, 2023 # trenus, 2023
# #
#, fuzzy #, fuzzy
@@ -15,8 +15,8 @@ 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: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: trenus, 2023\n" "Last-Translator: trenus, 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"
@@ -33,7 +33,7 @@ msgstr "Google Analytics Universal"
msgid "Google Analytics V4" msgid "Google Analytics V4"
msgstr "Google Analytics V4" msgstr "Google Analytics V4"
#: 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 ""
"Se necesita un personaje principal para realizar esa acción. Añade uno a " "Se necesita un personaje principal para realizar esa acción. Añade uno a "
@@ -48,63 +48,68 @@ msgstr "E-mail"
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 "No puedes añadir o eliminar estos grupos restringidos: %s" msgstr "No puedes añadir o eliminar estos grupos restringidos: %s"
#: allianceauth/authentication/models.py:80 #: allianceauth/authentication/models.py:71
msgid "English" msgid "English"
msgstr "Inglés" msgstr "Inglés"
#: allianceauth/authentication/models.py:81 #: allianceauth/authentication/models.py:72
msgid "German" msgid "German"
msgstr "Alemán" msgstr "Alemán"
#: allianceauth/authentication/models.py:82 #: allianceauth/authentication/models.py:73
msgid "Spanish" msgid "Spanish"
msgstr "Español" msgstr "Español"
#: allianceauth/authentication/models.py:83 #: allianceauth/authentication/models.py:74
msgid "Chinese Simplified" msgid "Chinese Simplified"
msgstr "Chino Simplificado" msgstr "Chino Simplificado"
#: allianceauth/authentication/models.py:84 #: allianceauth/authentication/models.py:75
msgid "Russian" msgid "Russian"
msgstr "Ruso" msgstr "Ruso"
#: allianceauth/authentication/models.py:85 #: allianceauth/authentication/models.py:76
msgid "Korean" msgid "Korean"
msgstr "Coreano" msgstr "Coreano"
#: allianceauth/authentication/models.py:86 #: allianceauth/authentication/models.py:77
msgid "French" msgid "French"
msgstr "Francés" msgstr "Francés"
#: allianceauth/authentication/models.py:87 #: allianceauth/authentication/models.py:78
msgid "Japanese" msgid "Japanese"
msgstr "Japonés" msgstr "Japonés"
#: allianceauth/authentication/models.py:88 #: allianceauth/authentication/models.py:79
msgid "Italian" msgid "Italian"
msgstr "Italiano" msgstr "Italiano"
#: allianceauth/authentication/models.py:91 #: allianceauth/authentication/models.py:80
msgid "Ukrainian"
msgstr ""
#: allianceauth/authentication/models.py:96
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: allianceauth/authentication/models.py:96 #: 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 "Modo Nocturno" msgstr "Modo Nocturno"
#: 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 "Estado cambiado a: %s" msgstr "Estado cambiado a: %s"
#: 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 "El estado de su usuario es ahora: %(state)s" msgstr "El estado de su usuario es ahora: %(state)s"
#: 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 "Página principal" msgstr "Página principal"
@@ -162,8 +167,50 @@ msgstr "Corporación"
msgid "Alliance" msgid "Alliance"
msgstr "Allianza" msgstr "Allianza"
#: 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 "Acciones"
#: 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 "Personaje"
#: 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 "Ingresar" msgstr "Ingresar"
@@ -197,7 +244,7 @@ msgstr "Registrar"
msgid "Invalid or expired activation link." msgid "Invalid or expired activation link."
msgstr "Enlace de activacion expirado o invalido" msgstr "Enlace de activacion expirado o invalido"
#: 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 "
@@ -206,45 +253,47 @@ msgstr ""
"No se puede cambiar de personaje principal a %(char)s: personaje " "No se puede cambiar de personaje principal a %(char)s: personaje "
"perteneciente a otra cuenta." "perteneciente a otra cuenta."
#: 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 "Se ha cambiado tu personaje principal ha %(char)s" msgstr "Se ha cambiado tu personaje principal ha %(char)s"
#: 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 "Se ha agregado a %(name)s a tu cuenta" msgstr "Se ha agregado a %(name)s a tu cuenta"
#: 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 ""
"Se fallo en agregar a %(name)s a tu cuenta: Ya se encuentra registrado en " "Se fallo en agregar a %(name)s a tu cuenta: Ya se encuentra registrado en "
"otra cuenta." "otra cuenta."
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "Imposible validar con el personaje seleccionado." "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "El token de registracion expiro." msgstr "El token de registracion expiro."
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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 ""
"Confirmacion de mail enviada. Por favor siga el enlace para confirmar " "Confirmacion de mail enviada. Por favor siga el enlace para confirmar "
#: allianceauth/authentication/views.py:257 #: allianceauth/authentication/views.py:307
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "" msgstr ""
"Se ha confirmado su direccion de mail. Por favor igrese su token para " "Se ha confirmado su direccion de mail. Por favor igrese su token para "
"continuar." "continuar."
#: allianceauth/authentication/views.py:262 #: allianceauth/authentication/views.py:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "En este momento no se permite el registro de nuevas cuentas." msgstr "En este momento no se permite el registro de nuevas cuentas."
@@ -287,19 +336,6 @@ msgstr "Sin registro"
msgid "Last update:" msgid "Last update:"
msgstr "Ultima Actualizacion:" msgstr "Ultima Actualizacion:"
#: 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 "Personaje"
#: 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
@@ -636,19 +672,24 @@ msgstr ""
msgid "Group Management" msgid "Group Management"
msgstr "Manejo de Grupo" msgstr "Manejo de Grupo"
#: allianceauth/groupmanagement/forms.py:15 #: allianceauth/groupmanagement/forms.py:18
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
msgid "Users"
msgstr "Usuarios"
#: 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 "Este nombre ha sido reservado y no puede utilizarse para grupos." msgstr "Este nombre ha sido reservado y no puede utilizarse para grupos."
#: allianceauth/groupmanagement/forms.py:25 #: allianceauth/groupmanagement/forms.py:62
msgid "(auto)" msgid "(auto)"
msgstr "(auto)" msgstr "(auto)"
#: 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 "Ya existe un grupo con ese nombre." msgstr "Ya existe un grupo con ese nombre."
#: allianceauth/groupmanagement/models.py:105 #: allianceauth/groupmanagement/models.py:104
msgid "" msgid ""
"Internal group, users cannot see, join or request to join this " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
@@ -658,13 +699,13 @@ msgstr ""
"grupo.<br>Se utiliza para grupos como Miembros, Corp_*, Alliance_*, " "grupo.<br>Se utiliza para grupos como Miembros, Corp_*, Alliance_*, "
"etc.<br><b>Anula las opciones Oculto y Abierto cuando se selecciona.</b>" "etc.<br><b>Anula las opciones Oculto y Abierto cuando se selecciona.</b>"
#: 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 ""
"El grupo está oculto para los usuarios, pero aún pueden unirse con el enlace" "El grupo está oculto para los usuarios, pero aún pueden unirse con el enlace"
" correcto." " correcto."
#: 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."
@@ -673,7 +714,7 @@ msgstr ""
"soliciten.<br>Si el grupo no está abierto, los usuarios necesitarán que su " "soliciten.<br>Si el grupo no está abierto, los usuarios necesitarán que su "
"solicitud sea aprobada manualmente." "solicitud sea aprobada manualmente."
#: 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 "
@@ -685,7 +726,7 @@ msgstr ""
"grupo.<br>Auth no eliminará automáticamente a los usuarios de este grupo " "grupo.<br>Auth no eliminará automáticamente a los usuarios de este grupo "
"cuando ya no estén autenticados." "cuando ya no estén autenticados."
#: 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."
@@ -693,7 +734,7 @@ msgstr ""
"El grupo está restringido. Esto significa que para añadir o eliminar " "El grupo está restringido. Esto significa que para añadir o eliminar "
"usuarios de este grupo se requiere un superusuario administrador." "usuarios de este grupo se requiere un superusuario administrador."
#: allianceauth/groupmanagement/models.py:144 #: allianceauth/groupmanagement/models.py:143
msgid "" msgid ""
"Group leaders can process requests for this group. Use the " "Group leaders 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 "
@@ -703,7 +744,7 @@ msgstr ""
" permiso <code>auth.group_management</code> para permitir que un usuario " " permiso <code>auth.group_management</code> para permitir que un usuario "
"gestione todos los grupos.<br>" "gestione todos los grupos.<br>"
#: 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 "
@@ -713,7 +754,7 @@ msgstr ""
"grupo. Utilice el <code>permiso auth.group_management</code> para permitir " "grupo. Utilice el <code>permiso auth.group_management</code> para permitir "
"que un usuario gestione todos los grupos.<br>" "que un usuario gestione todos los grupos.<br>"
#: 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>"
@@ -721,42 +762,42 @@ msgstr ""
"Los estados que figuren en esta lista podrán unirse a este grupo siempre que" "Los estados que figuren en esta lista podrán unirse a este grupo siempre que"
" dispongan de los permisos adecuados.<br>" " dispongan de los permisos adecuados.<br>"
#: 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 ""
"Breve descripción <i>(máx. 512 caracteres)</i> del grupo que se muestra a " "Breve descripción <i>(máx. 512 caracteres)</i> del grupo que se muestra a "
"los usuarios." "los usuarios."
#: allianceauth/groupmanagement/models.py:178 #: allianceauth/groupmanagement/models.py:177
msgid "Can request non-public groups" msgid "Can request non-public groups"
msgstr "Se pueden solicitar grupos no públicos" msgstr "Se pueden solicitar grupos no públicos"
#: allianceauth/groupmanagement/models.py:209 #: allianceauth/groupmanagement/models.py:208
msgid "name" msgid "name"
msgstr "nombre" msgstr "nombre"
#: 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 "Nombre que no se puede utilizar para los grupos." msgstr "Nombre que no se puede utilizar para los grupos."
#: allianceauth/groupmanagement/models.py:215 #: allianceauth/groupmanagement/models.py:214
msgid "reason" msgid "reason"
msgstr "razón" msgstr "razón"
#: 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 "Razón por la que este nombre está reservado." msgstr "Razón por la que este nombre está reservado."
#: allianceauth/groupmanagement/models.py:218 #: allianceauth/groupmanagement/models.py:217
msgid "created by" msgid "created by"
msgstr "creado por" msgstr "creado por"
#: allianceauth/groupmanagement/models.py:223 #: allianceauth/groupmanagement/models.py:222
msgid "created at" msgid "created at"
msgstr "creado en" msgstr "creado en"
#: 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 "Fecha de creación de esta entrada" msgstr "Fecha de creación de esta entrada"
@@ -983,26 +1024,26 @@ msgstr "Solicitudes de Grupo"
msgid "Group Membership" msgid "Group Membership"
msgstr "Membresia de Grupo" msgstr "Membresia de Grupo"
#: 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 "El usuario %(user)s fue removido del grupo %(group)s" msgstr "El usuario %(user)s fue removido del grupo %(group)s"
#: 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 "El usuario no existe en ese grupos" msgstr "El usuario no existe en ese grupos"
#: allianceauth/groupmanagement/views.py:168 #: allianceauth/groupmanagement/views.py:171
msgid "Group does not exist" msgid "Group does not exist"
msgstr "El grupo no existe" msgstr "El grupo no existe"
#: 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 "Solicitud aceptada de %(mainchar)s a %(group)s" msgstr "Solicitud aceptada de %(mainchar)s a %(group)s"
#: 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 "
@@ -1011,18 +1052,18 @@ msgstr ""
"Ocurrio un error cuando se intento procesar la informacion de %(mainchar)s " "Ocurrio un error cuando se intento procesar la informacion de %(mainchar)s "
"al grupo %(group)s." "al grupo %(group)s."
#: 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 "Se rechazo la solicitud de %(mainchar)s al grupo %(group)s." msgstr "Se rechazo la solicitud de %(mainchar)s al grupo %(group)s."
#: 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 "Se acepto la solicitud de %(mainchar)s para dejar el grupo %(group)s." msgstr "Se acepto la solicitud de %(mainchar)s para dejar el grupo %(group)s."
#: 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 "
@@ -1031,43 +1072,43 @@ msgstr ""
"Se ha producido un error al procesar la solicitud de %(mainchar)s para " "Se ha producido un error al procesar la solicitud de %(mainchar)s para "
"abandonar %(group)s." "abandonar %(group)s."
#: 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 ""
"Se rechazo la solicitud de %(mainchar)s para dejar el grupo %(group)s." "Se rechazo la solicitud de %(mainchar)s para dejar el grupo %(group)s."
#: allianceauth/groupmanagement/views.py: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 "No puedes unirte a ese grupo" msgstr "No puedes unirte a ese grupo"
#: 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 "Ya eres miembro de ese grupo." msgstr "Ya eres miembro de ese grupo."
#: 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 "Ya tiene una solicitud pendiente para ese grupo." msgstr "Ya tiene una solicitud pendiente para ese grupo."
#: 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 "Solicitud enviada al grupo %(group)s." msgstr "Solicitud enviada al grupo %(group)s."
#: allianceauth/groupmanagement/views.py:377 #: allianceauth/groupmanagement/views.py:380
msgid "You cannot leave that group" msgid "You cannot leave that group"
msgstr "No puedes dejar el grupos" msgstr "No puedes dejar el grupos"
#: allianceauth/groupmanagement/views.py: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 "No eres miembro de ese grupo" msgstr "No eres miembro de ese grupo"
#: 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 "Ya tiene una solicitud de baja pendiente para ese grupo." msgstr "Ya tiene una solicitud de baja pendiente para ese grupo."
#: 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 "Solicitaste dejar el grupo %(group)s." msgstr "Solicitaste dejar el grupo %(group)s."
@@ -1129,16 +1170,6 @@ msgstr "Crear Solicitud"
msgid "Username" msgid "Username"
msgstr "Usuario" msgstr "Usuario"
#: 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 "Acciones"
#: 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
@@ -1477,10 +1508,6 @@ msgstr "Modelo"
msgid "Code Name" msgid "Code Name"
msgstr "Nombre codigo" msgstr "Nombre codigo"
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
msgid "Users"
msgstr "Usuarios"
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41
msgid "States" msgid "States"
msgstr "Estados" msgstr "Estados"
@@ -2219,14 +2246,12 @@ msgstr ""
"Estado de %(total)s tareas procesadas • últimos %(latest)s" "Estado de %(total)s tareas procesadas • últimos %(latest)s"
#: 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 ""
"\n"
"%(queue_length)s tareas en cola"
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
@@ -2241,11 +2266,11 @@ msgid "AA Support Discord"
msgstr "Soporte Discord AA" msgstr "Soporte Discord AA"
#: 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 "Menú de usuario" msgstr "Menú de usuario"
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:56 #: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:67
msgid "Logout" msgid "Logout"
msgstr "Salir" msgstr "Salir"
@@ -2301,22 +2326,30 @@ msgid "Objective"
msgstr "Objetivo" msgstr "Objetivo"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "Dias restantes" msgstr "Dias restantes"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "Horas Restantes" msgstr "Horas Restantes"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "Minutos Restantes" msgstr "Minutos Restantes"
#: allianceauth/timerboard/form.py:69 #: allianceauth/timerboard/form.py:71
msgid "Important" msgid "Important"
msgstr "Importante" msgstr "Importante"
#: allianceauth/timerboard/form.py:70 #: allianceauth/timerboard/form.py:72
msgid "Corp-Restricted" msgid "Corp-Restricted"
msgstr "Restringido a Corp" msgstr "Restringido a Corp"

View File

@@ -4,23 +4,23 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
# Translators: # Translators:
# François LACROIX-DURANT <umbre@fallenstarscreations.com>, 2020 # Mickael Gr4vity, 2023
# Philippe Querin-Laporte <philippe.querin@hotmail.com>, 2020 # Idea ., 2023
# Keven D. <theenarki@gmail.com>, 2020 # rockclodbuster, 2023
# Idea ., 2021 # Keven D. <theenarki@gmail.com>, 2023
# Mickael PATTE, 2021
# Geoffrey Fabbro, 2021
# Mohssine Daghghar, 2023 # Mohssine Daghghar, 2023
# Ludovick Fortin, 2023 # Philippe Querin-Laporte <philippe.querin@hotmail.com>, 2023
# draktanar KarazGrong <umbre@fallenstarscreations.com>, 2023
# Geoffrey Fabbro, 2023
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n" "POT-Creation-Date: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: Ludovick Fortin, 2023\n" "Last-Translator: Geoffrey Fabbro, 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"
@@ -36,7 +36,7 @@ msgstr "Google Analytique Universelle"
msgid "Google Analytics V4" msgid "Google Analytics V4"
msgstr "Google Analytics V4" msgstr "Google Analytics V4"
#: 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 ""
"Un personnage principal est nécessaire pour effectuer cette action. Ajoutez-" "Un personnage principal est nécessaire pour effectuer cette action. Ajoutez-"
@@ -53,63 +53,68 @@ msgstr ""
"Vous n'avez pas lautorisation d'ajouter ou d'enlever ces groupes " "Vous n'avez pas lautorisation d'ajouter ou d'enlever ces groupes "
"restreints: %s" "restreints: %s"
#: allianceauth/authentication/models.py:80 #: allianceauth/authentication/models.py:71
msgid "English" msgid "English"
msgstr "Anglais" msgstr "Anglais"
#: allianceauth/authentication/models.py:81 #: allianceauth/authentication/models.py:72
msgid "German" msgid "German"
msgstr "Allemand" msgstr "Allemand"
#: allianceauth/authentication/models.py:82 #: allianceauth/authentication/models.py:73
msgid "Spanish" msgid "Spanish"
msgstr "Espagnol" msgstr "Espagnol"
#: allianceauth/authentication/models.py:83 #: allianceauth/authentication/models.py:74
msgid "Chinese Simplified" msgid "Chinese Simplified"
msgstr "Chinois simplifié" msgstr "Chinois simplifié"
#: allianceauth/authentication/models.py:84 #: allianceauth/authentication/models.py:75
msgid "Russian" msgid "Russian"
msgstr "Russe" msgstr "Russe"
#: allianceauth/authentication/models.py:85 #: allianceauth/authentication/models.py:76
msgid "Korean" msgid "Korean"
msgstr "Coréen" msgstr "Coréen"
#: allianceauth/authentication/models.py:86 #: allianceauth/authentication/models.py:77
msgid "French" msgid "French"
msgstr "Français" msgstr "Français"
#: allianceauth/authentication/models.py:87 #: allianceauth/authentication/models.py:78
msgid "Japanese" msgid "Japanese"
msgstr "Japonais" msgstr "Japonais"
#: allianceauth/authentication/models.py:88 #: allianceauth/authentication/models.py:79
msgid "Italian" msgid "Italian"
msgstr "Italien" msgstr "Italien"
#: allianceauth/authentication/models.py:91 #: allianceauth/authentication/models.py:80
msgid "Ukrainian"
msgstr ""
#: allianceauth/authentication/models.py:96
msgid "Language" msgid "Language"
msgstr "Langue" msgstr "Langue"
#: allianceauth/authentication/models.py:96 #: 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 "Mode Nuit" msgstr "Mode Nuit"
#: 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 "État changé à: %s" msgstr "État changé à: %s"
#: 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 "L'état de votre personnage est maintenant: %(state)s" msgstr "L'état de votre personnage est maintenant: %(state)s"
#: 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 "Écran de bord" msgstr "Écran de bord"
@@ -167,8 +172,50 @@ msgstr "Corpo"
msgid "Alliance" msgid "Alliance"
msgstr "Alliance" msgstr "Alliance"
#: 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 "Actions"
#: 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 "Personnage"
#: 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 "Connexion" msgstr "Connexion"
@@ -202,7 +249,7 @@ msgstr "S'inscrire"
msgid "Invalid or expired activation link." msgid "Invalid or expired activation link."
msgstr "Lien d'activation invalide ou expiré." msgstr "Lien d'activation invalide ou expiré."
#: 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 "
@@ -211,30 +258,32 @@ msgstr ""
"Impossible de changer le personnage principal à %(char)s. Le personnage " "Impossible de changer le personnage principal à %(char)s. Le personnage "
"appartient à un autre compte." "appartient à un autre compte."
#: 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 "Changé le personnage principal à %(char)s" msgstr "Changé le personnage principal à %(char)s"
#: 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 "Ajouté %(name)s à votre compte." msgstr "Ajouté %(name)s à votre compte."
#: 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 "Impossible d'ajouter %(name)s à votre compte: ils ont déjà un compte." msgstr "Impossible d'ajouter %(name)s à votre compte: ils ont déjà un compte."
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "Personnage principal : échec de l'identification." "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "Le token d'enregistrement est expiré." msgstr "Le token d'enregistrement est expiré."
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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."
@@ -242,12 +291,12 @@ msgstr ""
"Email de confirmation envoyé. Cliquez sur le lien pour valider votre adresse" "Email de confirmation envoyé. Cliquez sur le lien pour valider votre adresse"
" email." " email."
#: allianceauth/authentication/views.py:257 #: allianceauth/authentication/views.py:307
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "" msgstr ""
"Votre adresse email a été confirmé. Veuillez vous connecter pour continuer." "Votre adresse email a été confirmé. Veuillez vous connecter pour continuer."
#: allianceauth/authentication/views.py:262 #: allianceauth/authentication/views.py:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "La création de nouveaux comptes n'est pas actuellement permise." msgstr "La création de nouveaux comptes n'est pas actuellement permise."
@@ -290,19 +339,6 @@ msgstr "Pas inscrit"
msgid "Last update:" msgid "Last update:"
msgstr "Dernière mise à jour:" msgstr "Dernière mise à jour:"
#: 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 "Personnage"
#: 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
@@ -639,19 +675,24 @@ msgstr ""
msgid "Group Management" msgid "Group Management"
msgstr "Gestion de groupe" msgstr "Gestion de groupe"
#: allianceauth/groupmanagement/forms.py:15 #: allianceauth/groupmanagement/forms.py:18
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
msgid "Users"
msgstr "Utilisateurs"
#: 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 "Ce nom a été réserver et il ne peut être utilisé pour les groupes." msgstr "Ce nom a été réserver et il ne peut être utilisé pour les groupes."
#: allianceauth/groupmanagement/forms.py:25 #: allianceauth/groupmanagement/forms.py:62
msgid "(auto)" msgid "(auto)"
msgstr "(automatique)" msgstr "(automatique)"
#: 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 "Il existe déjà un groupe portant ce nom." msgstr "Il existe déjà un groupe portant ce nom."
#: allianceauth/groupmanagement/models.py:105 #: allianceauth/groupmanagement/models.py:104
msgid "" msgid ""
"Internal group, users cannot see, join or request to join this " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
@@ -662,13 +703,13 @@ msgstr ""
"Corporations _*, Alliance etc.<br><b> Annule les options masquer et exposer " "Corporations _*, Alliance etc.<br><b> Annule les options masquer et exposer "
"quand sélectionner." "quand sélectionner."
#: 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 ""
"Le groupe est caché aux utilisateurs, mais ils peuvent toujours rejoindre " "Le groupe est caché aux utilisateurs, mais ils peuvent toujours rejoindre "
"avec le bon lien." "avec le bon lien."
#: 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."
@@ -677,7 +718,7 @@ msgstr ""
" demande. <br> Si le groupe nest pas ouvert, les utilisateurs auront besoin" " demande. <br> Si le groupe nest pas ouvert, les utilisateurs auront besoin"
" que leurs demandes soit approuvées manuellement." " que leurs demandes soit approuvées manuellement."
#: 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 "
@@ -689,7 +730,7 @@ msgstr ""
"groupe.<br> L' Auth ne supprimera pas automatiquement les utilisateurs de ce" "groupe.<br> L' Auth ne supprimera pas automatiquement les utilisateurs de ce"
" groupe lorsquils ne seront plus authentifiés." " groupe lorsquils ne seront plus authentifiés."
#: 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."
@@ -697,62 +738,62 @@ msgstr ""
"Le groupe est restreint. Cela signifie que lajout ou la suppression " "Le groupe est restreint. Cela signifie que lajout ou la suppression "
"dutilisateurs pour ce groupe nécessite un administrateur superutilisateur." "dutilisateurs pour ce groupe nécessite un administrateur superutilisateur."
#: allianceauth/groupmanagement/models.py:144 #: allianceauth/groupmanagement/models.py:143
msgid "" msgid ""
"Group leaders can process requests for this group. Use the " "Group leaders 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: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 ""
"Brève description <i> (512 caractères maximum) </i> du groupe présenté aux " "Brève description <i> (512 caractères maximum) </i> du groupe présenté aux "
"utilisateurs." "utilisateurs."
#: allianceauth/groupmanagement/models.py:178 #: allianceauth/groupmanagement/models.py:177
msgid "Can request non-public groups" msgid "Can request non-public groups"
msgstr "Peut demander des groupes non publics" msgstr "Peut demander des groupes non publics"
#: allianceauth/groupmanagement/models.py:209 #: allianceauth/groupmanagement/models.py:208
msgid "name" msgid "name"
msgstr "Nom" msgstr "Nom"
#: 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 "Nom qui ne peut pas être utilisé pour les groupes." msgstr "Nom qui ne peut pas être utilisé pour les groupes."
#: allianceauth/groupmanagement/models.py:215 #: allianceauth/groupmanagement/models.py:214
msgid "reason" msgid "reason"
msgstr "raison" msgstr "raison"
#: 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 "Raison pour laquelle ce nom est réservé." msgstr "Raison pour laquelle ce nom est réservé."
#: allianceauth/groupmanagement/models.py:218 #: allianceauth/groupmanagement/models.py:217
msgid "created by" msgid "created by"
msgstr "créé par" msgstr "créé par"
#: allianceauth/groupmanagement/models.py:223 #: allianceauth/groupmanagement/models.py:222
msgid "created at" msgid "created at"
msgstr "créé à" msgstr "créé à"
#: 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 "Date de création de cette entrée" msgstr "Date de création de cette entrée"
@@ -979,26 +1020,26 @@ msgstr "Demandes de groupe"
msgid "Group Membership" msgid "Group Membership"
msgstr "Groupe appartenance " msgstr "Groupe appartenance "
#: 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 "L'utilisateur %(user)s à été retiré du groupe %(group)s." msgstr "L'utilisateur %(user)s à été retiré du groupe %(group)s."
#: 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 "L'utilisateur n'existe pas dans ce groupe" msgstr "L'utilisateur n'existe pas dans ce groupe"
#: allianceauth/groupmanagement/views.py:168 #: allianceauth/groupmanagement/views.py:171
msgid "Group does not exist" msgid "Group does not exist"
msgstr "Groupe non-existant" msgstr "Groupe non-existant"
#: 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 "Candidature de %(mainchar)s acceptée à %(group)s." msgstr "Candidature de %(mainchar)s acceptée à %(group)s."
#: 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 "
@@ -1007,18 +1048,18 @@ msgstr ""
"Une erreur inattendue est survenue durant le traitement de l'application de " "Une erreur inattendue est survenue durant le traitement de l'application de "
"%(mainchar)s à %(group)s." "%(mainchar)s à %(group)s."
#: 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 "L'application de %(mainchar)s à %(group)s est rejetée." msgstr "L'application de %(mainchar)s à %(group)s est rejetée."
#: 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 "La demande de retirer %(mainchar)s de %(group)s est acceptée." msgstr "La demande de retirer %(mainchar)s de %(group)s est acceptée."
#: 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 "
@@ -1027,42 +1068,42 @@ msgstr ""
"Une erreur inattendue est survenue durant le traitement de la demande de " "Une erreur inattendue est survenue durant le traitement de la demande de "
"retirer %(mainchar)s de %(group)s." "retirer %(mainchar)s de %(group)s."
#: 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 "La remande de retirer %(mainchar)s de %(group)s a été refusée." msgstr "La remande de retirer %(mainchar)s de %(group)s a été refusée."
#: 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 "Vous ne pouvez pas joindre ce groupe." msgstr "Vous ne pouvez pas joindre ce groupe."
#: 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 "Vous faites déjà parti de ce groupe." msgstr "Vous faites déjà parti de ce groupe."
#: 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 "Vous avez déjà une application en attente pour joindre ce groupe." msgstr "Vous avez déjà une application en attente pour joindre ce groupe."
#: 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 "Appliqué au groupe %(group)s." msgstr "Appliqué au groupe %(group)s."
#: allianceauth/groupmanagement/views.py:377 #: allianceauth/groupmanagement/views.py:380
msgid "You cannot leave that group" msgid "You cannot leave that group"
msgstr "Vous ne pouvez pas quitter ce groupe." msgstr "Vous ne pouvez pas quitter ce groupe."
#: 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 "Vous n'êtes pas un membre de ce groupe." msgstr "Vous n'êtes pas un membre de ce groupe."
#: 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 "Vous avec déjà une demande de quitter ce groupe en attente." msgstr "Vous avec déjà une demande de quitter ce groupe en attente."
#: 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 "Appliqué pour quitter le groupe %(group)s." msgstr "Appliqué pour quitter le groupe %(group)s."
@@ -1124,16 +1165,6 @@ msgstr "Créer une application"
msgid "Username" msgid "Username"
msgstr "Nom d'utilisateur" msgstr "Nom d'utilisateur"
#: 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 "Actions"
#: 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
@@ -1472,10 +1503,6 @@ msgstr "Modèle"
msgid "Code Name" msgid "Code Name"
msgstr "Nom De Code" msgstr "Nom De Code"
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
msgid "Users"
msgstr "Utilisateurs"
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41
msgid "States" msgid "States"
msgstr "États" msgstr "États"
@@ -2217,15 +2244,12 @@ 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 ""
"\n"
" %(queue_length)stâches en file d'attente\n"
" "
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
@@ -2240,11 +2264,11 @@ msgid "AA Support Discord"
msgstr "Support Discord AA" msgstr "Support Discord AA"
#: 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 "Menu Utilisateur" msgstr "Menu Utilisateur"
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:56 #: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:67
msgid "Logout" msgid "Logout"
msgstr "Déconnexion" msgstr "Déconnexion"
@@ -2300,22 +2324,30 @@ msgid "Objective"
msgstr "Objectif" msgstr "Objectif"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "Jour restants" msgstr "Jour restants"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "Heures restantes" msgstr "Heures restantes"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "Minutes restantes" msgstr "Minutes restantes"
#: allianceauth/timerboard/form.py:69 #: allianceauth/timerboard/form.py:71
msgid "Important" msgid "Important"
msgstr "Important" msgstr "Important"
#: allianceauth/timerboard/form.py:70 #: allianceauth/timerboard/form.py:72
msgid "Corp-Restricted" msgid "Corp-Restricted"
msgstr "Limité à la Corporation" msgstr "Limité à la Corporation"

File diff suppressed because it is too large Load Diff

View File

@@ -4,17 +4,18 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
# Translators: # Translators:
# Foch Petain <brigadier.rockforward@gmail.com>, 2020
# kotaneko, 2023 # kotaneko, 2023
# Foch Petain <brigadier.rockforward@gmail.com>, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n" "POT-Creation-Date: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+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"
@@ -30,7 +31,7 @@ msgstr "Google ユニバーサル アナリティクス"
msgid "Google Analytics V4" msgid "Google Analytics V4"
msgstr "Google アナリティクス 4" msgstr "Google アナリティクス 4"
#: 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 "実行するためにはメインキャラクターの設定が必要です。設定してください。"
@@ -43,63 +44,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 "これらの制限付きグループを追加または削除することはできません。%s" msgstr "これらの制限付きグループを追加または削除することはできません。%s"
#: 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 "Ukrainian"
msgstr ""
#: allianceauth/authentication/models.py:96
msgid "Language" msgid "Language"
msgstr "言語" msgstr "言語"
#: allianceauth/authentication/models.py:96 #: 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 "分類が%sに変更されました。" msgstr "分類が%sに変更されました。"
#: 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 "あなたの分類は%(state)sになりました。" msgstr "あなたの分類は%(state)sになりました。"
#: 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 "ダッシュボード"
@@ -157,8 +163,50 @@ msgstr "Corp"
msgid "Alliance" msgid "Alliance"
msgstr "Alliance" msgstr "Alliance"
#: 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 "ログイン"
@@ -190,47 +238,49 @@ 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 "メインキャラクターを%(char)sへ変更できません。別のアカウントによって利用されています。" msgstr "メインキャラクターを%(char)sへ変更できません。別のアカウントによって利用されています。"
#: 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 "メインキャラクターを%(char)sへ変更しました。" msgstr "メインキャラクターを%(char)sへ変更しました。"
#: 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 "%(name)sをアカウントに追加しました。" msgstr "%(name)sをアカウントに追加しました。"
#: 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 "%(name)sをアカウントに追加することができません。すでに他のアカウントを持っています。" msgstr "%(name)sをアカウントに追加することができません。すでに他のアカウントを持っています。"
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "選択されたキャラクターの認証が行えませんでした。" "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "Registrationトークンが有効期限切れです。" msgstr "Registrationトークンが有効期限切れです。"
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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:307
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:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "新規アカウントの登録は、現時点ではできません。" msgstr "新規アカウントの登録は、現時点ではできません。"
@@ -273,19 +323,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
@@ -615,19 +652,24 @@ msgstr "{character.character_name} のフリート参加を登録できません
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 "(auto)" msgstr "(auto)"
#: 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 " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
@@ -636,18 +678,18 @@ msgstr ""
"内部グループです。ユーザーはこのグループを表示したり、参加したり、参加をリクエストしたりすることはできません。<br>Members、Corp_*、Alliance_*などのグループに使用します。選択すると、非表示" "内部グループです。ユーザーはこのグループを表示したり、参加したり、参加をリクエストしたりすることはできません。<br>Members、Corp_*、Alliance_*などのグループに使用します。選択すると、非表示"
" オプションと 開く <br> <b> オプションが上書きされます。</b> " " オプションと 開く <br> <b> オプションが上書きされます。</b> "
#: 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 ""
"グループはオープンで、ユーザーはリクエストに応じて自動的に追加されます。<br>グループがオープンでない場合、ユーザーのリクエストは手動で承認する必要があります。" "グループはオープンで、ユーザーはリクエストに応じて自動的に追加されます。<br>グループがオープンでない場合、ユーザーのリクエストは手動で承認する必要があります。"
#: 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 "
@@ -657,13 +699,13 @@ msgstr ""
"グループは一般公開されています。登録ユーザーなら誰でもこのグループに参加でき、このグループに設定されている他のオプションに基づいて表示されます。<br>認証されなくなっても、Auth" "グループは一般公開されています。登録ユーザーなら誰でもこのグループに参加でき、このグループに設定されている他のオプションに基づいて表示されます。<br>認証されなくなっても、Auth"
" はユーザーをこのグループから自動的に削除しません。" " はユーザーをこのグループから自動的に削除しません。"
#: 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 " "Group leaders 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 "
@@ -672,7 +714,7 @@ msgstr ""
"グループリーダーは、このグループのリクエストを処理できます。<code>auth.group_management </code> " "グループリーダーは、このグループのリクエストを処理できます。<code>auth.group_management </code> "
"権限を使用して、ユーザーがすべてのグループを管理できるようにします。<br> " "権限を使用して、ユーザーがすべてのグループを管理できるようにします。<br> "
#: 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 "
@@ -681,46 +723,46 @@ msgstr ""
"リーダーグループのメンバーは、このグループのリクエストを処理できます。<code>auth.group_management </code> " "リーダーグループのメンバーは、このグループのリクエストを処理できます。<code>auth.group_management </code> "
"権限を使用して、ユーザーがすべてのグループを管理できるようにします。<br> " "権限を使用して、ユーザーがすべてのグループを管理できるようにします。<br> "
#: 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 "ここに記載されているStatesは、適切な許可があれば、このグループに参加できます。<br> " msgstr "ここに記載されているStatesは、適切な許可があれば、このグループに参加できます。<br> "
#: 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 "ユーザーに表示されるグループの簡単な説明 <i> (最大 512 文字) </i>。" msgstr "ユーザーに表示されるグループの簡単な説明 <i> (最大 512 文字) </i>。"
#: 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 "このエントリが作成された日付"
@@ -947,86 +989,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 "%(user)sを%(group)sから削除する。" msgstr "%(user)sを%(group)sから削除する。"
#: 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 "%(mainchar)sからの%(group)sへの参加申請を承認しました。" msgstr "%(mainchar)sからの%(group)sへの参加申請を承認しました。"
#: 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 "%(mainchar)sからの%(group)sへの参加申請を処理中にエラーが発生しました。" msgstr "%(mainchar)sからの%(group)sへの参加申請を処理中にエラーが発生しました。"
#: 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 "%(mainchar)sからの%(group)sへの参加申請は拒否されました。" msgstr "%(mainchar)sからの%(group)sへの参加申請は拒否されました。"
#: 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 "%(mainchar)sからの%(group)sからの脱退申請は承認されました。" msgstr "%(mainchar)sからの%(group)sからの脱退申請は承認されました。"
#: 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 "%(mainchar)sからの%(group)sからの脱退申請を処理中にエラーが発生しました。" msgstr "%(mainchar)sからの%(group)sからの脱退申請を処理中にエラーが発生しました。"
#: 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 "%(mainchar)sの%(group)sからの脱退申請は拒否されました。" msgstr "%(mainchar)sの%(group)sからの脱退申請は拒否されました。"
#: 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 "このGroupには入れません" msgstr "このGroupには入れません"
#: 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 "すでにその Group に参加してます。" msgstr "すでにその Group に参加してます。"
#: 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 "%(group)sへの参加申請を送信しました。" msgstr "%(group)sへの参加申請を送信しました。"
#: allianceauth/groupmanagement/views.py:377 #: allianceauth/groupmanagement/views.py:380
msgid "You cannot leave that group" msgid "You cannot leave that group"
msgstr "この Group から脱退することはできません" msgstr "この Group から脱退することはできません"
#: 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 "あなたはその Group のメンバーではありません" msgstr "あなたはその Group のメンバーではありません"
#: 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 "%(group)sからの脱退申請を送信しました。" msgstr "%(group)sからの脱退申請を送信しました。"
@@ -1088,16 +1130,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
@@ -1436,10 +1468,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 "States" msgstr "States"
@@ -2170,15 +2198,12 @@ 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 ""
"\n"
" %(queue_length)sキューに入っているタスク\n"
" "
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
@@ -2193,11 +2218,11 @@ msgid "AA Support Discord"
msgstr "AA サポートディスコード" msgstr "AA サポートディスコード"
#: 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 "ログアウト"
@@ -2253,22 +2278,30 @@ msgid "Objective"
msgstr "目標" msgstr "目標"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "残り日数" msgstr "残り日数"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "残り時間" msgstr "残り時間"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "残り分数" msgstr "残り分数"
#: allianceauth/timerboard/form.py:69 #: 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 "コーポレーション制限付き"

View File

@@ -4,22 +4,22 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
# Translators: # Translators:
# None None <khd1226543@gmail.com>, 2020 # Joel Falknau <ozirascal@gmail.com>, 2023
# Seowon Jung <seowon@hawaii.edu>, 2020 # None None <khd1226543@gmail.com>, 2023
# Olgeda Choi <undead.choi@gmail.com>, 2020 # ThatRagingKid, 2023
# Lahty <js03js70@gmail.com>, 2020 # Lahty <js03js70@gmail.com>, 2023
# Joel Falknau <ozirascal@gmail.com>, 2020 # Olgeda Choi <undead.choi@gmail.com>, 2023
# ThatRagingKid, 2022 # Seowon Jung <seowon@hawaii.edu>, 2023
# jackfrost, 2022 # Alpha, 2023
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n" "POT-Creation-Date: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: jackfrost, 2022\n" "Last-Translator: Alpha, 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"
@@ -35,7 +35,7 @@ msgstr "Google 애널리틱스 유니버설"
msgid "Google Analytics V4" msgid "Google Analytics V4"
msgstr "Google 애널리틱스 V4" msgstr "Google 애널리틱스 V4"
#: 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 "해당 기능을 수행하려면 주 캐릭터가 요구됩니다. 아래에서 하나를 추가하시오."
@@ -48,63 +48,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 "상태가 %s로 변경됐습니다." msgstr "상태가 %s로 변경됐습니다."
#: 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 "사용자의 상태는 %(state)s입니다." msgstr "사용자의 상태는 %(state)s입니다."
#: 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 "대시보드"
@@ -163,8 +168,50 @@ 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 "로그인"
@@ -196,47 +243,49 @@ 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 "%(char)s를 주 캐릭터로 변경할 수 없음: 다른 계정이 해당 캐릭터를 소유하고 있습니다." msgstr "%(char)s를 주 캐릭터로 변경할 수 없음: 다른 계정이 해당 캐릭터를 소유하고 있습니다."
#: 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 "주 캐릭터가 %(char)s로 변경됨" msgstr "주 캐릭터가 %(char)s로 변경됨"
#: 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 "계정에 %(name)s를 추가했습니다." msgstr "계정에 %(name)s를 추가했습니다."
#: 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 "계정에 %(name)s를 추가하지 못했습니다. 이미 다른 계정에 추가되었습니다." msgstr "계정에 %(name)s를 추가하지 못했습니다. 이미 다른 계정에 추가되었습니다."
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "선택한 캐릭터로 인증할 수 없습니다." "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "가입 토큰이 만료되었습니다." msgstr "가입 토큰이 만료되었습니다."
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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:307
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:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "현재 새로운 계정 등록은 받지않습니다." msgstr "현재 새로운 계정 등록은 받지않습니다."
@@ -279,19 +328,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
@@ -621,19 +657,24 @@ 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 " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
@@ -642,11 +683,11 @@ msgstr ""
"시스템 그룹, 유저들은 이 그룹을 보거나, 참여하거나, 지원할 수 없습니다. <br>멤버, 코퍼레이션_*, 얼라이언스_* 등에 " "시스템 그룹, 유저들은 이 그룹을 보거나, 참여하거나, 지원할 수 없습니다. <br>멤버, 코퍼레이션_*, 얼라이언스_* 등에 "
"사용됨.<br><b>선택된 경우 비공개와 공개 옵션을 무시함.</b>" "사용됨.<br><b>선택된 경우 비공개와 공개 옵션을 무시함.</b>"
#: 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."
@@ -654,7 +695,7 @@ msgstr ""
"그룹은 공개되어 있으며 요청 시 유저는 자동적으로 추가됩니다.<br>그룹이 공개되어 있지 않은 경우, 유저는 직접 요청을 승인받아야 " "그룹은 공개되어 있으며 요청 시 유저는 자동적으로 추가됩니다.<br>그룹이 공개되어 있지 않은 경우, 유저는 직접 요청을 승인받아야 "
"합니다." "합니다."
#: 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 "
@@ -664,13 +705,13 @@ msgstr ""
"공개 그룹입니다. 등록된 모든 유저는 이 그룹에 참여할 수 있으며, 이 그룹의 설정에 따라 공개 여부가 달라집니다.<br>유저가 더 " "공개 그룹입니다. 등록된 모든 유저는 이 그룹에 참여할 수 있으며, 이 그룹의 설정에 따라 공개 여부가 달라집니다.<br>유저가 더 "
"이상 인증을 하지 않을 때, Auth는 이 그룹에서 유저를 자동 추방하지 않습니다." "이상 인증을 하지 않을 때, Auth는 이 그룹에서 유저를 자동 추방하지 않습니다."
#: 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 " "Group leaders 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 "
@@ -679,7 +720,7 @@ msgstr ""
"그룹 리더는 이 그룹의 요청을 처리할 수 있습니다. <code>auth.group_management</code> 권한을 사용하여 " "그룹 리더는 이 그룹의 요청을 처리할 수 있습니다. <code>auth.group_management</code> 권한을 사용하여 "
"사용자가 모든 그룹을 관리할 수 있도록 합니다.<br>" "사용자가 모든 그룹을 관리할 수 있도록 합니다.<br>"
#: 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 "
@@ -688,46 +729,46 @@ msgstr ""
"리더 그룹의 구성원은 이 그룹에 대한 요청을 처리할 수 있습니다. <code>1auth.group_management1</code> " "리더 그룹의 구성원은 이 그룹에 대한 요청을 처리할 수 있습니다. <code>1auth.group_management1</code> "
"권한을 사용하여 사용자가 모든 그룹을 관리할 수 있도록 합니다.<br>" "권한을 사용하여 사용자가 모든 그룹을 관리할 수 있도록 합니다.<br>"
#: 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 "사용자에게 나타나는 그룹에 대한 간단한 설명 <i>(최대 512자)</i> 입니다." msgstr "사용자에게 나타나는 그룹에 대한 간단한 설명 <i>(최대 512자)</i> 입니다."
#: 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 "이 항목이 생성된 날짜"
@@ -954,86 +995,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 "유저 %(user)s이(가) %(group)s에서 제거됨." msgstr "유저 %(user)s이(가) %(group)s에서 제거됨."
#: 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 "%(mainchar)s의 %(group)s 그룹 신청 수락" msgstr "%(mainchar)s의 %(group)s 그룹 신청 수락"
#: 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 "%(mainchar)s의 %(group)s 그룹 신청을 처리하는 중 알 수 없는 에러 발생" msgstr "%(mainchar)s의 %(group)s 그룹 신청을 처리하는 중 알 수 없는 에러 발생"
#: 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 "%(mainchar)s의 %(group)s 그룹 신청 거절" msgstr "%(mainchar)s의 %(group)s 그룹 신청 거절"
#: 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 "%(mainchar)s의 %(group)s 그룹 탈퇴 수락" msgstr "%(mainchar)s의 %(group)s 그룹 탈퇴 수락"
#: 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 "%(mainchar)s의 %(group)s 그룹 탈퇴를 처리하는 중 알 수 없는 에러 발생" msgstr "%(mainchar)s의 %(group)s 그룹 탈퇴를 처리하는 중 알 수 없는 에러 발생"
#: 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 "%(mainchar)s의 %(group)s 그룹 탈퇴 거절" msgstr "%(mainchar)s의 %(group)s 그룹 탈퇴 거절"
#: 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 "%(group)s그룹에 지원하였음." msgstr "%(group)s그룹에 지원하였음."
#: 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 "%(group)s그룹의 탈퇴가 신청됨." msgstr "%(group)s그룹의 탈퇴가 신청됨."
@@ -1095,16 +1136,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
@@ -1443,10 +1474,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 "상태"
@@ -2170,11 +2197,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
@@ -2190,11 +2217,11 @@ msgid "AA Support Discord"
msgstr "AA Support Discord" msgstr "AA Support Discord"
#: 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 "로그아웃"
@@ -2250,22 +2277,30 @@ msgid "Objective"
msgstr "목표 대상" msgstr "목표 대상"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "남은 일수" msgstr "남은 일수"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "남은 시간" msgstr "남은 시간"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "남은 분" msgstr "남은 분"
#: allianceauth/timerboard/form.py:69 #: 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 "코퍼레이션 제한"

View File

@@ -4,9 +4,9 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
# Translators: # Translators:
# Alexander Gess <de.alex.gess@gmail.com>, 2020 # Андрей Зубков <and.vareba81@gmail.com>, 2023
# Yuriy K <thedjcooltv@gmail.com>, 2020 # Yuriy K <thedjcooltv@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
@@ -14,8 +14,8 @@ 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: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+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"
@@ -32,7 +32,7 @@ msgstr "Google Analytics Universal"
msgid "Google Analytics V4" msgid "Google Analytics V4"
msgstr "Google Analytics V4" msgstr "Google Analytics V4"
#: 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 ""
"Для продолжения следует указать основного персонажа. Выберите его ниже." "Для продолжения следует указать основного персонажа. Выберите его ниже."
@@ -46,63 +46,68 @@ msgstr "Email"
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 "Вам не разрешено добавлять или удалять эти ограниченные группы: %s" msgstr "Вам не разрешено добавлять или удалять эти ограниченные группы: %s"
#: 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 "Ukrainian"
msgstr ""
#: allianceauth/authentication/models.py:96
msgid "Language" msgid "Language"
msgstr "Язык" msgstr "Язык"
#: allianceauth/authentication/models.py:96 #: 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 "Статус изменен: %s" msgstr "Статус изменен: %s"
#: 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 "Статус пилота: %(state)s" msgstr "Статус пилота: %(state)s"
#: 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 "Панель показателей"
@@ -161,8 +166,50 @@ 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 "Вход"
@@ -195,7 +242,7 @@ 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 "
@@ -203,40 +250,42 @@ msgid ""
msgstr "" msgstr ""
"Нельзя сменить основного персонажа на %(char)s: похоже, что Владелец не Вы. " "Нельзя сменить основного персонажа на %(char)s: похоже, что Владелец не Вы. "
#: 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 "Основной персонаж заменен на %(char)s" msgstr "Основной персонаж заменен на %(char)s"
#: 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 "Добавлен %(name)s на Ваш аккаунт." msgstr "Добавлен %(name)s на Ваш аккаунт."
#: 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 "Персонаж %(name)s уже добавлен." msgstr "Персонаж %(name)s уже добавлен."
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "Невозможно авторизировать этого персонажа. " "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "Регистрационный токен просрочен." msgstr "Регистрационный токен просрочен."
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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:307
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "Подтвердите Ваш email адрес. Зайти для подтверждения. " msgstr "Подтвердите Ваш email адрес. Зайти для подтверждения. "
#: allianceauth/authentication/views.py:262 #: allianceauth/authentication/views.py:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "Регистрация новых аккаунтов в настоящее время невозможна." msgstr "Регистрация новых аккаунтов в настоящее время невозможна."
@@ -279,19 +328,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
@@ -629,20 +665,25 @@ 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 " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
@@ -653,13 +694,13 @@ msgstr ""
"Members, Corp_*, Alliance_* и т. п.<br><b>Будучи выбранной, отменяет " "Members, Corp_*, Alliance_* и т. п.<br><b>Будучи выбранной, отменяет "
"настройки \"Скрытая\" и \"Открытая\".</b>" "настройки \"Скрытая\" и \"Открытая\".</b>"
#: 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."
@@ -668,7 +709,7 @@ msgstr ""
"при отправке запроса.<br>Если группа не является открытой, запросы от " "при отправке запроса.<br>Если группа не является открытой, запросы от "
"пользователей будут требовать ручного подтверждения." "пользователей будут требовать ручного подтверждения."
#: 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 "
@@ -680,7 +721,7 @@ msgstr ""
"настройках данной группы.<br>Auth не будет удалять пользователей из этой " "настройках данной группы.<br>Auth не будет удалять пользователей из этой "
"группы автоматически при окончании срока их аутентификации." "группы автоматически при окончании срока их аутентификации."
#: 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."
@@ -688,7 +729,7 @@ msgstr ""
"Группа является ограниченной. Это значит что добавление пользователей в эту " "Группа является ограниченной. Это значит что добавление пользователей в эту "
"группу или удаление из неё требует прав superuser admin." "группу или удаление из неё требует прав superuser admin."
#: allianceauth/groupmanagement/models.py:144 #: allianceauth/groupmanagement/models.py:143
msgid "" msgid ""
"Group leaders can process requests for this group. Use the " "Group leaders 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 "
@@ -698,7 +739,7 @@ msgstr ""
"Используйте разрешение <code>auth.group_management</code>, чтобы позволить " "Используйте разрешение <code>auth.group_management</code>, чтобы позволить "
"пользователю управлять всеми группами.<br>" "пользователю управлять всеми группами.<br>"
#: 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 "
@@ -708,7 +749,7 @@ msgstr ""
"Используйте разрешение <code>auth.group_management</code>, чтобы позволить " "Используйте разрешение <code>auth.group_management</code>, чтобы позволить "
"пользователю управлять всеми группами.<br>" "пользователю управлять всеми группами.<br>"
#: 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>"
@@ -716,42 +757,42 @@ msgstr ""
"Статусы, перечисленные здесь, смогут присоединиться к группе, если у них " "Статусы, перечисленные здесь, смогут присоединиться к группе, если у них "
"есть соответствующие разрешения.<br>" "есть соответствующие разрешения.<br>"
#: 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 ""
"Краткое описание <i>(макс. 512 символов)</i> группы, отображаемое " "Краткое описание <i>(макс. 512 символов)</i> группы, отображаемое "
"пользователям." "пользователям."
#: 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 "Дата, когда данное содержимое было создано"
@@ -978,26 +1019,26 @@ 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 "Пользователь %(user)s исключен из %(group)s." msgstr "Пользователь %(user)s исключен из %(group)s."
#: 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 "Запрос от %(mainchar)sв %(group)s принят." msgstr "Запрос от %(mainchar)sв %(group)s принят."
#: 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 "
@@ -1006,18 +1047,18 @@ msgstr ""
"Персонаж %(mainchar)s не может быть добавлен %(group)s, из-за непредвиденной" "Персонаж %(mainchar)s не может быть добавлен %(group)s, из-за непредвиденной"
" ошибки. " " ошибки. "
#: 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 "%(mainchar)s исключен из %(group)s." msgstr "%(mainchar)s исключен из %(group)s."
#: 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 "Утвержден выход %(mainchar)s из %(group)s. " msgstr "Утвержден выход %(mainchar)s из %(group)s. "
#: 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 "
@@ -1026,42 +1067,42 @@ msgstr ""
"Возникла ошибка во время обработки %(mainchar)s на выход из группы " "Возникла ошибка во время обработки %(mainchar)s на выход из группы "
"%(group)s. Повторите позже." "%(group)s. Повторите позже."
#: 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 "Прошение об исключении %(mainchar)s из %(group)s отклонено. " msgstr "Прошение об исключении %(mainchar)s из %(group)s отклонено. "
#: 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 "Вступить в группу %(group)s." msgstr "Вступить в группу %(group)s."
#: 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 "Запрос на выход из группы %(group)s." msgstr "Запрос на выход из группы %(group)s."
@@ -1123,16 +1164,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
@@ -1471,10 +1502,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 "Статусы"
@@ -2216,14 +2243,12 @@ msgstr ""
" Статус %(total)s обработанных задач • последние %(latest)s" " Статус %(total)s обработанных задач • последние %(latest)s"
#: 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 ""
"\n"
" %(queue_length)s запланированных задач"
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
@@ -2238,11 +2263,11 @@ msgid "AA Support Discord"
msgstr "Discord поддержки AA" msgstr "Discord поддержки AA"
#: 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 "Выход"
@@ -2298,22 +2323,30 @@ msgid "Objective"
msgstr "Задача" msgstr "Задача"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "Дней осталось" msgstr "Дней осталось"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "Часов осталось" msgstr "Часов осталось"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "Минут осталось" msgstr "Минут осталось"
#: allianceauth/timerboard/form.py:69 #: 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 "Корпорация зарегистрированна"

View File

@@ -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
@@ -11,8 +12,8 @@ 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: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+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"
@@ -29,10 +30,10 @@ msgstr "Універсальна Google Аналітика"
msgid "Google Analytics V4" msgid "Google Analytics V4"
msgstr "Google Analytics V4" msgstr "Google Analytics V4"
#: 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 ""
"Для виконання цієї дії потрібен головний персонаж. Додайте його нижче." "Для виконання цієї дії потрібен основний персонаж. Додайте його нижче."
#: allianceauth/authentication/forms.py:12 #: allianceauth/authentication/forms.py:12
msgid "Email" msgid "Email"
@@ -43,63 +44,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 "Вам заборонено додавати або видаляти ці обмежені групи: %s" msgstr "Вам заборонено додавати або видаляти ці обмежені групи: %s"
#: 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 "Ukrainian"
msgstr ""
#: allianceauth/authentication/models.py:96
msgid "Language" msgid "Language"
msgstr "Мова" msgstr "Мова"
#: allianceauth/authentication/models.py:96 #: 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 "Стан змінено на: %s" msgstr "Стан змінено на: %s"
#: 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 "Стан вашого користувача зараз: %(state)s" msgstr "Стан вашого користувача зараз: %(state)s"
#: 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 "Панель приладів"
@@ -124,7 +130,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"
@@ -157,8 +163,50 @@ 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 "Увійти"
@@ -192,7 +240,7 @@ 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 "
@@ -201,32 +249,34 @@ msgstr ""
"Неможливо змінити основного персонажа на %(char)s: персонаж належить іншому " "Неможливо змінити основного персонажа на %(char)s: персонаж належить іншому "
"акаунту." "акаунту."
#: 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 "Основний персонаж змінено на %(char)s" msgstr "Основний персонаж змінено на %(char)s"
#: 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 "Додано %(name)s до вашого облікового запису." msgstr "Додано %(name)s до вашого облікового запису."
#: 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 ""
"Не вдалося додати %(name)s до вашого облікового запису: у них вже є " "Не вдалося додати %(name)s до вашого облікового запису: у них вже є "
"обліковий запис." "обліковий запис."
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "Не вдалося автентифікуватися як обраний персонаж." "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "Токен реєстрації застарів." msgstr "Токен реєстрації застарів."
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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."
@@ -234,13 +284,13 @@ msgstr ""
"Відправлено лист з підтвердженням. Будь ласка, перейдіть за посиланням, щоб " "Відправлено лист з підтвердженням. Будь ласка, перейдіть за посиланням, щоб "
"підтвердити свою адресу електронної пошти." "підтвердити свою адресу електронної пошти."
#: allianceauth/authentication/views.py:257 #: allianceauth/authentication/views.py:307
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:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "Реєстрація нових облікових записів наразі не дозволена." msgstr "Реєстрація нових облікових записів наразі не дозволена."
@@ -283,19 +333,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
@@ -352,7 +389,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 +493,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 +597,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 +662,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
@@ -633,19 +670,24 @@ 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 " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
@@ -656,13 +698,12 @@ msgstr ""
"\"Members, Corp_, Alliance_ і т.д.<br><b>Перевизначає параметри Hidden і \"\n" "\"Members, Corp_, Alliance_ і т.д.<br><b>Перевизначає параметри Hidden і \"\n"
"\"Open при виборі.</b>\"" "\"Open при виборі.</b>\""
#: 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."
@@ -671,7 +712,7 @@ msgstr ""
"запитом.<br>Якщо група закрита, користувачі повинні отримати ручне " "запитом.<br>Якщо група закрита, користувачі повинні отримати ручне "
"підтвердження запиту." "підтвердження запиту."
#: 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 "
@@ -683,7 +724,7 @@ msgstr ""
"групи.<br>Авторизація не буде автоматично видаляти користувачів з цієї " "групи.<br>Авторизація не буде автоматично видаляти користувачів з цієї "
"групи, коли вони більше не автентифіковані." "групи, коли вони більше не автентифіковані."
#: 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."
@@ -691,7 +732,7 @@ msgstr ""
"Група обмежена. Це означає, що додавання або видалення користувачів для цієї" "Група обмежена. Це означає, що додавання або видалення користувачів для цієї"
" групи вимагає адміністратора-суперкористувача." " групи вимагає адміністратора-суперкористувача."
#: allianceauth/groupmanagement/models.py:144 #: allianceauth/groupmanagement/models.py:143
msgid "" msgid ""
"Group leaders can process requests for this group. Use the " "Group leaders 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 "
@@ -701,7 +742,7 @@ msgstr ""
"<code>auth.group_management</code>, щоб дозволити користувачеві керувати " "<code>auth.group_management</code>, щоб дозволити користувачеві керувати "
"всіма групами.<br>" "всіма групами.<br>"
#: 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 "
@@ -711,7 +752,7 @@ msgstr ""
" дозвіл <code>auth.group_management</code>, щоб дозволити користувачеві " " дозвіл <code>auth.group_management</code>, щоб дозволити користувачеві "
"керувати всіма групами.<br>" "керувати всіма групами.<br>"
#: 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>"
@@ -719,42 +760,42 @@ msgstr ""
"Штати, перераховані тут, матимуть змогу приєднатися до цієї групи, якщо вони" "Штати, перераховані тут, матимуть змогу приєднатися до цієї групи, якщо вони"
" мають відповідні дозволи.<br>" " мають відповідні дозволи.<br>"
#: 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 ""
"Короткий опис <i>(максимум 512 символів)</i> групи, що відображається " "Короткий опис <i>(максимум 512 символів)</i> групи, що відображається "
"користувачам." "користувачам."
#: 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 "Дата створення цього запису"
@@ -981,26 +1022,26 @@ 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 "Користувач %(user)s вилучений з групи %(group)s." msgstr "Користувач %(user)s вилучений з групи %(group)s."
#: 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 "Заявка %(mainchar)s на вступ до %(group)s прийнята." msgstr "Заявка %(mainchar)s на вступ до %(group)s прийнята."
#: 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 "
@@ -1009,18 +1050,18 @@ msgstr ""
"Під час обробки заявки %(mainchar)s на вступ до %(group)s виникла помилка, " "Під час обробки заявки %(mainchar)s на вступ до %(group)s виникла помилка, "
"яку не можна обробити." "яку не можна обробити."
#: 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 "Заявка %(mainchar)s на вступ до %(group)s відхилена." msgstr "Заявка %(mainchar)s на вступ до %(group)s відхилена."
#: 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 "Заявка %(mainchar)s на вихід з %(group)s прийнята." msgstr "Заявка %(mainchar)s на вихід з %(group)s прийнята."
#: 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 "
@@ -1029,42 +1070,42 @@ msgstr ""
"Під час обробки заявки %(mainchar)s на вихід з %(group)s виникла помилка, " "Під час обробки заявки %(mainchar)s на вихід з %(group)s виникла помилка, "
"яку не можна обробити." "яку не можна обробити."
#: 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 "Заявка %(mainchar)s на вихід з %(group)s відхилена." msgstr "Заявка %(mainchar)s на вихід з %(group)s відхилена."
#: 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 "Подано заявку на групу %(group)s." msgstr "Подано заявку на групу %(group)s."
#: 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 "Подано заявку на вихід з групи %(group)s." msgstr "Подано заявку на вихід з групи %(group)s."
@@ -1126,16 +1167,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
@@ -1321,7 +1352,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 +1376,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 +1431,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
@@ -1474,17 +1505,13 @@ 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 "Стани"
#: 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 +1532,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 +1572,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 +1641,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 +1729,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 +1740,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 +1748,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 +1771,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 +1788,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 +1896,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 +1908,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
@@ -2220,14 +2247,12 @@ msgstr ""
"Статус %(total)s виконаних завдань • останній %(latest)s" "Статус %(total)s виконаних завдань • останній %(latest)s"
#: 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 ""
"\n"
"%(queue_length)s завдань в черзі"
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
@@ -2242,11 +2267,11 @@ msgid "AA Support Discord"
msgstr "Підтримка AA у Discord" msgstr "Підтримка AA у Discord"
#: 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 "Вихід"
@@ -2302,22 +2327,30 @@ msgid "Objective"
msgstr "Мета" msgstr "Мета"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "Залишилося днів" msgstr "Залишилося днів"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "Залишилося годин" msgstr "Залишилося годин"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "Залишилося хвилин" msgstr "Залишилося хвилин"
#: allianceauth/timerboard/form.py:69 #: 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 "Обмежено для корпорації"

View File

@@ -4,18 +4,19 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
# Translators: # Translators:
# Joel Falknau <ozirascal@gmail.com>, 2020 # Shen Yang, 2023
# Jesse . <sgeine@hotmail.com>, 2020 # Jesse . <sgeine@hotmail.com>, 2023
# Aaron BuBu <351793078@qq.com>, 2020 # Aaron BuBu <351793078@qq.com>, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n" "POT-Creation-Date: 2024-02-17 18:50+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: Aaron BuBu <351793078@qq.com>, 2020\n" "Last-Translator: Joel Falknau <ozirascal@gmail.com>, 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"
@@ -31,7 +32,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 "只有主要角色才能执行这个操作。在下面添加一个"
@@ -44,63 +45,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 "账户总览"
@@ -156,8 +162,50 @@ 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 "登录"
@@ -189,47 +237,49 @@ 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 "不能修改主角色为%(char)s这个角色被另一个账户所拥有" msgstr "不能修改主角色为%(char)s这个角色被另一个账户所拥有"
#: 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 "修改主要角色为%(char)s" msgstr "修改主要角色为%(char)s"
#: 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 "添加%(name)s到您的账户" msgstr "添加%(name)s到您的账户"
#: 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 "添加%(name)s到您的账户失败他们已经在一个账户中了" msgstr "添加%(name)s到您的账户失败他们已经在一个账户中了"
#: allianceauth/authentication/views.py:133 #: allianceauth/authentication/views.py:178
msgid "Unable to authenticate as the selected character." msgid ""
msgstr "无法作为选定的角色进行身份验证" "Unable to authenticate as the selected character. Please log in with the "
"main character associated with this account."
msgstr ""
#: allianceauth/authentication/views.py:197 #: allianceauth/authentication/views.py:244
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "注册令牌过期。" msgstr "注册令牌过期。"
#: allianceauth/authentication/views.py:252 #: allianceauth/authentication/views.py:302
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:307
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:312
msgid "Registration of new accounts is not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "" msgstr ""
@@ -272,19 +322,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
@@ -614,36 +651,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 " "Internal group, users cannot see, join or request to join this "
"group.<br>Used for groups such as Members, Corp_*, Alliance_* " "group.<br>Used for groups such as Members, Corp_*, Alliance_* "
"etc.<br><b>Overrides Hidden and Open options when selected.</b>" "etc.<br><b>Overrides 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 "
@@ -651,66 +693,66 @@ 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 " "Group leaders 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: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 ""
@@ -754,7 +796,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."
@@ -937,86 +979,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 "已将用户%(user)s从用户组%(group)s中移除" msgstr "已将用户%(user)s从用户组%(group)s中移除"
#: 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 "已接受用户%(mainchar)s加入%(group)s的申请" msgstr "已接受用户%(mainchar)s加入%(group)s的申请"
#: 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 "在处理用户%(mainchar)s加入%(group)s的申请的过程中出现了一个搞不定的错误" msgstr "在处理用户%(mainchar)s加入%(group)s的申请的过程中出现了一个搞不定的错误"
#: 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 "%(mainchar)s加入%(group)s的申请已拒绝" msgstr "%(mainchar)s加入%(group)s的申请已拒绝"
#: 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 "%(mainchar)s加入%(group)s的申请已通过" msgstr "%(mainchar)s加入%(group)s的申请已通过"
#: 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 "在处理%(mainchar)s离开%(group)s的程序时发生了未知的错误" msgstr "在处理%(mainchar)s离开%(group)s的程序时发生了未知的错误"
#: 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 "%(mainchar)s离开%(group)s的请求已被拒绝" msgstr "%(mainchar)s离开%(group)s的请求已被拒绝"
#: 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 "修改已经应用到%(group)s啦" msgstr "修改已经应用到%(group)s啦"
#: 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 "已经离开群组%(group)s" msgstr "已经离开群组%(group)s"
@@ -1078,16 +1120,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
@@ -1198,11 +1230,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 +1375,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 +1384,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
@@ -1426,10 +1458,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 "声望"
@@ -2152,11 +2180,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
@@ -2172,11 +2200,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 "登出"
@@ -2232,22 +2260,30 @@ msgid "Objective"
msgstr "声望" msgstr "声望"
#: allianceauth/timerboard/form.py:64 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "剩余天数" msgstr "剩余天数"
#: allianceauth/timerboard/form.py:65 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "剩余小时数" msgstr "剩余小时数"
#: allianceauth/timerboard/form.py:67 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "剩余分钟" msgstr "剩余分钟"
#: allianceauth/timerboard/form.py:69 #: 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 "受限制的公司"
@@ -2257,15 +2293,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 +2309,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"

View File

@@ -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

View File

@@ -172,7 +172,7 @@ MESSAGE_TAGS = {
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django_redis.cache.RedisCache", "BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1" # change the 1 here to change the database used "LOCATION": "redis://127.0.0.1:6379/1" # change the 1 here for the DB used
} }
} }
@@ -202,6 +202,8 @@ LOGOUT_REDIRECT_URL = 'authentication:dashboard' # destination after logging ou
# scopes required on new tokens when logging in. Cannot be blank. # scopes required on new tokens when logging in. Cannot be blank.
LOGIN_TOKEN_SCOPES = ['publicData'] LOGIN_TOKEN_SCOPES = ['publicData']
EMAIL_TIMEOUT = 15
# number of days email verification links are valid for # number of days email verification links are valid for
ACCOUNT_ACTIVATION_DAYS = 1 ACCOUNT_ACTIVATION_DAYS = 1

View File

@@ -32,10 +32,13 @@ INSTALLED_APPS += [
# To change the logging level for extensions, uncomment the following line. # To change the logging level for extensions, uncomment the following line.
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG' # LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
# By default apps are prevented from having public views for security reasons. # By default, apps are prevented from having public views for security reasons.
# If you want to allow specific apps to have public views # To allow specific apps to have public views, add them to APPS_WITH_PUBLIC_VIEWS
# you can put there names here (same name as in INSTALLED_APPS): # » The format is the same as in INSTALLED_APPS
APPS_WITH_PUBLIC_VIEWS = [] # » The app developer must also explicitly allow public views for their app
APPS_WITH_PUBLIC_VIEWS = [
]
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3 # Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
DATABASES['default'] = { DATABASES['default'] = {

View File

@@ -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 '' )

View File

@@ -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.

View File

@@ -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)

View File

@@ -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
): ):

View File

@@ -122,7 +122,7 @@ ul.list-group.list-group-horizontal > li.list-group-item {
padding-top: 0.5rem; padding-top: 0.5rem;
} }
.navbar-nav > li.top-user-menu.with-main-character a { .navbar-nav > li.top-user-menu a {
padding: 14px; padding: 14px;
} }

View File

@@ -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>

View File

@@ -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">
@@ -21,33 +21,37 @@
</head> </head>
<body class="{% if NIGHT_MODE %}template-dark-mode{% else %}template-light-mode{% endif %}"> <body class="{% if NIGHT_MODE %}template-dark-mode{% else %}template-light-mode{% endif %}">
{% if user.is_authenticated %} <div id="wrapper" class="container">
<div id="wrapper" class="container"> <!-- Navigation -->
<!-- Navigation --> {% include 'allianceauth/top-menu.html' %}
{% include 'allianceauth/top-menu.html' %}
<div class="row" id="site-body-wrapper"> <div class="clearfix{% if user.is_authenticated %} row{% endif %}" id="site-body-wrapper">
{% if user.is_authenticated %}
{% include 'allianceauth/side-menu.html' %} {% include 'allianceauth/side-menu.html' %}
<div class="col-sm-10"> {% endif %}
{% include 'allianceauth/messages.html' %}
{% block content %} <div class="{% if user.is_authenticated %}col-sm-10{% else %}col-sm-12{% endif %}">
{% endblock content %} {% include 'allianceauth/messages.html' %}
</div>
<div class="clearfix"></div> {% block content %}
{% endblock content %}
</div> </div>
</div> </div>
{% endif %} </div>
{% include 'bundles/bootstrap-js.html' %} {% include 'bundles/bootstrap-js.html' %}
{% include 'bundles/jquery-visibility-js.html' %} {% include 'bundles/jquery-visibility-js.html' %}
<script> {% if user.is_authenticated %}
let notificationUPdateSettings = { <script>
notificationsListViewUrl: "{% url 'notifications:list' %}", let notificationUPdateSettings = {
notificationsRefreshTime: "{% notifications_refresh_time %}", notificationsListViewUrl: "{% url 'notifications:list' %}",
userNotificationsCountViewUrl: "{% url 'notifications:user_notifications_count' request.user.pk %}" notificationsRefreshTime: "{% notifications_refresh_time %}",
}; userNotificationsCountViewUrl: "{% url 'notifications:user_notifications_count' request.user.pk %}"
</script> };
{% include 'bundles/refresh-notifications-js.html' %} </script>
{% include 'bundles/refresh-notifications-js.html' %}
{% endif %}
{% include 'bundles/evetime-js.html' %} {% include 'bundles/evetime-js.html' %}
{% block extra_javascript %} {% block extra_javascript %}

View File

@@ -11,7 +11,10 @@
</span> </span>
{% endwith %} {% endwith %}
{% else %} {% else %}
{% translate "User Menu" %} <img class="img-rounded ra-avatar" src="{{ 1|character_portrait_url:32 }}" alt="{{ main.character_name }}">
<span class="hidden-sm hidden-md hidden-lg">
{% translate "User Menu" %}
</span>
{% endif %} {% endif %}
<span class="caret"></span> <span class="caret"></span>
</a> </a>

View File

@@ -22,9 +22,12 @@
<li class="nav-item-eve-time"> <li class="nav-item-eve-time">
<div class="eve-time-wrapper">{% translate "Eve Time" %}: <span class="eve-time-clock"></span></div> <div class="eve-time-wrapper">{% translate "Eve Time" %}: <span class="eve-time-clock"></span></div>
</li> </li>
<li class="{% navactive request 'notifications:' %}" id="menu_item_notifications">
{% include 'allianceauth/notifications_menu_item.html' %} {% if user.is_authenticated %}
</li> <li class="{% navactive request 'notifications:' %}" id="menu_item_notifications">
{% include 'allianceauth/notifications_menu_item.html' %}
</li>
{% endif %}
{% include 'allianceauth/top-menu-user-dropdown.html' %} {% include 'allianceauth/top-menu-user-dropdown.html' %}
</ul> </ul>

View File

@@ -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:

View 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)

View File

@@ -1,6 +1,9 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch
from allianceauth.authentication.task_statistics.helpers import ItemCounter from allianceauth.utils.counters import ItemCounter
MODULE_PATH = "allianceauth.utils.counters"
COUNTER_NAME = "test-counter" COUNTER_NAME = "test-counter"
@@ -72,3 +75,46 @@ class TestItemCounter(TestCase):
counter.decr(1) counter.decr(1)
# then # then
self.assertEqual(counter.value(), -1) 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)

View File

@@ -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.0 AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.8.1
# Nginx Proxy Manager # Nginx Proxy Manager
PROXY_HTTP_PORT=80 PROXY_HTTP_PORT=80

View File

@@ -1,5 +1,5 @@
FROM python:3.9-slim FROM python:3.9-slim
ARG AUTH_VERSION=v3.6.0 ARG AUTH_VERSION=v3.8.1
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

72
docs/_static/css/tabs.css vendored Normal file
View File

@@ -0,0 +1,72 @@
.sphinx-tabs {
margin-bottom: 1rem;
}
[role="tablist"] {
border-bottom: 1px solid #a0b3bf;
}
.sphinx-tabs-tab {
position: relative;
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif;
color: #1D5C87;
line-height: 24px;
margin: 0;
font-size: 16px;
font-weight: 400;
background-color: rgba(255, 255, 255, 0);
border-radius: 5px 5px 0 0;
border: 0;
padding: 1rem 1.5rem;
margin-bottom: 0;
}
.sphinx-tabs-tab[aria-selected="true"] {
font-weight: 700;
border: 1px solid #a0b3bf;
border-bottom: 1px solid white;
margin: -1px;
background-color: white;
}
.sphinx-tabs-tab:focus {
z-index: 1;
outline-offset: 1px;
}
.sphinx-tabs-panel {
position: relative;
padding: 1rem;
border: 1px solid #a0b3bf;
margin: 0px -1px -1px -1px;
border-radius: 0 0 5px 5px;
border-top: 0;
background: white;
}
.sphinx-tabs-panel.code-tab {
padding: 0.4rem;
}
.sphinx-tab img {
margin-bottom: 24 px;
}
/* Dark theme preference styling */
@media (prefers-color-scheme: dark) {
.sphinx-tabs-panel {
color: white;
background-color: rgb(50, 50, 50);
}
.sphinx-tabs-tab {
color: white;
background-color: rgba(255, 255, 255, 0.05);
}
.sphinx-tabs-tab[aria-selected="true"] {
border-bottom: 1px solid rgb(50, 50, 50);
background-color: rgb(50, 50, 50);
}
}

View File

@@ -1,15 +1,10 @@
# Configuration file for the Sphinx documentation builder.
# #
# Alliance Auth documentation build configuration file, created by # This file only contains a selection of the most common options. For a full
# sphinx-quickstart on Tue Jan 3 12:56:59 2017. # list see the documentation:
# # https://www.sphinx-doc.org/en/master/usage/configuration.html
# This file is execfile()d with the current directory set to its
# containing dir. # -- Path setup --------------------------------------------------------------
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@@ -18,23 +13,23 @@
import os import os
import sys import sys
import django import django
import sphinx_rtd_theme # noqa
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings_all' os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings_all'
django.setup() django.setup()
# -- Project information -----------------------------------------------------
project = 'Alliance Auth'
copyright = '2018-2023, Alliance Auth'
author = 'Alliance Auth Team'
# -- General configuration ---------------------------------------------------
# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True' on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# Support for recommonmark module
import recommonmark
from recommonmark.transform import AutoStructify
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
@@ -42,11 +37,18 @@ from recommonmark.transform import AutoStructify
extensions = [ extensions = [
'sphinx_rtd_theme', 'sphinx_rtd_theme',
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon', 'sphinx.ext.napoleon',
'recommonmark',
'sphinxcontrib_django2',
'sphinx.ext.viewcode', 'sphinx.ext.viewcode',
'sphinx_copybutton' 'myst_parser',
'sphinxcontrib_django',
'sphinx_copybutton',
'sphinx_tabs.tabs'
]
myst_enable_extensions = [
"colon_fence",
"tasklist",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@@ -59,41 +61,16 @@ templates_path = ['_templates']
source_suffix = ['.md', '.rst'] source_suffix = ['.md', '.rst']
# The master toctree document. # The master toctree document.
master_doc = 'index' root_doc = 'index'
# General information about the project.
project = 'Alliance Auth'
copyright = '2018-2022, Alliance Auth'
author = 'Alliance Auth Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '3.0'
# The full version, including alpha/beta/rc tags.
# release = u'1.14.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing. # If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False todo_include_todos = False
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
@@ -101,20 +78,11 @@ todo_include_todos = False
# #
html_theme = 'sphinx_rtd_theme' html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'navigation_depth': 4,
}
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ['_static']
html_css_files = ["css/rtd_dark.css"] html_css_files = ["css/rtd_dark.css", "css/tabs.css"]
# -- Options for HTMLHelp output ------------------------------------------ # -- Options for HTMLHelp output ------------------------------------------
@@ -147,7 +115,7 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, 'AllianceAuth.tex', 'Alliance Auth Documentation', 'R4stl1n', 'manual'), (root_doc, 'AllianceAuth.tex', 'Alliance Auth Documentation', 'Alliance Auth Team', 'manual'),
] ]
@@ -156,7 +124,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
(master_doc, 'allianceauth', 'Alliance Auth Documentation', (root_doc, 'allianceauth', 'Alliance Auth Documentation',
[author], 1) [author], 1)
] ]
@@ -169,15 +137,12 @@ add_module_names = False
# Grouping the document tree into Texinfo files. List of tuples # Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [(
(master_doc, 'AllianceAuth', 'Alliance Auth Documentation', root_doc, 'AllianceAuth', 'Alliance Auth Documentation', author, 'AllianceAuth',
author, 'AllianceAuth', 'An auth system for EVE Online to help in-game organizations manage online service access.', 'An auth system for EVE Online to help in-game organizations manage online service access.', 'Miscellaneous'),
'Miscellaneous'),
] ]
def setup(app): # -- https://myst-parser.readthedocs.io/en/latest/configuration.html
app.add_config_value('recommonmark_config', {
'auto_toc_tree_section': 'Contents', myst_heading_anchors = 4
}, True)
app.add_transform(AutoStructify)

View File

@@ -1,12 +1,12 @@
# Contributing # Contributing
Alliance Auth is developed by the community and we are always looking to welcome new contributors. If you are interested in contributing, here are some ideas where to start: Alliance Auth is developed by the community, and we are always looking to welcome new contributors. If you are interested in contributing, here are some ideas where to start:
## Publish a new community app or service ## Publish a new community app or service
One great way to contribute is to develop and publish your own community app or service for Alliance Auth. By design Auth only comes with some basic features and therefore heavily relies on the community to provide apps to extend Auth with additional features. One great way to contribute is to develop and publish your own community app or service for Alliance Auth. By design, Auth only comes with some basic features and therefore heavily relies on the community to provide apps to extend Auth with additional features.
To publish your app make sure it can be installed from a public repo or PyPI. Once it's ready, you can inform everybody about your new app by posting it to our [list of community apps](/features/community/index.md). To publish your app, make sure it can be installed from a public repo or PyPI. Once it's ready, you can inform everybody about your new app by posting it to our [list of community apps](/features/community/index.md).
If you are looking for ideas on what to make, you can check out Auth's [issue list](https://gitlab.com/allianceauth/allianceauth/-/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=enhancement). Many of those issues are feature requests that will probably never make into Auth core, but would be awesome to have as community app or service. You could also ask the other devs on our Discord server for ideas or to help you get a feeling about which new features might be in higher demand than others. If you are looking for ideas on what to make, you can check out Auth's [issue list](https://gitlab.com/allianceauth/allianceauth/-/issues?scope=all&utf8=%E2%9C%93&state=opened&label_name[]=enhancement). Many of those issues are feature requests that will probably never make into Auth core, but would be awesome to have as community app or service. You could also ask the other devs on our Discord server for ideas or to help you get a feeling about which new features might be in higher demand than others.
@@ -16,7 +16,7 @@ There are quite a few great community apps that need help from additional mainta
Sometimes original app owners may even be looking to completely hand over their apps to a new owner. Sometimes original app owners may even be looking to completely hand over their apps to a new owner.
If you are interested to help maintain an existing community app or service you can just start working on open issues and create merge requests. Or just ask other devs on our Discord. If you are interested to help maintain an existing community app or service, you can start working on open issues and create merge requests. Or just ask other devs on our Discord.
## Help with improving Auth documentation ## Help with improving Auth documentation
@@ -26,7 +26,7 @@ Auth has an extensive [documentation](https://allianceauth.readthedocs.io/en/lat
One of the main functions of the Auth Discord server is to help the community with any support question they may have when installing or running an Auth installation. One of the main functions of the Auth Discord server is to help the community with any support question they may have when installing or running an Auth installation.
Note that you do not need a be part of any official group to become a supporter. Just jump in and help with answering new questions from the community if you know how to help. Note that you do not need to be part of any official group to become a supporter. Jump in and help with answering new questions from the community if you know how to help.
## Help to improve Alliance Auth core ## Help to improve Alliance Auth core
@@ -34,12 +34,12 @@ Alliance Auth has an issue list, which is usually the basis for all maintenance
We usually have a long list of open issues and very much welcome every help to fix existing bugs or work on new features for Auth. We usually have a long list of open issues and very much welcome every help to fix existing bugs or work on new features for Auth.
Before starting to code on any topic we'd suggest talking to the other devs on Discord to make sure your issue is not already being worked on. Also, some feature request may be better implemented in a community app. Another aspect, which is best clarified by talking with the other devs. Before starting to code on any topic, we'd suggest talking to the other devs on Discord to make sure your issue is not already being worked on. Also, some feature requests may be better implemented in a community app. Another aspect, which is best clarified by talking with the other devs.
If you like to contribute to Auth core, but are unsure where to start, we have a dedicated label for issues that are suitable for beginners: [beginner friendly](https://gitlab.com/allianceauth/allianceauth/-/issues?label_name%5B%5D=beginner+friendly). If you like to contribute to Auth core, but are unsure where to start, we have a dedicated label for issues that are suitable for beginners: [beginner-friendly](https://gitlab.com/allianceauth/allianceauth/-/issues?label_name%5B%5D=beginner+friendly).
## Additional Resources ## Additional Resources
For more information on how to create community apps or how to setup a developer environment for Auth, please see our official [developer documentation](/development/index.md). For more information on how to create community apps or how to set up a developer environment for Auth, please see our official [developer documentation](/development/index.md).
For getting in touch with other contributors please feel free to join the [Alliance Auth Discord server](https://discord.gg/fjnHAmk). For getting in touch with other contributors, please feel free to join the [Alliance Auth Discord server](https://discord.gg/fjnHAmk).

View File

@@ -2,14 +2,13 @@
It is possible to customize your **Alliance Auth** instance. It is possible to customize your **Alliance Auth** instance.
```eval_rst :::{warning}
.. warning:: Keep in mind that you may need to update some of your customizations manually after new Auth releases (e.g., when replacing templates).
Keep in mind that you may need to update some of your customizations manually after new Auth releases (e.g. when replacing templates). :::
```
## Site name ## Site name
You can replace the default name shown on the web site with your own, e.g. the name of your Alliance. You can replace the default name shown on the website with your own, e.g., the name of your Alliance.
Just update `SITE_NAME` in your `local.py` settings file accordingly, e.g.: Just update `SITE_NAME` in your `local.py` settings file accordingly, e.g.:
@@ -19,15 +18,15 @@ SITE_NAME = 'Awesome Alliance'
## Custom Static and Templates ## Custom Static and Templates
Within your auth project exists two folders named `static` and `templates`. These are used by Django for rendering web pages. Static refers to content Django does not need to parse before displaying, such as CSS styling or images. When running via a WSGI worker such as Gunicorn static files are copied to a location for the web server to read from. Templates are always read from the template folders, rendered with additional context from a view function, and then displayed to the user. Within your auth project exists two folders named `static` and `templates`. These are used by Django for rendering web pages. Static refers to content Django does not need to parse before displaying, such as CSS styling or images. When running via a WSGI worker such as Gunicorn, static files are copied to a location for the web server to read from. Templates are always read from the template folders, rendered with additional context from a view function, and then displayed to the user.
You can add extra static or templates by putting files in these folders. Note that changes to static requires running the `python manage.py collectstatic` command to copy to the web server directory. You can add extra static or templates by putting files in these folders. Note that changes to static require running the `python manage.py collectstatic` command to copy to the web server directory.
It is possible to overload static and templates shipped with Django or Alliance Auth by including a file with the exact path of the one you wish to overload. For instance if you wish to add extra links to the menu bar by editing the template, you would make a copy of the `allianceauth/templates/allianceauth/base.html` file to `myauth/templates/allianceauth/base.html` and edit it there. Notice the paths are identical after the `templates/` directory - this is critical for it to be recognized. Your custom template would be used instead of the one included with Alliance Auth when Django renders the web page. Similar idea for static: put CSS or images at an identical path after the `static/` directory and they will be copied to the web server directory instead of the ones included. It is possible to overload static and templates shipped with Django or Alliance Auth by including a file with the exact path of the one you wish to overload. For instance if you wish to add extra links to the menu bar by editing the template, you would make a copy of the `allianceauth/templates/allianceauth/base.html` file to `myauth/templates/allianceauth/base.html` and edit it there. Notice the paths are identical after the `templates/` directory - this is critical for it to be recognized. Your custom template would be used instead of the one included with Alliance Auth when Django renders the web page. Similar idea for static: put CSS or images at an identical path after the `static/` directory and they will be copied to the web server directory instead of the ones included.
## Custom URLs and Views ## Custom URLs and Views
It is possible to add or override URLs with your auth project's URL config file. Upon install it is of the form: It is possible to add or override URLs with your auth project's URL config file. Upon installing, it is of the form:
```python ```python
from django.urls import re_path from django.urls import re_path
@@ -57,7 +56,7 @@ urlpatterns = [
] ]
``` ```
Additionally you can override URLs used by Alliance Auth here: Additionally, you can override URLs used by Alliance Auth here:
```python ```python
from django.urls import re_path from django.urls import re_path
@@ -74,47 +73,46 @@ urlpatterns = [
## Example: Adding an external link to the sidebar ## Example: Adding an external link to the sidebar
As an example we are adding an external links to the Alliance Auth sidebar using the template overrides feature. For our example let's add a link to Google's start page. As an example, we are adding an external links to the Alliance Auth sidebar using the template overrides feature. For example, let's add a link to Google's start page.
### Step 1 - Create the template override folder ### Step 1 - Create the template override folder
First you need to create the folder for the template on your server. For AA to pick it up it has to match a specific structure. First, you need to create the folder for the template on your server. For Alliance Auth to pick it up, it has to match a specific structure.
If you have a default installation you can create the folder like this: If you have a default installation, you can create a folder like this:
```sh ```shell
mkdir -p /home/allianceserver/myauth/myauth/templates/allianceauth mkdir -p /home/allianceserver/myauth/myauth/templates/allianceauth
``` ```
### Step 2 - Download the original template ### Step 2 - Download the original template
Next you need to download a copy of the original template file we want to change. For that let's move into the above folder and then download the file into the current folder with: Next, you need to download a copy of the original template file we want to change. For that, let's move into the above folder and then download the file into the current folder with:
```sh ```shell
cd /home/allianceserver/myauth/myauth/templates/allianceauth cd /home/allianceserver/myauth/myauth/templates/allianceauth
wget <https://gitlab.com/allianceauth/allianceauth/-/raw/master/allianceauth/templates/allianceauth/side-menu.html> wget <https://gitlab.com/allianceauth/allianceauth/-/raw/master/allianceauth/templates/allianceauth/side-menu.html>
``` ```
### Step 3 - Modify the template ### Step 3 - Modify the template
Now you can modify the template to add your custom link. To create the google link we can add this snippet *between* the `{% menu_items %}` and the `</ul>` tag: Now you can modify the template to add your custom link. To create the Google link, we can add this snippet *between* the `{% menu_items %}` and the `</ul>` tag:
```sh ```shell
nano /home/allianceserver/myauth/myauth/templates/allianceauth/side-menu.html nano /home/allianceserver/myauth/myauth/templates/allianceauth/side-menu.html
``` ```
```jinja ```django
<li> <li>
<a href="https://www.google.com/" target="_blank"> <a href="https://www.google.com/" target="_blank">
<i class="fab fa-google fa-fw"></i>Google <i class="fab fa-google fa-fw"></i>Google
</a> </a>
</li> </li>
``` ```
```eval_rst :::{hint}
.. hint::
You can find other icons with a matching style on the `Font Awesome site <https://fontawesome.com/v5/search?m=free>`_ . AA currently uses Font Awesome version 5. You also want to keep the ``fa-fw`` tag to ensure all icons have the same width. You can find other icons with a matching style on the `Font Awesome site <https://fontawesome.com/v5/search?m=free>`_ . AA currently uses Font Awesome version 5. You also want to keep the ``fa-fw`` tag to ensure all icons have the same width.
``` :::
### Step 4 - Restart your AA services ### Step 4 - Restart your AA services

View 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
```shell
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.

View File

@@ -4,15 +4,15 @@ The documentation for Alliance Auth uses [Sphinx](http://www.sphinx-doc.org/) to
to specific branches is made (master, primarily), the repository is automatically pulled, docs built and deployed on to specific branches is made (master, primarily), the repository is automatically pulled, docs built and deployed on
[readthedocs.org](https://readthedocs.org/). [readthedocs.org](https://readthedocs.org/).
Documentation was migrated from the GitHub wiki pages and into the repository to allow documentation changes to be Documentation was migrated from the GitHub wiki pages and into the repository to allow documentation changes to be
included with pull requests. This means that documentation can be guaranteed to be updated when a pull request is included with pull requests. This means that documentation can be guaranteed to be updated when a pull request is
accepted rather than hoping documentation is updated afterwards or relying on maintainers to do the work. It also accepted rather than hoping documentation is updated afterwards or relying on maintainers to do the work. It also
allows for documentation to be maintained at different versions more easily. allows for documentation to be maintained at different versions more easily.
## Building Documentation ## Building Documentation
If you're developing new documentation, its likely you'll want or need to test build it before committing to your
branch. To achieve this you can use Sphinx to build the documentation locally as it appears on Read the Docs. If you're developing new documentation, it's likely you'll want or need to test build it before committing to your
branch. To achieve this, you can use Sphinx to build the documentation locally as it appears on Read the Docs.
Activate your virtual environment (if you're using one) and install the documentation requirements found in Activate your virtual environment (if you're using one) and install the documentation requirements found in
`docs/requirements.txt` using pip, e.g. `pip install -r docs/requirements.txt`. `docs/requirements.txt` using pip, e.g. `pip install -r docs/requirements.txt`.
@@ -24,15 +24,16 @@ build in the `/docs/_build/` directory.
Occasionally you may need to fully rebuild the documents by running `make clean` first, usually when you add or Occasionally you may need to fully rebuild the documents by running `make clean` first, usually when you add or
rearrange toctrees. rearrange toctrees.
## Documentation Format ## Documentation Format
CommonMark Markdown is the current preferred format, via [recommonmark](https://github.com/rtfd/recommonmark). CommonMark-plus Markdown is the current preferred format, via [MyST-Parser](https://github.com/executablebooks/MyST-Parser).
reStructuredText is supported if required, or you can execute snippets of reST inside Markdown by using a code block: reStructuredText is supported if required, or you can execute snippets of MyST inside Markdown by using a code block:
```eval_rst ````md
reStructuredText here ```{eval-rst}
``` reStructuredText here
```
````
Markdown is used elsewhere on Github so it provides the most portability of documentation from Issues and Pull Requests Markdown is used elsewhere on GitHub, so it provides the most portability of documentation from Issues and Pull Requests
as well as providing an easier initial migration path from the Github wiki. as well as providing an easier initial migration path from the GitHub wiki.

View File

@@ -2,9 +2,9 @@
This section contains important information on how to develop Alliance Auth itself. This section contains important information on how to develop Alliance Auth itself.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
documentation documentation
``` code-style
:::

View File

@@ -2,12 +2,11 @@
This section describes how to extend **Alliance Auth** with custom apps and services. This section describes how to extend **Alliance Auth** with custom apps and services.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
integrating-services integrating-services
menu-hooks menu-hooks
url-hooks url-hooks
logging logging
``` :::

View File

@@ -1,16 +1,16 @@
# Integrating Services # Integrating Services
One of the primary roles of Alliance Auth is integrating with external services in order to authenticate and manage users. This is achieved through the use of service modules. One of the primary roles of Alliance Auth is integrating with external services to authenticate and manage users. This is achieved through the use of service modules.
## The Service Module ## The Service Module
Each service module is its own self contained Django app. It will likely contain views, models, migrations and templates. Anything that is valid in a Django app is valid in a service module. Each service module is its own self-contained Django app. It will likely contain views, models, migrations and templates. Anything that is valid in a Django app is valid in a service module.
Normally service modules live in `services.modules` though they may also be installed as external packages and installed via `pip` if you wish. A module is installed by including it in the `INSTALLED_APPS` setting. Normally service modules live in `services.modules` though they may also be installed as external packages and installed via `pip` if you wish. A module is installed by including it in the `INSTALLED_APPS` setting.
### Service Module Structure ### Service Module Structure
Typically a service will contain 5 key components: Typically, a service will contain 5 key components:
- [The Hook](#the-hook) - [The Hook](#the-hook)
- [The Service Manager](#the-service-manager) - [The Service Manager](#the-service-manager)
@@ -20,60 +20,65 @@ Typically a service will contain 5 key components:
The architecture looks something like this: The architecture looks something like this:
urls -------▶ Views ```none
▲ | urls -------▶ Views
| | |
| | |
ServiceHook ----▶ Tasks ----▶ Manager | ▼
ServiceHook ----▶ Tasks ----▶ Manager
|
| |
AllianceAuth |
AllianceAuth
Where: Where:
Module --▶ Dependency/Import Module --▶ Dependency/Import
```
While this is the typical structure of the existing services modules, there is no enforcement of this structure and you are, effectively, free to create whatever architecture may be necessary. A service module need not even communicate with an external service, for example, if similar triggers such as validate_user, delete_user are required for a module it may be convenient to masquerade as a service. Ideally though, using the common structure improves the maintainability for other developers. While this is the typical structure of the existing services modules, there is no enforcement of this structure, and you are, effectively, free to create whatever architecture may be necessary. A service module need not even communicate with an external service, for example, if similar triggers such as validate_user, delete_user are required for a module it may be convenient to masquerade as a service. Ideally, using the common structure improves the maintainability for other developers.
### The Hook ### The Hook
In order to integrate with Alliance Auth service modules must provide a `services_hook`. This hook will be a function that returns an instance of the `services.hooks.ServiceHook` class and decorated with the `@hooks.registerhook` decorator. For example: To integrate with Alliance Auth service modules must provide a `services_hook`. This hook will be a function that returns an instance of the `services.hooks.ServiceHook` class and decorated with the `@hooks.registerhook` decorator. For example:
@hooks.register('services_hook') ```python
def register_service(): @hooks.register('services_hook')
return ExampleService() def register_service():
return ExampleService()
```
This would register the ExampleService class which would need to be a subclass of `services.hooks.ServiceHook`. This would register the ExampleService class which would need to be a subclass of `services.hooks.ServiceHook`.
```eval_rst :::{important}
.. important:: The hook **MUST** be registered in ``yourservice.auth_hooks`` along with any other hooks you are registering for Alliance Auth.
The hook **MUST** be registered in ``yourservice.auth_hooks`` along with any other hooks you are registering for Alliance Auth. :::
```
A subclassed `ServiceHook` might look like this: A subclassed `ServiceHook` might look like this:
class ExampleService(ServicesHook): ```python
def __init__(self): class ExampleService(ServicesHook):
ServicesHook.__init__(self) def __init__(self):
self.urlpatterns = urlpatterns ServicesHook.__init__(self)
self.service_url = 'http://exampleservice.example.com' self.urlpatterns = urlpatterns
self.service_url = 'https://exampleservice.example.com'
""" """
Overload base methods here to implement functionality Overload base methods here to implement functionality
""" """
```
### The ServiceHook class ### The ServiceHook class
The base `ServiceHook` class defines function signatures that Alliance Auth will call under certain conditions in order to trigger some action in the service. The base `ServiceHook` class defines function signatures that Alliance Auth will call under certain conditions to trigger some action in the service.
You will need to subclass `services.hooks.ServiceHook` in order to provide implementation of the functions so that Alliance Auth can interact with the service correctly. All of the functions are optional, so its up to you to define what you need. You will need to subclass `services.hooks.ServiceHook` in order to provide implementation of the functions so that Alliance Auth can interact with the service correctly. All the functions are optional, so its up to you to define what you need.
Instance Variables: Instance Variables:
- [self.name](#self-name) - [self.name](#selfname)
- [self.urlpatterns](#self-url-patterns) - [self.urlpatterns](#selfurlpatterns)
- [self.service_ctrl_template](#self-service-ctrl-template) - [self.service_ctrl_template](#selfservice_ctrl_template)
Properties: Properties:
@@ -88,8 +93,6 @@ Functions:
- [update_groups](#update_groups) - [update_groups](#update_groups)
- [update_groups_bulk](#update_groups_bulk) - [update_groups_bulk](#update_groups_bulk)
- [update_all_groups](#update_all_groups) - [update_all_groups](#update_all_groups)
- [service_enabled_members](#service_enabled_members)
- [service_enabled_blues](#service_enabled_blues)
- [service_active_for_user](#service_active_for_user) - [service_active_for_user](#service_active_for_user)
- [show_service_ctrl](#show_service_ctrl) - [show_service_ctrl](#show_service_ctrl)
- [render_service_ctrl](#render_service_ctrl) - [render_service_ctrl](#render_service_ctrl)
@@ -100,30 +103,32 @@ Internal name of the module, should be unique amongst modules.
#### self.urlpatterns #### self.urlpatterns
You should define all of your service URLs internally, usually in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns. You should usually define all of your service URLs internally, in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns.
from . import urls ```python
... from . import urls
class MyService(ServiceHook): ...
def __init__(self): class MyService(ServiceHook):
... def __init__(self):
self.urlpatterns = urls.urlpatterns ...
self.urlpatterns = urls.urlpatterns
```
All of your apps defined urlpatterns will then be included in the `URLconf` when the core application starts. All of your apps defined urlpatterns will then be included in the `URLconf` when the core application starts.
#### self.service_ctrl_template #### self.service_ctrl_template
This is provided as a courtesy and defines the default template to be used with [render_service_ctrl](#render-service-ctrl). You are free to redefine or not use this variable at all. This is provided as a courtesy and defines the default template to be used with [render_service_ctrl](#render_service_ctrl). You are free to redefine or not use this variable at all.
#### title #### title
This is a property which provides a user friendly display of your service's name. It will usually do a reasonably good job unless your service name has punctuation or odd capitalization. If this is the case you should override this method and return a string. This is a property which provides a user-friendly display of your service's name. It will usually do a reasonably good job unless your service name has punctuation or odd capitalization. If this is the case, you should override this method and return a string.
#### delete_user #### delete_user
`def delete_user(self, user, notify_user=False):` `def delete_user(self, user, notify_user=False):`
Delete the users service account, optionally notify them that the service has been disabled. The `user` parameter should be a Django User object. If notify_user is set to `True` a message should be set to the user via the `notifications` module to alert them that their service account has been disabled. Delete the user's service account, optionally notify them that the service has been disabled. The `user` parameter should be a Django User object. If notify_user is set to `True` a message should be set to the user via the `notifications` module to alert them that their service account has been disabled.
The function should return a boolean, `True` if successfully disabled, `False` otherwise. The function should return a boolean, `True` if successfully disabled, `False` otherwise.
@@ -131,14 +136,16 @@ The function should return a boolean, `True` if successfully disabled, `False` o
`def validate_user(self, user):` `def validate_user(self, user):`
Validate the users service account, deleting it if they should no longer have access. The `user` parameter should be a Django User object. Validate the user's service account, deleting it if they should no longer have access. The `user` parameter should be a Django User object.
An implementation will probably look like the following: An implementation will probably look like the following:
def validate_user(self, user): ```python
logger.debug('Validating user %s %s account' % (user, self.name)) def validate_user(self, user):
if ExampleTasks.has_account(user) and not self.service_active_for_user(user): logger.debug('Validating user %s %s account' % (user, self.name))
self.delete_user(user, notify_user=True) if ExampleTasks.has_account(user) and not self.service_active_for_user(user):
self.delete_user(user, notify_user=True)
```
No return value is expected. No return value is expected.
@@ -148,7 +155,7 @@ This function will be called periodically on all users to validate that the give
`def sync_nickname(self, user):` `def sync_nickname(self, user):`
Very optional. As of writing only one service defines this. The `user` parameter should be a Django User object. When called, the given users nickname for the service should be updated and synchronized with the service. Very optional. As of writing, only one service defines this. The `user` parameter should be a Django User object. When called, the given users nickname for the service should be updated and synchronized with the service.
If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users. If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users.
@@ -158,7 +165,7 @@ If this function is defined, an admin action will be registered on the Django Us
Updates the nickname for a list of users. The `users` parameter must be a list of Django User objects. Updates the nickname for a list of users. The `users` parameter must be a list of Django User objects.
If this method is defined, the admin action for updating service related nicknames for users will call this bulk method instead of sync_nickname. This gives you more control over how mass updates are executed, e.g. ensuring updates do not run in parallel to avoid causing rate limit violations from an external API. If this method is defined, the admin action for updating service related nicknames for users will call this bulk method instead of sync_nickname. This gives you more control over how mass updates are executed, e.g., ensuring updates do not run in parallel to avoid causing rate limit violations from an external API.
This is an optional method. This is an optional method.
@@ -166,12 +173,12 @@ This is an optional method.
`def update_groups(self, user):` `def update_groups(self, user):`
Update the users group membership. The `user` parameter should be a Django User object. Update the user's group membership. The `user` parameter should be a Django User object.
When this is called the service should determine the groups the user is a member of and synchronize the group membership with the external service. If you service does not support groups then you are not required to define this. When this is called, the service should determine the groups the user is a member of and synchronize the group membership with the external service. If your service does not support groups, then you are not required to define this.
If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users. If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users.
This action is usually called via a signal when a users group membership changes (joins or leaves a group). This action is usually called via a signal when a user's group membership changes (joins or leaves a group).
#### update_groups_bulk #### update_groups_bulk
@@ -179,7 +186,7 @@ This action is usually called via a signal when a users group membership changes
Updates the group memberships for a list of users. The `users` parameter must be a list of Django User objects. Updates the group memberships for a list of users. The `users` parameter must be a list of Django User objects.
If this method is defined, the admin action for updating service related groups for users will call this bulk method instead of update_groups. This gives you more control over how mass updates are executed, e.g. ensuring updates do not run in parallel to avoid causing rate limit violations from an external API. If this method is defined, the admin action for updating service related groups for users will call this bulk method instead of update_groups. This gives you more control over how mass updates are executed, e.g., ensuring updates do not run in parallel to avoid causing rate limit violations from an external API.
This is an optional method. This is an optional method.
@@ -197,7 +204,7 @@ I'm really not sure when this is called, it may have been a hold over from befor
Is this service active for the given user? The `user` parameter should be a Django User object. Is this service active for the given user? The `user` parameter should be a Django User object.
Usually you wont need to override this as it calls `service_enabled_members` or `service_enabled_blues` depending on the users state. Usually you won't need to override this as it calls `service_enabled_members` or `service_enabled_blues` depending on the user's state.
#### show_service_ctrl #### show_service_ctrl
@@ -205,92 +212,100 @@ Usually you wont need to override this as it calls `service_enabled_members` or
Should the service be shown for the given `user` with the given `state`? The `user` parameter should be a Django User object, and the `state` parameter should be a valid state from `authentication.states`. Should the service be shown for the given `user` with the given `state`? The `user` parameter should be a Django User object, and the `state` parameter should be a valid state from `authentication.states`.
Usually you wont need to override this function. Usually you won't need to override this function.
For more information see the [render_service_ctrl](#render-service-ctrl) section. For more information see the [render_service_ctrl](#render_service_ctrl) section.
#### render_service_ctrl #### render_service_ctrl
`def render_services_ctrl(self, request):` `def render_services_ctrl(self, request):`
Render the services control row. This will be called for all active services when a user visits the `/services/` page and [show_service_ctrl](#show-service-ctrl) returns `True` for the given user. Render the services control row. This will be called for all active services when a user visits the `/services/` page and [show_service_ctrl](#show_service_ctrl) returns `True` for the given user.
It should return a string (usually from `render_to_string`) of a table row (`<tr>`) with 4 columns (`<td>`). Column #1 is the service name, column #2 is the users username for this service, column #3 is the services URL, and column #4 is the action buttons. It should return a string (usually from `render_to_string`) of a table row (`<tr>`) with 4 columns (`<td>`). Column #1 is the service name, column #2 is the user's username for this service, column #3 is the services URL, and column #4 is the action buttons.
You may either define your own service template or use the default one provided. The default can be used like this example: You may either define your own service template or use the default one provided. The default can be used like this example:
def render_services_ctrl(self, request): ```python
""" def render_services_ctrl(self, request):
Example for rendering the service control panel row """
You can override the default template and create a Example for rendering the service control panel row
custom one if you wish. You can override the default template and create a
:param request: custom one if you wish.
:return: :param request:
""" :return:
urls = self.Urls() """
urls.auth_activate = 'auth_example_activate' urls = self.Urls()
urls.auth_deactivate = 'auth_example_deactivate' urls.auth_activate = 'auth_example_activate'
urls.auth_reset_password = 'auth_example_reset_password' urls.auth_deactivate = 'auth_example_deactivate'
urls.auth_set_password = 'auth_example_set_password' urls.auth_reset_password = 'auth_example_reset_password'
return render_to_string(self.service_ctrl_template, { urls.auth_set_password = 'auth_example_set_password'
'service_name': self.title, return render_to_string(self.service_ctrl_template, {
'urls': urls, 'service_name': self.title,
'service_url': self.service_url, 'urls': urls,
'username': 'example username' 'service_url': self.service_url,
}, request=request) 'username': 'example username'
}, request=request)
```
the `Urls` class defines the available URL names for the 4 actions available in the default template: the `Urls` class defines the available URL names for the four actions available in the default template:
- Activate (create service account) - Activate (create a service account)
- Deactivate (delete service account) - Deactivate (delete a service account)
- Reset Password (random password) - Reset Password (random password)
- Set Password (custom password) - Set Password (custom password)
If you don't define one or all of these variable the button for the undefined URLs will not be displayed. If you don't define one or all of these variables, the button for the undefined URLs will not be displayed.
Most services will survive with the default template. If, however, you require extra buttons for whatever reason, you are free to provide your own template as long as you stick within the 4 columns. Multiple rows should be OK, though may be confusing to users. Most services will survive with the default template. If, however, you require extra buttons for whatever reason, you are free to provide your own template as long as you stick within the 4 columns. Multiple rows should be OK, though it may be confusing to users.
### Menu Item Hook ### Menu Item Hook
If you services needs cannot be satisfied by the Service Control row, you are free to specify extra hooks by subclassing or instantiating the `services.hooks.MenuItemHook` class. If your service needs cannot be satisfied by the Service Control row, you are free to specify extra hooks by subclassing or instantiating the `services.hooks.MenuItemHook` class.
For more information see the [Menu Hooks](menu-hooks.md) page. For more information, see the [Menu Hooks](menu-hooks.md) page.
### The Service Manager ### The Service Manager
The service manager is what interacts with the external service. Ideally it should be completely agnostic about its environment, meaning that it should avoid calls to Alliance Auth and Django in general (except in special circumstances where the service is managed locally, e.g. Mumble). Data should come in already arranged by the Tasks and data passed back for the tasks to manage or distribute. The service manager is what interacts with the external service. Ideally, it should be completely agnostic about its environment, meaning that it should avoid calls to Alliance Auth and Django in general (except in special circumstances where the service is managed locally, e.g., Mumble). Data should come in already arranged by the Tasks and data passed back for the tasks to manage or distribute.
The reason for maintaining this separation is that managers may be reused from other sources and there may not even be a need to write a custom manager. Likewise, by maintaining this neutral environment others may reuse the managers that we write. It can also significantly ease the unit testing of services. The reason for maintaining this separation is that managers may be reused from other sources, and there may not even be a need to write a custom manager. Likewise, by maintaining this neutral environment, others may reuse the managers that we write. It can also significantly ease the unit testing of services.
### The Views ### The Views
As mentioned at the start of this page, service modules are fully fledged Django apps. This means you're free to do whatever you wish with your views. As mentioned at the start of this page, service modules are fully fledged Django apps. This means you're free to do whatever you wish with your views.
Typically most traditional username/password services define four views. Typically, most traditional username/password services define four views.
- Create Account - Create Account
- Delete Account - Delete Account
- Reset Password - Reset Password
- Set Password - Set Password
These views should interact with the service via the Tasks, though in some instances may bypass the Tasks and access the manager directly where necessary, for example OAuth functionality. These views should interact with the service via the Tasks, though in some instances may bypass the Tasks and access the manager directly where necessary, for example, OAuth functionality.
### The Tasks ### The Tasks
The tasks component is the glue that holds all of the other components of the service module together. It provides the function implementation to handle things like adding and deleting users, updating groups, validating the existence of a users account. Whatever tasks `auth_hooks` and `views` have with interacting with the service will probably live here. The tasks component is the glue that holds all the other components of the service module together. It provides the function implementation to handle things like adding and deleting users, updating groups, and validating the existence of a user's account. Whatever tasks `auth_hooks` and `views` have with interacting with the service will probably live here.
### The Models ### The Models
Its very likely that you'll need to store data about a users remote service account locally. As service modules are fully fledged Django apps you are free to create as many models as necessary for persistent storage. You can create foreign keys to other models in Alliance Auth if necessary, though I _strongly_ recommend you limit this to the User and Groups models from `django.contrib.auth.models` and query any other data manually. It's very likely that you'll need to store data about a users remote service account locally. As service modules are fully fledged Django apps, you are free to create as many models as necessary for persistent storage. You can create foreign keys to other models in Alliance Auth if necessary, though I _strongly_ recommend you limit this to the User and Groups models from `django.contrib.auth.models` and query any other data manually.
If you create models you should create the migrations that go along with these inside of your module/app. If you create models, you should create the migrations that go along with them inside your module/app.
## Examples ## Examples
There is a bare bones example service included in `services.modules.example`, you may like to use this as the base for your new service. There is a bare-bones example service included in `services.modules.example`, you may like to use this as the base for your new service.
You should have a look through some of the other service modules before you get started to get an idea of the general structure of one. A lot of them aren't perfect so don't feel like you have to rigidly follow the structure of the existing services if you think its sub-optimal or doesn't suit the external service you're integrating. You should have a look through some of the other service modules before you get started to get an idea of the general structure. A lot of them aren't perfect, so don't feel like you have to rigidly follow the structure of the existing services if you think its suboptimal or doesn't suit the external service you're integrating.
## Testing ## Testing
You will need to add unit tests for all aspects of your service module before it is accepted. Be mindful that you don't actually want to make external calls to the service so you should mock the appropriate components to prevent this behavior. You will need to add unit tests for all aspects of your service module before it is accepted. Be mindful that you don't actually want to make external calls to the service, so you should mock the appropriate components to prevent this behavior.
```{eval-rst}
.. autoclass:: allianceauth.services.hooks.ServicesHook
:members:
:undoc-members:
```

View File

@@ -1,7 +1,9 @@
# Logging from Custom Apps # Logging from Custom Apps
Alliance Auth provides a logger for use with custom apps to make everyone's life a little easier. Alliance Auth provides a logger for use with custom apps to make everyone's life a little easier.
## Using the Extensions Logger ## Using the Extensions Logger
AllianceAuth provides a helper function to get the logger for the current module to reduce the amount of AllianceAuth provides a helper function to get the logger for the current module to reduce the amount of
code you need to write. code you need to write.
@@ -15,19 +17,30 @@ This works by creating a child logger of the extension logger which propagates a
to the parent (extensions) logger. to the parent (extensions) logger.
## Changing the Logging Level ## Changing the Logging Level
By default, the extension logger's level is set to `DEBUG`. By default, the extension logger's level is set to `DEBUG`.
To change this, uncomment (or add) the following line in `local.py`. To change this, uncomment (or add) the following line in `local.py`.
```python ```python
LOGGING['handlers']['extension_file']['level'] = 'INFO' LOGGING['handlers']['extension_file']['level'] = 'INFO'
``` ```
*(Remember to restart your supervisor workers after changes to `local.py`)* *(Remember to restart your supervisor workers after changes to `local.py`)*
This will change the logger's level to the level you define. This will change the logger's level to the level you define.
Options are: *(all options accept entries of levels listed below them)* Options are: *(all options accept entries of levels listed below them)*
* `DEBUG` * `DEBUG`
* `INFO` * `INFO`
* `WARNING` * `WARNING`
* `ERROR` * `ERROR`
* `CRITICAL` * `CRITICAL`
## allianceauth.services.hooks.get_extension_logger
```{eval-rst}
.. automodule:: allianceauth.services.hooks.get_extension_logger
:members:
:undoc-members:
```

View File

@@ -1,13 +1,13 @@
# Menu Hooks # Menu Hooks
The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks. The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this, you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks.
To register a MenuItemHook class you would do the following: To register a MenuItemHook class, you would do the following:
```Python ```python
@hooks.register('menu_item_hook') @hooks.register('menu_item_hook')
def register_menu(): def register_menu():
return MenuItemHook('Example Item', 'glyphicon glyphicon-heart', 'example_url_name',150) return MenuItemHook('Example Item', 'fas fa-users fa-fw', 'example_url_name',150)
``` ```
The `MenuItemHook` class specifies some parameters/instance variables required for menu item display. The `MenuItemHook` class specifies some parameters/instance variables required for menu item display.
@@ -16,7 +16,7 @@ The `MenuItemHook` class specifies some parameters/instance variables required f
### text ### text
The text shown as menu item, e.g. usually the name of the app. The text shown as menu item, e.g., usually the name of the app.
### classes ### classes
@@ -28,7 +28,7 @@ The name of the Django URL to use
### order ### order
An integer which specifies the order of the menu item, lowest to highest. Community apps are free ot use an oder above `1000`. Numbers below are served for Auth. An integer which specifies the order of the menu item, lowest to highest. Community apps are free ot use an oder above `1000`. The numbers below are reserved for Auth.
### navactive ### navactive
@@ -38,18 +38,17 @@ A list of views or namespaces the link should be highlighted on. See [django-nav
`count` is an integer shown next to the menu item as badge when `count` is not `None`. `count` is an integer shown next to the menu item as badge when `count` is not `None`.
This is a great feature to signal the user, that he has some open issues to take care of within an app. For example Auth uses this feature to show the specific number of open group request to the current user. This is a great feature to signal the user that he has some open issues to take care of within an app. For example, Auth uses this feature to show the specific number of open group request to the current user.
```eval_rst :::{hint}
.. hint:: Here is how to stay consistent with the Auth design philosophy for using this feature:
Here is how to stay consistent with the Auth design philosophy for using this feature:
1. Use it to display open items that the current user can close by himself only. Do not use it for items, that the user has no control over.
2. If there are currently no open items, do not show a badge at all.
```
1. Use it to display open items that the current user can close by himself only. Do not use it for items that the user has no control over.
2. If there are currently no open items, do not show a badge at all.
:::
To use it set count the `render()` function of your subclass in accordance to the current user. Here is an example: To use it set count the `render()` function of your subclass in accordance to the current user. Here is an example:
```Python ```python
def render(self, request): def render(self, request):
# ... # ...
self.count = calculate_count_for_user(request.user) self.count = calculate_count_for_user(request.user)

View File

@@ -2,9 +2,9 @@
## Base functionality ## Base functionality
The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook. The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this, you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook.
To register a UrlHook class you would do the following: To register a UrlHook class, you would do the following:
```python ```python
@hooks.register('url_hook') @hooks.register('url_hook')
@@ -14,14 +14,13 @@ def register_urls():
### Public views ### Public views
In addition is it possible to make views public. Normally, all views are automatically decorated with the `main_character_required` decorator. That decorator ensures a user needs to be logged in and have a main before he can access that view. This feature protects against a community app sneaking in a public view without the administrator knowing about it. In addition, is it possible to make views public. Normally, all views are automatically decorated with the `main_character_required` decorator. That decorator ensures a user needs to be logged in and have a main before he can access that view. This feature protects against a community app sneaking in a public view without the administrator knowing about it.
An app can opt-out of this feature by adding a list of views to be excluded when registering the URLs. See the `excluded_views` parameter for details. An app can opt out of this feature by adding a list of views to be excluded when registering the URLs. See the `excluded_views` parameter for details.
```eval_rst :::{note}
.. note:: Note that for a public view to work, administrators need to also explicitly allow apps to have public views in their AA installation, by adding the app label to ``APPS_WITH_PUBLIC_VIEWS`` setting.
Note that for a public view to work, administrators need to also explicitly allow apps to have public views in their AA installation, by adding the apps label to ``APPS_WITH_PUBLIC_VIEWS`` setting. :::
```
## Examples ## Examples
@@ -43,7 +42,7 @@ urlpatterns = [
] ]
``` ```
Subsequently it would implement the UrlHook in a dedicated `auth_hooks.py` file like so: Subsequently, it would implement the UrlHook in a dedicated `auth_hooks.py` file like so:
```python ```python
from alliance_auth import hooks from alliance_auth import hooks
@@ -59,7 +58,7 @@ When this app is included in the project's `settings.INSTALLED_APPS` users would
## API ## API
```eval_rst ```{eval-rst}
.. autoclass:: allianceauth.services.hooks.UrlHook .. autoclass:: allianceauth.services.hooks.UrlHook
:members: :members:
``` ```

View File

@@ -1,21 +1,20 @@
# Development on Windows 10 with WSL and Visual Studio Code # Development on Windows 10 with WSL and Visual Studio Code
This document describes step-by-step how to setup a complete development environment for Alliance Auth apps on Windows 10 with Windows Subsystem for Linux (WSL) and Visual Studio Code. This document describes step-by-step how to set up a complete development environment for Alliance Auth apps on Windows 10 with Windows Subsystem for Linux (WSL) and Visual Studio Code.
The main benefit of this setup is that it runs all services and code in the native Linux environment (WSL) and at the same time can be full controlled from within a comfortable Windows IDE (Visual Studio Code) including code debugging. The main benefit of this setup is that it runs all services and code in the native Linux environment (WSL) and at the same time can be fully controlled from within a comfortable Windows IDE (Visual Studio Code) including code debugging.
In addition all tools described in this guide are open source or free software. In addition, all tools described in this guide are open source or free software.
```eval_rst :::{hint}
.. hint:: This guide is meant for development purposes only and not for installing AA in a production environment. For production installation, please see chapter **Installation**.
This guide is meant for development purposes only and not for installing AA in a production environment. For production installation please see chapter **Installation**. :::
```
## Overview ## Overview
The development environment consists of the following components: The development environment consists of the following components:
- Visual Studio Code with Remote WSL and Python extension - Visual Studio Code with the Remote WSL and Python extension
- WSL with Ubuntu (18.04. LTS or higher) - WSL with Ubuntu (18.04. LTS or higher)
- Python environment on WSL (3.8 or higher) - Python environment on WSL (3.8 or higher)
- MySQL server on WSL - MySQL server on WSL
@@ -23,86 +22,77 @@ The development environment consists of the following components:
- Alliance Auth on WSL - Alliance Auth on WSL
- Celery on WSL - Celery on WSL
We will use the build-in Django development web server, so we don't need to setup a WSGI server or a web server. We will use the build-in Django development web server, so we don't need to set up a WSGI server or a web server.
```eval_rst :::{note}
.. note:: This setup works with both WSL 1 and WSL 2. However, due to the significantly better performance, we recommend WSL 2.
This setup works with both WSL 1 and WSL 2. However, due to the significantly better performance we recommend WSL 2. :::
```
## Requirement ## Requirement
The only requirement is a PC with Windows 10 and Internet connection in order to download the additional software components. The only requirement is a PC with Windows 10 and Internet connection to download the additional software components.
## Installing Windows apps ## Installing Windows apps
### Windows Subsystem for Linux ### Windows Subsystem for Linux
- Install from here: [Microsoft docs](https://docs.microsoft.com/en-us/windows/wsl/install-win10) - Install from here: [Microsoft docs](https://docs.microsoft.com/en-us/windows/wsl/install-win10)
- Choose Ubuntu 18.04. LTS or higher - Choose Ubuntu 18.04. LTS or higher
### Visual Studio Code ### Visual Studio Code
- Install from here: [VSC Download](https://code.visualstudio.com/Download) - Install from here: [VSC Download](https://code.visualstudio.com/Download)
- Open the app and install the following VSC extensions: - Open the app and install the following VSC extensions:
- Remote WSL - Remote WSL
- Connect to WSL. This will automatically install the VSC server on the VSC server for WSL - Connect to WSL. This will automatically install the VSC server on the VSC server for WSL
- Once connected to WSL, install the Python extension on the WSL side
- Once connected to WSL install the Python extension on the WSL side
## Setting up WSL / Linux ## Setting up WSL / Linux
Open a WSL bash and update all software packets: Open a WSL bash and update all software packets:
```bash ```shell
sudo apt update && sudo apt upgrade -y sudo apt update && sudo apt upgrade -y
``` ```
### Install Tools ### Install Tools
```bash ```shell
sudo apt-get install build-essential sudo apt-get install build-essential
sudo apt-get install gettext sudo apt-get install gettext
``` ```
### Install Python ### Install Python
Next we need to install Python and related development tools. Next, we need to install Python and related development tools.
```eval_rst :::{note}
.. hint:: Should your Ubuntu come with a newer version of Python we recommend to still set up your dev environment with the oldest Python 3 version currently supported by AA (e.g., Python 3.8 at this time of writing) to ensure your apps are compatible with all current AA installations
To check your system's Python 3 version you can enter: ``python3 --version`` You can check out this `page <https://askubuntu.com/questions/682869/how-do-i-install-a-different-python-version-using-apt-get/1195153>`_ on how to install additional Python versions on Ubuntu.
If you install a different python version from the default, you need to adjust some commands below to install appopriate versions of those packages, for example, using Python 3.8 you might need to run the following after using the setup steps for the repository mentioned in the AskUbuntu post above:
```shell
sudo apt-get install python3.8 python3.8-dev python3.8-venv python3-setuptools python3-pip python-pip
``` ```
```eval_rst :::
.. note::
Should your Ubuntu come with a newer version of Python we recommend to still setup your dev environment with the oldest Python 3 version currently supported by AA (e.g Python 3.8 at this time of writing) to ensure your apps are compatible with all current AA installations
You an check out this `page <https://askubuntu.com/questions/682869/how-do-i-install-a-different-python-version-using-apt-get/1195153>`_ on how to install additional Python versions on Ubuntu.
If you install a different python version from the default you need to adjust some of the commands below to install appopriate versions of those packages for example using Python 3.8 you might need to run the following after using the setup steps for the repository mentioned in the AskUbuntu post above:
`sudo apt-get install python3.8 python3.8-dev python3.8-venv python3-setuptools python3-pip python-pip`
```
Use the following command to install Python 3 with all required libraries with the default version: Use the following command to install Python 3 with all required libraries with the default version:
```bash ```shell
sudo apt-get install python3 python3-dev python3-venv python3-setuptools python3-pip python-pip sudo apt-get install python3 python3-dev python3-venv python3-setuptools python3-pip python-pip
``` ```
### Install redis and other tools ### Install redis and other tools
```bash ```shell
sudo apt-get install unzip git redis-server curl libssl-dev libbz2-dev libffi-dev pkg-config sudo apt-get install unzip git redis-server curl libssl-dev libbz2-dev libffi-dev pkg-config
``` ```
Start redis Start redis
```bash ```shell
sudo redis-server --daemonize yes sudo redis-server --daemonize yes
``` ```
@@ -110,30 +100,29 @@ sudo redis-server --daemonize yes
Install MySQL and required libraries with the following command: Install MySQL and required libraries with the following command:
```bash ```shell
sudo apt-get install mysql-server mysql-client libmysqlclient-dev sudo apt-get install mysql-server mysql-client libmysqlclient-dev
``` ```
```eval_rst :::{note}
.. note:: We chose to use MySQL instead of MariaDB, because the standard version of MariaDB that comes with this Ubuntu distribution will not work with AA.
We chose to use MySQL instead of MariaDB, because the standard version of MariaDB that comes with this Ubuntu distribution will not work with AA. :::
```
We need to apply a permission fix to mysql or you will get a warning with every startup: We need to apply a permission fix to mysql, or you will get a warning with every startup:
```bash ```shell
sudo usermod -d /var/lib/mysql/ mysql sudo usermod -d /var/lib/mysql/ mysql
``` ```
Start the mysql server Start the mysql server
```bash ```shell
sudo service mysql start sudo service mysql start
``` ```
Create database and user for AA Create a database and user for AA
```bash ```shell
sudo mysql -u root sudo mysql -u root
``` ```
@@ -147,26 +136,25 @@ exit;
Add timezone info to mysql: Add timezone info to mysql:
```bash ```shell
sudo mysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql -u root mysql sudo mysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql -u root mysql
``` ```
```eval_rst :::{note}
.. note:: If your WSL does not have an init.d service, it will not automatically start your services such as MySQL and Redis when you boot your Windows machine, and you have to manually start them. For convenience, we recommend putting these commands in a bash script. Here is an example:
If your WSL does not have an init.d service, it will not automatically start your services such as MySQL and Redis when you boot your Windows machine and you have to manually start them. For convenience we recommend putting these commands in a bash script. Here is an example:
::
#/bin/bash
# start services for AA dev
sudo service mysql start
sudo redis-server --daemonize yes
```shell
#/bin/bash
# start services for AA dev
sudo service mysql start
sudo redis-server --daemonize yes
``` ```
:::
### Setup dev folder on WSL ### Setup dev folder on WSL
Setup your folders on WSL bash for your dev project. Our approach will setup one AA project with one venv and multiple apps running under the same AA project, but each in their own folder and git. Set up your folders on WSL bash for your dev project. Our approach will set up one AA project with one venv and multiple apps running under the same AA project, but each in their own folder and git.
A good location for setting up this folder structure is your home folder or a subfolder of your home: A good location for setting up this folder structure is your home folder or a subfolder of your home:
@@ -179,32 +167,31 @@ A good location for setting up this folder structure is your home folder or a su
|- ... |- ...
``` ```
Following this approach you can also setup additional AA projects, e.g. aa-dev-2, aa-dev-3 if needed. Following this approach, you can also set up additional AA projects, e.g. aa-dev-2, aa-dev-3 if needed.
Create the root folder `aa-dev`. Create the root folder `aa-dev`.
```eval_rst :::{hint}
.. hint:: The folders `venv` and `myauth` will be created automatically in later steps. Please do not create them manually as this would lead to errors.
The folders `venv` and `myauth` will be created automatically in later steps. Please do not create them manually as this would lead to errors. :::
```
### Setup virtual Python environment for aa-dev ### Setup virtual Python environment for aa-dev
Create the virtual environment. Run this in your aa-dev folder: Create the virtual environment. Run this in your aa-dev folder:
```bash ```shell
python3 -m venv venv python3 -m venv venv
``` ```
And activate your venv: And activate your venv:
```bash ```shell
source venv/bin/activate source venv/bin/activate
``` ```
### Install and update basic Python packages ### Install and update basic Python packages
```bash ```shell
pip install -U pip setuptools wheel pip install -U pip setuptools wheel
``` ```
@@ -212,29 +199,29 @@ pip install -U pip setuptools wheel
### Install and create AA instance ### Install and create AA instance
```bash ```shell
pip install allianceauth pip install allianceauth
``` ```
Now we are ready to setup our AA instance. Make sure to run this command in your aa-dev folder: Now we are ready to set up our AA instance. Make sure to run this command in your aa-dev folder:
```bash ```shell
allianceauth start myauth allianceauth start myauth
``` ```
Next we will setup our VSC project for aa-dev by starting it directly from the WSL bash: Next, we will set up our VSC project for aa-dev by starting it directly from the WSL bash:
```bash ```shell
code . code .
``` ```
First you want to make sure exclude the venv folder from VSC as follows: First you want to make sure exclude the venv folder from VSC as follows:
Open settings and go to Files:Exclude Open settings and go to Files:Exclude
Add pattern: `**/venv` Add the pattern: `**/venv`
### Create EVE Online SSO App ### Create EVE Online SSO App
For the Eve Online related setup you need to create a SSO app on the developer site: For the Eve Online related setup you need to create an SSO app on the developer site:
- Create your Eve Online SSO App on the [Eve Online developer site](https://developers.eveonline.com/) - Create your Eve Online SSO App on the [Eve Online developer site](https://developers.eveonline.com/)
- Add all ESI scopes - Add all ESI scopes
@@ -244,10 +231,9 @@ For the Eve Online related setup you need to create a SSO app on the developer s
Open your local Django settings with VSC. The file is under `myauth/myauth/settings/local.py` Open your local Django settings with VSC. The file is under `myauth/myauth/settings/local.py`
```eval_rst :::{hint}
.. hint:: There are two Django settings files: ``base.py`` and ``local.py``. The base settings file is controlled by the AA project and may change at any time. It is therefore recommended to only change the local settings file.
There are two Django settings files: ``base.py`` and ``local.py``. The base settings file is controlled by the AA project and may change at any time. It is therefore recommended to only change the local settings file. :::
```
```python ```python
DEBUG = True DEBUG = True
@@ -291,16 +277,16 @@ REGISTRATION_VERIFY_EMAIL = False
### Migrations and superuser ### Migrations and superuser
Before we can start AA we need to run migrations: Before we can start AA, we need to run migrations:
```bash ```shell
cd myauth cd myauth
python manage.py migrate python manage.py migrate
``` ```
We also need to create a superuser for our AA installation: We also need to create a superuser for our AA installation:
```bash ```shell
python manage.py createsuperuser python manage.py createsuperuser
``` ```
@@ -310,50 +296,48 @@ python manage.py createsuperuser
We are now ready to run out AA instance with the following command: We are now ready to run out AA instance with the following command:
```bash ```shell
python manage.py runserver python manage.py runserver
``` ```
Once running you can access your auth site on the browser under `http://localhost:8000`. Or the admin site under `http://localhost:8000/admin` Once running, you can access your auth site on the browser under `http://localhost:8000`. Or the admin site under `http://localhost:8000/admin`
```eval_rst :::{hint}
.. hint:: You can start your AA server directly from a terminal window in VSC or with a VSC debug config (see chapter about debugging for details).
You can start your AA server directly from a terminal window in VSC or with a VSC debug config (see chapter about debugging for details). :::
```
```eval_rst :::{note}
.. note:: **Debug vs. Non-Debug mode**
**Debug vs. Non-Debug mode** Usually it is best to run your dev AA instance in debug mode, so you get all the detailed error messages that help a lot for finding errors. But there might be cases where you want to test features that do not exist in debug mode (e.g. error pages) or just want to see how your app behaves in non-debug / production mode.
Usually it is best to run your dev AA instance in debug mode, so you get all the detailed error messages that helps a lot for finding errors. But there might be cases where you want to test features that do not exist in debug mode (e.g. error pages) or just want to see how your app behaves in non-debug / production mode.
When you turn off debug mode you will see a problem though: Your pages will not render correctly. The reason is that Django will stop serving your static files in production mode and expect you to serve them from a real web server. Luckily, there is an option that forces Django to continue serving your static files directly even when not in debug mode. Just start your server with the following option: ``python manage.py runserver --insecure`` When you turn off debug mode, you will see a problem though: Your pages will not render correctly. The reason is that Django will stop serving your static files in production mode and expect you to serve them from a real web server. Luckily, there is an option that forces Django to continue serving your static files directly even when not in debug mode. Start your server with the following option: ``python manage.py runserver --insecure``
``` :::
### Celery ### Celery
In addition you can start a celery worker instance for myauth. For development purposed it makes sense to only start one instance and add some additional logging. In addition, you can start a celery worker instance for myauth. For development purposes, it makes sense to only start one instance and add some additional logging.
This can be done from the command line with the following command in the myauth folder (where manage.py is located): This can be done from the command line with the following command in the myauth folder (where manage.py is located):
```bash ```shell
celery -A myauth worker -l info -P solo celery -A myauth worker -l info -P solo
``` ```
Same as AA itself you can start Celery from any terminal session, from a terminal window within VSC or as a debug config in VSC (see chapter about debugging for details). For convenience we recommend starting Celery as debug config. Same as AA itself, you can start Celery from any terminal session, from a terminal window within VSC or as a debug config in VSC (see chapter about debugging for details). For convenience, we recommend starting Celery as debug config.
## Debugging setup ## Debugging setup
To be able to debug your code you need to add debugging configuration to VSC. At least one for AA and one for celery. To be able to debug your code, you need to add a debugging configuration to VSC. At least one for AA and one for celery.
### Breakpoints ### Breakpoints
By default VSC will break on any uncaught exception. Since every error raised by your tests will cause an uncaught exception we recommend to deactivate this feature. By default, VSC will break on any uncaught exception. Since every error raised by your tests will cause an uncaught exception, we recommend deactivating this feature.
To deactivate open click on the debug icon to switch to the debug view. Then un-check "Uncaught Exceptions" on the breakpoints window. To deactivate, click on the debug icon to switch to the debug view. Then uncheck "Uncaught Exceptions" in the "Breakpoints" window.
### AA debug config ### AA debug config
In VSC click on Debug / Add Configuration and choose "Django". Should Django not appear as option make sure to first open a Django file (e.g. the local.py settings) to help VSC detect that you are using Django. In VSC, click on Debug / Add Configuration and choose "Django". Should Django not appear as an option, make sure to first open a Django file (e.g., the local.py settings) to help VSC detect that you are using Django.
The result should look something like this: The result should look something like this:
@@ -375,7 +359,7 @@ The result should look something like this:
### Debug celery ### Debug celery
For celery we need another debug config, so that we can run it in parallel to our AA instance. For celery, we need another debug config, so that we can run it in parallel to our AA instance.
Here is an example debug config for Celery: Here is an example debug config for Celery:
@@ -403,7 +387,7 @@ Here is an example debug config for Celery:
### Debug config for unit tests ### Debug config for unit tests
Finally it makes sense to have a dedicated debug config for running unit tests. Here is an example config for running all tests of the app `example`. Finally, it makes sense to have a dedicated debug config for running unit tests. Here is an example config for running all tests of the app `example`.
```json ```json
{ {
@@ -423,11 +407,11 @@ Finally it makes sense to have a dedicated debug config for running unit tests.
}, },
``` ```
You can also specify to run just a part of your test suite down to a test method. Just give the full path to the test you want to run, e.g. `example.test.test_models.TestDemoModel.test_this_method` You can also specify to run just a part of your test suite down to a test method. Give the full path to the test you want to run, e.g. `example.test.test_models.TestDemoModel.test_this_method`
### Debugging normal python scripts ### Debugging normal python scripts
Finally you may also want to have a debug config to debug a non-Django Python script: Finally, you may also want to have a debug config to debug a non-Django Python script:
```json ```json
{ {
@@ -463,43 +447,41 @@ Extension for Visual Studio Code - Markdown linting and style checking for Visua
#### Live Server #### Live Server
Live Server allows you to start a mini webserver for any file quickly. This can e.g. be useful for looking at changes to to Sphinx docs.: [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) Live Server allows you to start a mini webserver for any file quickly. This can e.g. be useful for looking at changes to Sphinx docs.: [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)
### Django apps ### Django apps
#### Django Extensions #### Django Extensions
[django-extensions](https://django-extensions.readthedocs.io/en/latest/) is a swiss army knife for django developers with adds a lot of very useful features to your Django site. Here are a few highlights: [django-extensions](https://django-extensions.readthedocs.io/en/latest/) is a swiss army knife for django developers with adds a lot of useful features to your Django site. Here are a few highlights:
- shell_plus - An enhanced version of the Django shell. It will auto-load all your models at startup so you don't have to import anything and can use them right away. - `shell_plus` - An enhanced version of the Django shell. It will autoload all your models at startup, so you don't have to import anything and can use them right away.
- graph_models - Creates a dependency graph of Django models. Visualizing a model dependency structure can be very useful for trying to understand how an existing Django app works, or e.g. how all the AA models work together. - `graph_models` - Creates a dependency graph of Django models. Visualizing a model dependency structure can be useful for trying to understand how an existing Django app works, or e.g., how all the AA models work together.
- runserver_plus - The standard runserver stuff but with the Werkzeug debugger baked in. This is a must have for any serious debugging. - `runserver_plus` - The standard runserver stuff but with the debugger baked in. This is a must-have for any serious debugging.
#### Django Debug Toolbar #### Django Debug Toolbar
The [Django Debug Toolbar](https://github.com/jazzband/django-debug-toolbar) is a configurable set of panels that display various debug information about the current request/response and when clicked, display more details about the panel's content. The [Django Debug Toolbar](https://github.com/jazzband/django-debug-toolbar) is a configurable set of panels that display various debug information about the current request/response and when clicked, display more details about the panel's content. This tool is invaluable to debug and fix performance issues with Django queries.
E.g. this tool is invaluable to debug and fix performance issues with Django queries.
### Windows applications ### Windows applications
#### DBeaver #### DBeaver
DBeaver is a free universal database tool and works with many different kinds of databases include MySQL. It can be installed on Windows 10 and will be able to help manage your MySQL databases running on WSL. DBeaver is a free universal database tool and works with many different kinds of databases including MySQL. It can be installed on Windows 10 and will be able to help manage your MySQL databases running on WSL.
Install from here. [DBeaver](https://dbeaver.io/) Install from here. [DBeaver](https://dbeaver.io/)
## Adding apps for development ## Adding apps for development
The idea behind the particular folder structure of aa-dev is to have each and every app in its own folder and git repo. To integrate them with the AA instance they need to be installed once using the -e option that enabled editing of the package. And then added to the INSTALLED_APPS settings. The idea behind the particular folder structure of aa-dev is to have each and every app in its own folder and git repo. To integrate them with the AA instance, they need to be installed once using the -e option that enabled editing of the package. And then added to the INSTALLED_APPS settings.
To demonstrate let's add the example plugin to our environment. To demonstrate, let's add the example plugin to our environment.
Open a WSL bash and navigate to the aa-dev folder. Make sure you have activate your virtual environment. (`source venv/bin/activate`) Open a WSL bash and navigate to the aa-dev folder. Make sure you have activated your virtual environment. (`source venv/bin/activate`)
Run these commands: Run these commands:
```bash ```shell
git clone https://gitlab.com/ErikKalkoken/allianceauth-example-plugin.git git clone https://gitlab.com/ErikKalkoken/allianceauth-example-plugin.git
pip install -e allianceauth-example-plugin pip install -e allianceauth-example-plugin
``` ```
@@ -508,7 +490,7 @@ Add `'example'` to INSTALLED_APPS in your `local.py` settings.
Run migrations and restart your AA server, e.g.: Run migrations and restart your AA server, e.g.:
```bash ```shell
cd myauth cd myauth
python manage.py migrate python manage.py migrate
``` ```

View File

@@ -2,9 +2,8 @@
Here you find guides on how to setup your development environment for AA. Here you find guides on how to setup your development environment for AA.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
aa-dev-setup-wsl-vsc-v2 aa-dev-setup-wsl-vsc-v2
``` :::

View File

@@ -2,12 +2,11 @@
**Alliance Auth** is designed to be extended easily. Learn how to develop your own apps and services for AA or to develop for AA core in the development chapter. **Alliance Auth** is designed to be extended easily. Learn how to develop your own apps and services for AA or to develop for AA core in the development chapter.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
custom/index custom/index
aa_core/index aa_core/index
dev_setup/index dev_setup/index
tech_docu/index tech_docu/index
``` :::

View File

@@ -1,17 +1,16 @@
# API # API
To reduce redundancy and help speed up development we encourage developers to utilize the following packages when developing apps for Alliance Auth. To reduce redundancy and help speed up development, we encourage developers to utilize the following packages when developing apps for Alliance Auth.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
discord_client discord_client
discord_service discord_service
esi esi
evelinks evelinks
eveonline eveonline
notifications notifications
testutils testutils
utils utils
``` :::

View File

@@ -2,32 +2,31 @@
**Alliance Auth** uses Celery for asynchronous task management. This page aims to give developers some guidance on how to use Celery when developing apps for Alliance Auth. **Alliance Auth** uses Celery for asynchronous task management. This page aims to give developers some guidance on how to use Celery when developing apps for Alliance Auth.
For a complete documentation of Celery please refer to the [official Celery documentation](http://docs.celeryproject.org/en/latest/index.html). For the complete documentation of Celery, please refer to the [official Celery documentation](http://docs.celeryproject.org/en/latest/index.html).
## When should I use Celery in my app? ## When should I use Celery in my app?
There are two main cases for using celery. Long duration of a process and recurrence of a process. There are two main reasons for using celery. Long duration of a process, and recurrence of a process.
### Duration ### Duration
Alliance Auth is an online web application and as such the user expects fast and immediate responses to any of his clicks or actions. Same as with any other good web site. Good response times are measures in ms and a user will perceive everything that takes longer than 1 sec as an interruption of his flow of thought (see also [Response Times: The 3 Important Limits](https://www.nngroup.com/articles/response-times-3-important-limits/)). Alliance Auth is an online web application, and as such, the user expects fast and immediate responses to any of his clicks or actions. Same as with any other good website. Good response times are measured in ms, and a user will perceive everything that takes longer than 1 sec as an interruption of his flow of thought (see also [Response Times: The 3 Important Limits](https://www.nngroup.com/articles/response-times-3-important-limits/)).
As a rule of thumb we therefore recommend to use celery tasks for every process that can take longer than 1 sec to complete (also think about how long your process might take with large amounts of data). As a rule of thumb, we therefore recommend using celery tasks for every process that can take longer than 1 sec to complete (also think about how long your process might take with large amounts of data).
```eval_rst :::{note}
.. note:: Another solution for dealing with long response time in particular when loading pages is to load parts of a page asynchronously, for example, with AJAX.
Another solution for dealing with long response time in particular when loading pages is to load parts of a page asynchronously, for example with AJAX. :::
```
### Recurrence ### Recurrence
Another case for using celery tasks is when you need recurring execution of tasks. For example you may want to update the list of characters in a corporation from ESI every hour. Another case for using celery tasks is when you need recurring execution of tasks. For example, you may want to update the list of characters in a corporation from ESI every hour.
These are called periodic tasks and Alliance Auth uses [celery beat](https://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html) to implement them. These are called periodic tasks, and Alliance Auth uses [celery beat](https://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html) to implement them.
## What is a celery task? ## What is a celery task?
For the most part a celery task is a Python functions that is configured to be executed asynchronously and controlled by Celery. Celery tasks can be automatically retried, executed periodically, executed in work flows and much more. See the [celery docs](https://docs.celeryproject.org/en/latest/userguide/tasks.html) for a more detailed description. For the most part, a celery task is a Python function configured to be executed asynchronously and controlled by Celery. Celery tasks can be automatically retried, executed periodically, executed in work flows and much more. See the [celery docs](https://docs.celeryproject.org/en/latest/userguide/tasks.html) for a more detailed description.
## How should I use Celery in my app? ## How should I use Celery in my app?
@@ -40,7 +39,7 @@ Please use the following approach to ensure your tasks are working properly with
Here is an example implementation of a task: Here is an example implementation of a task:
```Python ```python
import logging import logging
from celery import shared_task from celery import shared_task
@@ -54,7 +53,7 @@ def example():
This task can then be started from any another Python module like so: This task can then be started from any another Python module like so:
```Python ```python
from .tasks import example from .tasks import example
example.delay() example.delay()
@@ -62,25 +61,25 @@ example.delay()
## How should I use celery tasks in the UI? ## How should I use celery tasks in the UI?
There is a well established pattern for integrating asynchronous processes in the UI, for example when the user asks your app to perform a longer running action: There is a well-established pattern for integrating asynchronous processes in the UI, for example, when the user asks your app to perform a longer running action:
1. Notify the user immediately (with a Django message) that the process for completing the action has been started and that he will receive a report once completed. 1. Notify the user immediately (with a Django message) that the process for completing the action has been started and that he will receive a report once completed.
2. Start the celery task 2. Start the celery task
3. Once the celery task is completed it should send a notification containing the result of the action to the user. It's important to send that notification also in case of errors. 3. Once the celery task is completed, it should send a notification containing the result of the action to the user. It's important to send that notification also in case of errors.
## Can I use long running tasks? ## Can I use long-running tasks?
Long running tasks are possible, but in general Celery works best with short running tasks. Therefore we strongly recommend to try and break down long running tasks into smaller tasks if possible. Long-running tasks are possible, but in general Celery works best with short running tasks. Therefore, we strongly recommend trying to break down long-running tasks into smaller tasks if possible.
If contextually possible try to break down your long running task in shorter tasks that can run in parallel. If contextually possible, try to break down your long-running task in shorter tasks that can run in parallel.
However, many long running tasks consist of several smaller processes that need to run one after the other. For example you may have a loop where you perform the same action on hundreds of objects. In those cases you can define each of the smaller processes as it's own task and then link them together, so that they are run one after the other. That is called chaining in Celery and is the preferred approach for implementing long running processes. However, many long-running tasks consist of several smaller processes that need to run one after the other. For example, you may have a loop where you perform the same action on hundreds of objects. In those cases, you can define each of the smaller processes as its own task and then link them together, so that they are run one after the other. That is called chaining in Celery and is the preferred approach for implementing long-running processes.
Example implementation for a celery chain: Example implementation for a celery chain:
```Python ```python
import logging import logging
from celery import shared_task, chain from celery import shared_task, chain
@@ -102,42 +101,41 @@ def long_runner():
chain(my_tasks).delay() chain(my_tasks).delay()
``` ```
In this example we fist add 10 example tasks that need to run one after the other to a list. This can be done by creating a so called signature for a task. Those signature are a kind of wrapper for tasks and can be used in various ways to compose work flow for tasks. In this example, we first add 10 example tasks that need to run one after the other to a list. This can be done by creating a so-called signature for a task. Those signatures are a kind of wrapper for tasks and can be used in various ways to compose work flow for tasks.
The list of task signatures is then converted to a chain and started asynchronously. The list of task signatures is then converted to a chain and started asynchronously.
```eval_rst :::{hint}
.. hint:: In our example we use ``si()``, which is a shortcut for "immutable signatures" and prevents us from having to deal with result sharing between tasks.
In our example we use ``si()``, which is a shortcut for "immutable signatures" and prevents us from having to deal with result sharing between tasks.
For more information on signature and work flows see the official documentation on `Canvas <https://docs.celeryproject.org/en/latest/userguide/canvas.html>`_. For more information on signature and work flows see the official documentation on `Canvas <https://docs.celeryproject.org/en/latest/userguide/canvas.html>`_.
In this context please note that Alliance Auth currently only supports chaining, because all other variants require a so called results back, which Alliance Auth does not have. In this context, please note that Alliance Auth currently only supports chaining because all other variants require a so-called results back, which Alliance Auth does not have.
``` :::
## How can I define periodic tasks for my app? ## How can I define periodic tasks for my app?
Periodic tasks are normal celery tasks which are added the scheduler for periodic execution. The convention for defining periodic tasks for an app is to define them in the local settings. So user will need to add those settings manually to his local settings during the installation process. Periodic tasks are normal celery tasks that are added to the scheduler for periodic execution. The convention for defining periodic tasks for an app is to define them in the local settings. So user will need to add those settings manually to his local settings during the installation process.
Example setting: Example setting:
```Python ```python
CELERYBEAT_SCHEDULE['structures_update_all_structures'] = { CELERYBEAT_SCHEDULE['structures_update_all_structures'] = {
'task': 'structures.tasks.update_all_structures', 'task': 'structures.tasks.update_all_structures',
'schedule': crontab(minute='*/30'), 'schedule': crontab(minute='*/30'),
} }
``` ```
- `structures_update_all_structures` is the name of the scheduling entry. You can chose any name, but the convention is name of your app plus name of the task. - `structures_update_all_structures` is the name of the scheduling entry. You can choose any name, but the convention is name of your app plus name of the task.
- `'task'`: Name of your task (full path) - `'task'`: Name of your task (full path)
- `'schedule'`: Schedule definition (see Celery documentation on [Periodic Tasks](https://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html) for details) - `'schedule'`: Schedule definition (see Celery documentation on [Periodic Tasks](https://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html) for details)
## How can I use priorities for tasks? ## How can I use priorities for tasks?
In Alliance Auth we have defined task priorities from 0 - 9 as follows: In Alliance Auth we have defined task priorities from 0 to 9 as follows:
```eval_rst ```{eval-rst}
====== ========= =========== ====== ========= ===========
Number Priority Description Number Priority Description
====== ========= =========== ====== ========= ===========
@@ -150,30 +148,25 @@ In Alliance Auth we have defined task priorities from 0 - 9 as follows:
====== ========= =========== ====== ========= ===========
``` ```
```eval_rst :::{warning}
.. warning:: Please make sure to use task priorities with care and especially do not use higher priorities without a good reason. All apps including Alliance Auth share the same task queues, so using higher task priorities excessively can potentially prevent more important tasks (of other apps) from completing on time.
Please make sure to use task priorities with care and especially do not use higher priorities without a good reason. All apps including Alliance Auth share the same task queues, so using higher task priorities excessively can potentially prevent more important tasks (of other apps) from completing on time.
You also want to make sure to run use lower priorities if you have a large amount of tasks or long running tasks, which are not super urgent. (e.g. the regular update of all Eve characters from ESI runs with priority 7) You also want to make sure to run use lower priorities if you have a large number of tasks or long-running tasks, which are not super urgent. (e.g., the regular update of all Eve characters from ESI runs with priority 7)
``` :::
:::{hint}
```eval_rst If no priority is specified, all tasks will be started with the default priority, which is 5.
.. hint:: :::
If no priority is specified all tasks will be started with the default priority, which is 5. To run a task with a different priority, you need to specify it when starting it.
```
To run a task with a different priority you need to specify it when starting it.
Example for starting a task with priority 3: Example for starting a task with priority 3:
```Python ```python
example.apply_async(priority=3) example.apply_async(priority=3)
``` ```
```eval_rst :::{hint}
.. hint:: For defining a priority to tasks, you cannot use the convenient shortcut ``delay()``, but instead need to start a task with ``apply_async()``, which also requires you to pass parameters to your task function differently. Please check out the `official docs <https://docs.celeryproject.org/en/stable/reference/celery.app.task.html#celery.app.task.Task.apply_async>`_ for details.
For defining a priority to tasks you can not use the convenient shortcut ``delay()``, but instead need to start a task with ``apply_async()``, which also requires you to pass parameters to your task function differently. Please check out the `official docs <https://docs.celeryproject.org/en/stable/reference/celery.app.task.html#celery.app.task.Task.apply_async>`_ for details. :::
```
## What special features should I be aware of? ## What special features should I be aware of?
@@ -181,24 +174,24 @@ Every Alliance Auth installation will come with a couple of special celery relat
### celery-once ### celery-once
Celery-once is a celery extension "that allows you to prevent multiple execution and queuing of celery tasks". What that means is that you can ensure that only one instance of a celery task runs at any given time. This can be useful for example if you do not want multiple instances of you task to talk to the same external service at the same time. Celery-once is a celery extension "that allows you to prevent multiple execution and queuing of celery tasks". What that means is that you can ensure that only one instance of a celery task runs at any given time. This can be useful, for example, if you do not want multiple instances of your task to talk to the same external service at the same time.
We use a custom backend for celery_once in Alliance Auth defined [here](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/services/tasks.py#L14) We use a custom backend for celery_once in Alliance Auth defined [here](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/services/tasks.py#L14)
You can import it for use like so: You can import it for use like so:
```Python ```python
from allianceauth.services.tasks import QueueOnce from allianceauth.services.tasks import QueueOnce
``` ```
An example of AllianceAuth's use within the `@sharedtask` decorator, can be seen [here](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/services/modules/discord/tasks.py#L62) in the discord module An example of Alliance Auth's use within the `@sharedtask` decorator, can be seen [here](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/services/modules/discord/tasks.py#L62) in the discord module
You can use it like so: You can use it like so:
```Python ```python
@shared_task(bind=True, name='your_modules.update_task', base=QueueOnce) @shared_task(bind=True, name='your_modules.update_task', base=QueueOnce)
``` ```
Please see the [official documentation](hhttps://pypi.org/project/celery_once/) of celery-once for details. Please see the [official documentation](https://pypi.org/project/celery_once/) of celery-once for details.
### task priorities ### task priorities
Alliance Auth is using task priorities to enable priority based scheduling of task execution. Please see [How can I use priorities for tasks?](#how-can-i-use-priorities-for-tasks) for details. Alliance Auth is using task priorities to enable priority-based scheduling of task execution. Please see [How can I use priorities for tasks?](#how-can-i-use-priorities-for-tasks) for details.

View File

@@ -1,13 +1,12 @@
# Developing apps # Developing apps
In this section you find topics useful for app developers. In this section, you find topics useful for app developers.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
api/index api/index
celery celery
core_models core_models
templatetags templatetags
``` :::

View File

@@ -1,11 +1,6 @@
# Auto Groups # Auto Groups
```eval_rst Auto Groups allows you to automatically place users of certain states into corp or alliance-based groups. These groups are created when the first user is added to them and removed when the configuration is deleted.
.. note::
New in 2.0
```
Auto groups allows you to automatically place users of certain states into Corp or Alliance based groups. These groups are created when the first user is added to them and removed when the configuration is deleted.
## Installation ## Installation
@@ -15,30 +10,25 @@ To install this app add `'allianceauth.eveonline.autogroups',` to your `INSTALLE
## Configuring a group ## Configuring a group
When you create an autogroup config you will be given the following options: When you create an autogroup config, you will be given the following options:
![Create Autogroup page](/_static/images/features/apps/autogroups/group-creation.png) ![Create Autogroup page](/_static/images/features/apps/autogroups/group-creation.png)
```eval_rst :::{warning}
.. warning:: After creating a group, you won't be able to change the Corp and Alliance group prefixes, name source, and the replace spaces settings. Make sure you configure these the way you want before creating the config. If you need to change these, you will have to create a new autogroup config.
After creating a group you wont be able to change the Corp and Alliance group prefixes, name source and the replace spaces settings. Make sure you configure these the way you want before creating the config. If you need to change these you will have to create a new autogroup config. :::
```
- States selects which states will be added to automatic Corp/Alliance groups
- States select which states will be added to automatic Corp/Alliance groups
- Corp/Alliance groups checkbox toggles Corp/Alliance autogroups on or off for this config. - Corp/Alliance groups checkbox toggles Corp/Alliance autogroups on or off for this config.
- Corp/Alliance group prefix sets the prefix for the group name, e.g., if your corp was called `MyCorp` and your prefix was `Corp`, your autogroup name would be created as `Corp MyCorp`. This field accepts leading/trailing spaces.
- Corp/Alliance group prefix sets the prefix for the group name, e.g. if your Corp was called `MyCorp` and your prefix was `Corp`, your autogroup name would be created as `Corp MyCorp`. This field accepts leading/trailing spaces. - Corp/Alliance name source sets the source of the Corp/Alliance name used in creating the group name. Currently, the options are Full name and Ticker.
- Replace spaces allows you to replace spaces in the autogroup name with the value in the replace spaces with field. This can be blank.
- Corp/Alliance name source sets the source of the Corp/Alliance name used in creating the group name. Currently the options are Full name and Ticker.
- Replace spaces allows you to replace spaces in the autogroup name with the value in the Replace spaces with field. This can be blank.
## Permissions ## Permissions
Auto Groups are configured via models in the Admin Interface, a user will require the `Staff` Flag in addition to the following permissions. Auto Groups are configured via models in the Admin Interface, a user will require the `Staff` Flag in addition to the following permissions.
```eval_rst ```{eval-rst}
+-------------------------------------------+------------------+----------------+ +-------------------------------------------+------------------+----------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+===========================================+==================+================+ +===========================================+==================+================+

View File

@@ -12,13 +12,13 @@ Add `'allianceauth.corputils',` to your `INSTALLED_APPS` list in your auth proje
## Creating a Corp Stats ## Creating a Corp Stats
Upon initial install, nothing will be visible. For every Corp, a model will have to be created before data can be viewed. Upon initial installation, nothing will be visible. For every Corp, a model will have to be created before data can be viewed.
![nothing is visible](/_static/images/features/apps/corpstats/blank_header.png) ![nothing is visible](/_static/images/features/apps/corpstats/blank_header.png)
If you are a superuser, the add button will be immediate visible to you. If not, your user account requires the `add_corpstats` permission. If you are a superuser, the "add" button will be immediately visible to you. If not, your user account requires the `add_corpstats` permission.
Corp Stats requires an EVE SSO token to access data from the EVE Swagger Interface. Upon pressing the Add button, you will be prompted to authenticated. Please select the character who is in the Corporation you want data for. Corp Stats requires an EVE SSO token to access data from the EVE Swagger Interface. Upon pressing the Add button, you will be prompted to authenticate. Please select the character who is in the Corporation you want data for.
![authorize from the EVE site](/_static/images/features/apps/corpstats/eve_sso_authorization.png) ![authorize from the EVE site](/_static/images/features/apps/corpstats/eve_sso_authorization.png)
@@ -26,8 +26,8 @@ You will return to auth where you are asked to select a token with the green arr
![select an SSO token to create with](/_static/images/features/apps/corpstats/select_sso_token.png) ![select an SSO token to create with](/_static/images/features/apps/corpstats/select_sso_token.png)
If this works (and you have permission to view the Corp Stats you just created) you'll be returned to a view of the Corp Stats. If this works (and you have permission to view the Corp Stats you just created), you'll be returned to a view of the Corp Stats.
If it fails an error message will be displayed. If it fails, an error message will be displayed.
## Corp Stats View ## Corp Stats View
@@ -63,13 +63,13 @@ Each view contains a sortable and searchable table. The number of listings shown
![main list](/_static/images/features/apps/corpstats/main_list.png) ![main list](/_static/images/features/apps/corpstats/main_list.png)
This list contains all main characters in registered in the selected Corporation and their alts. Each character has a link to [zKillboard](https://zkillboard.com). This list contains all main characters registered in the selected Corporation and their alts. Each character has a link to [zKillboard](https://zkillboard.com).
#### Member List #### Member List
![member list](/_static/images/features/apps/corpstats/member_list.png) ![member list](/_static/images/features/apps/corpstats/member_list.png)
The list contains all characters in the Corporation. Red backgrounds means they are not registered in auth. A link to [zKillboard](https://zkillboard.com) is present for all characters. The list contains all characters in the Corporation. Red backgrounds mean they are not registered in auth. A link to [zKillboard](https://zkillboard.com) is present for all characters.
If registered, the character will also have a main character, main Corporation, and main Alliance field. If registered, the character will also have a main character, main Corporation, and main Alliance field.
#### Unregistered List #### Unregistered List
@@ -90,7 +90,7 @@ Characters from all Corp Stats to which the user has view access will be display
To use this feature, users will require some of the following: To use this feature, users will require some of the following:
```eval_rst ```{eval-rst}
+---------------------------------------+------------------+----------------------------------------------------+ +---------------------------------------+------------------+----------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+=======================================+==================+====================================================+ +=======================================+==================+====================================================+
@@ -108,7 +108,7 @@ Users who add a Corp Stats with their token will be granted permissions to view
## Automatic Updating ## Automatic Updating
By default Corp Stats are only updated on demand. If you want to automatically refresh on a schedule, add an entry to your project's settings file: By default, Corp Stats are only updated on demand. If you want to automatically refresh on a schedule, add an entry to your project's settings file:
```python ```python
CELERYBEAT_SCHEDULE['update_all_corpstats'] = { CELERYBEAT_SCHEDULE['update_all_corpstats'] = {
@@ -134,11 +134,11 @@ Only one Corp Stats may exist at a time for a given Corporation.
>Failed to gather corporation statistics with selected token. >Failed to gather corporation statistics with selected token.
During initial population, the EVE Swagger Interface did not return any member data. This aborts the creation process. Please wait for the API to start working before attempting to create again. During the initial population, the EVE Swagger Interface did not return any member data. This aborts the creation process. Please wait for the API to start working before attempting to create again.
### Failure to update Corp Stats ### Failure to update Corp Stats
Any of the following errors will result in a notification to the owning user, and deletion of the Corp Stats model. Any of the following errors will result in a notification to the owning user and deletion of the Corp Stats model.
>Your token has expired or is no longer valid. Please add a new one to create a new CorpStats. >Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.
@@ -146,7 +146,7 @@ This occurs when the SSO token is invalid, which can occur when deleted by the u
>CorpStats for (corp name) cannot update with your ESI token as you have left corp. >CorpStats for (corp name) cannot update with your ESI token as you have left corp.
The SSO token's character is no longer in the Corporation which the Corp Stats is for, and therefore membership data cannot be retrieved. The SSO token's character is no longer in the Corporation that the Corp Stats are for, and therefore membership data cannot be retrieved.
>HTTPForbidden >HTTPForbidden

View File

@@ -16,13 +16,12 @@ To administer this feature, users will require some of the following.
Users do not require any permissions to interact with FAT Links created. Users do not require any permissions to interact with FAT Links created.
```eval_rst ```{eval-rst}
+---------------------------------------+------------------+--------------------------------------------------------------------------+ +---------------------------------------+------------------+--------------------------------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+=======================================+==================+==========================================================================+ +=======================================+==================+==========================================================================+
| auth.fleetactivitytracking | None | Create and Modify FATLinks | | auth.fleetactivitytracking | None | Create and Modify FATLinks |
+---------------------------------------+------------------+--------------------------------------------------------------------------+ +---------------------------------------+------------------+--------------------------------------------------------------------------+
| auth.fleetactivitytracking_statistics | None | Can view detailed statistics for corp models and other characters. | | auth.fleetactivitytracking_statistics | None | Can view detailed statistics for corp models and other characters. |
+---------------------------------------+------------------+--------------------------------------------------------------------------+ +---------------------------------------+------------------+--------------------------------------------------------------------------+
``` ```

View File

@@ -4,7 +4,7 @@ This app allows you to manage applications for multiple corporations in your all
- Define application questionnaires for corporations - Define application questionnaires for corporations
- Users can apply to corporations by filling outquestionnaires - Users can apply to corporations by filling outquestionnaires
- Manage review and approval process of applications - Manage a review and approval process of applications
![hr](/_static/images/features/apps/hr.png) ![hr](/_static/images/features/apps/hr.png)
@@ -20,11 +20,11 @@ The most common task is creating ApplicationForm models for corps. Only when suc
The first step is to create questions. This is achieved by creating ApplicationQuestion models, one for each question. Titles are not unique. The first step is to create questions. This is achieved by creating ApplicationQuestion models, one for each question. Titles are not unique.
Next step is to create the actual ApplicationForm model. It requires an existing EveCorporationInfo model to which it will belong. It also requires the selection of questions. ApplicationForm models are unique per Corporation: only one may exist for any given Corporation concurrently. The next step is to create the actual ApplicationForm model. It requires an existing EveCorporationInfo model to which it will belong. It also requires the selection of questions. ApplicationForm models are unique per Corporation: only one may exist for any given Corporation concurrently.
You can adjust these questions at any time. This is the preferred method of modifying the form: deleting and recreating will cascade the deletion to all received applications from this form which is usually not intended. You can adjust these questions at any time. This is the preferred method of modifying the form: deleting and recreating will cascade the deletion to all received applications from this form, which is usually not intended.
Once completed the Corporation will be available to receive applications. Once completed, the Corporation will be available to receive applications.
### Reviewing Applications ### Reviewing Applications
@@ -32,7 +32,7 @@ Superusers can see all applications, while normal members with the required perm
Selecting an application from the management screen will provide all the answers to the questions in the form at the time the user applied. Selecting an application from the management screen will provide all the answers to the questions in the form at the time the user applied.
When a reviewer assigns themselves an application, they mark it as in progress. This notifies the applicant and permanently attached the reviewer to the application. When a reviewer assigns themselves an application, they mark it as in progress. This notifies the applicant and permanently attaches the reviewer to the application.
Only the assigned reviewer can approve/reject/delete the application if they possess the appropriate permission. Only the assigned reviewer can approve/reject/delete the application if they possess the appropriate permission.
@@ -44,7 +44,7 @@ To administer this feature, users will require some of the following.
Users do not require any permission to apply to a corporation and fill out the form. Users do not require any permission to apply to a corporation and fill out the form.
```eval_rst ```{eval-rst}
+---------------------------------------+------------------+----------------------------------------------------+ +---------------------------------------+------------------+----------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+=======================================+==================+====================================================+ +=======================================+==================+====================================================+
@@ -62,25 +62,25 @@ Users do not require any permission to apply to a corporation and fill out the f
A user with `auth.human_resources` can only see applications to their own corp. A user with `auth.human_resources` can only see applications to their own corp.
Best practice is to bundle the `auth.human_resources` permission alongside the `hrapplications.approve_application` and `hrapplications.reject_application` permissions, as in isolation these don't make much sense. Best practice is to bundle the `auth.human_resources` permission alongside the `hrapplications.approve_application` and `hrapplications.reject_application` permissions, as in isolation these make little sense.
## Models ## Models
### ApplicationQuestion ### ApplicationQuestion
This is the model representation of a question. It contains a title, and a field for optional "helper" text. It is referenced by ApplicationForm models but acts independently. Modifying the question after it has been created will not void responses, so it's not advisable to edit the title or the answers may not make sense to reviewers. This is the model representation of a question. It contains a title and a field for optional "helper" text. It is referenced by ApplicationForm models but acts independently. Modifying the question after it has been created will not void responses, so it's not advisable to edit the title or the answers may not make sense to reviewers.
### ApplicationForm ### ApplicationForm
This is the template for an application. It points at a Corporation, with only one form allowed per Corporation. It also points at ApplicationQuestion models. When a user creates an application, they will be prompted with each question the form includes at the given time. Modifying questions in a form after it has been created will not be reflected in existing applications, so it's perfectly fine to adjust them as you see fit. Changing Corporations however is not advisable, as existing applications will point at the wrong Corporation after they've been submitted, confusing reviewers. This is the template for an application. It points at a Corporation, with only one form allowed per Corporation. It also points at ApplicationQuestion models. When a user creates an application, they will be prompted with each question the form includes at the given time. Modifying questions in a form after it has been created will not be reflected in existing applications, so it's perfectly fine to adjust them as you see fit. Changing corporations, however, is not advisable, as existing applications will point at the wrong Corporation after they've been submitted, confusing reviewers.
### Application ### Application
This is the model representation of a completed application. It references an ApplicationForm from which it was spawned which is where the Corporation specificity comes from. It points at a user, contains info regarding its reviewer, and has a status. Shortcut properties also provide the applicant's main character, the applicant's APIs, and a string representation of the reviewer (for cases when the reviewer doesn't have a main character or the model gets deleted). This is the model representation of a completed application. It references an ApplicationForm from which it was spawned, which is where the Corporation specificity comes from. It points at a user, contains info regarding its reviewer, and has a status. Shortcut properties also provide the applicant's main character, the applicant's APIs, and a string representation of the reviewer (for cases when the reviewer doesn't have a main character or the model gets deleted).
### ApplicationResponse ### ApplicationResponse
This is an answer to a question. It points at the Application to which it belongs, to the ApplicationQuestion which it is answering, and contains the answer text. Modifying any of these fields in dangerous. This is an answer to a question. It points at the Application to which it belongs, to the ApplicationQuestion which it is answering, and contains the answer text. Modifying any of these fields is dangerous.
### ApplicationComment ### ApplicationComment
@@ -90,8 +90,8 @@ This is a reviewer's comment on an application. Points at the application, point
### No corps accepting applications ### No corps accepting applications
Ensure there are ApplicationForm models in the admin site. Ensure the user does not already have an application to these Corporations. If the users wishes to re-apply they must first delete their completed application Ensure there are ApplicationForm models in the admin site. Ensure the user does not already have an application to these Corporations. If the users wish to re-apply, they must first delete their completed application
### Reviewer unable to complete application ### Reviewer unable to complete application
Reviewers require a permission for each of the three possible outcomes of an application, Approve Reject or Delete. Any user with the human resources permission can mark an application as in-progress, but if they lack these permissions then the application will get stuck. Either grant the user the required permissions or change the assigned reviewer in the admin site. Best practice is to bundle the `auth.human_resources` permission alongside the `hrapplications.approve_application` and `hrapplications.reject_application` permissions, as in isolation these don't serve much purpose. Reviewers require permission for each of the three possible outcomes of an application, Approve Reject or Delete. Any user with the human resources permission can mark an application as in-progress, but if they lack these permissions, then the application will get stuck. Either grant the user the required permissions or change the assigned reviewer in the admin site. Best practice is to bundle the `auth.human_resources` permission alongside the `hrapplications.approve_application` and `hrapplications.reject_application` permissions, as in isolation these serve little purpose.

View File

@@ -1,17 +1,16 @@
# Apps # Apps
**Alliance Auth** comes with a set of apps (also called plugin-apps) which provide basic functions useful to many organizations in Eve Online like a fleet schedule and a timerboard. This section describes which apps are available and how to install and use them. Please note that any app need to be installed before it can be used. **Alliance Auth** comes with a set of apps (also called plugin-apps) which provide basic functions useful to many organizations in Eve Online like a fleet schedule and a timerboard. This section describes which apps are available and how to install and use them. Please note that any app needs to be installed before it can be used.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
autogroups autogroups
corpstats corpstats
fleetactivitytracking fleetactivitytracking
hrapplications hrapplications
optimer optimer
permissions_tool permissions_tool
srp srp
timerboard timerboard
``` :::

View File

@@ -12,7 +12,7 @@ Add `'allianceauth.optimer',` to your `INSTALLED_APPS` list in your auth project
To use and administer this feature, users will require some of the following. To use and administer this feature, users will require some of the following.
```eval_rst ```{eval-rst}
+---------------------------------------+------------------+--------------------------------------------------------------------------+ +---------------------------------------+------------------+--------------------------------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+=======================================+==================+==========================================================================+ +=======================================+==================+==========================================================================+

View File

@@ -1,6 +1,6 @@
# Permissions Auditing # Permissions Auditing
Access to most of Alliance Auth's features are controlled by Django's permissions system. In order to help you secure your services, Alliance Auth provides a permissions auditing tool. Access to most of Alliance Auth's features is controlled by Django's permissions system. To help you secure your services, Alliance Auth provides a permission auditing tool.
This is an optional app that needs to be installed. This is an optional app that needs to be installed.
@@ -10,9 +10,9 @@ To install it add `'allianceauth.permissions_tool',` to your `INSTALLED_APPS` li
### Access ### Access
In order to grant users access to the permissions auditing tool they will need to be granted the `permissions_tool.audit_permissions` permission or be a superuser. To grant users access to the permission auditing tool, they will need to be granted the `permissions_tool.audit_permissions` permission or be a superuser.
When a user has access to the tool they will see the "Permissions Audit" menu item under the "Util" sub menu. When a user has access to the tool, they will see the "Permissions Audit" menu item.
### Permissions Overview ### Permissions Overview
@@ -42,7 +42,7 @@ Please note that users may appear multiple times if this permission is granted v
To use this feature, users will require some of the following. To use this feature, users will require some of the following.
```eval_rst ```{eval-rst}
+---------------------------------------+------------------+--------------------------------------------------------------------------+ +---------------------------------------+------------------+--------------------------------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+=======================================+==================+==========================================================================+ +=======================================+==================+==========================================================================+

View File

@@ -12,7 +12,7 @@ Add `'allianceauth.srp',` to your `INSTALLED_APPS` list in your auth project's s
To use and administer this feature, users will require some of the following. To use and administer this feature, users will require some of the following.
```eval_rst ```{eval-rst}
+----------------------+------------------+------------------------------------------------------------+ +----------------------+------------------+------------------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+======================+==================+============================================================+ +======================+==================+============================================================+

View File

@@ -12,7 +12,7 @@ Add `'allianceauth.timerboard',` to your `INSTALLED_APPS` list in your auth proj
To use and administer this feature, users will require some of the following. To use and administer this feature, users will require some of the following.
```eval_rst ```{eval-rst}
+---------------------------------------+------------------+--------------------------------------------------------------------------+ +---------------------------------------+------------------+--------------------------------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+=======================================+==================+==========================================================================+ +=======================================+==================+==========================================================================+

View File

@@ -1,7 +1,7 @@
# Community Contributions # Community Contributions
Another key feature of **Alliance Auth** is that it can be easily extended. Our great community is providing a variety of plug-in apps and services, which you can choose from to add more functions to your AA installation. Another key feature of **Alliance Auth** is that it can be easily extended. Our great community is providing a variety of plug-in apps and services, which you can choose from to add more functions to your AA installation.
Check out the [Community Creations](https://gitlab.com/allianceauth/community-creations) repo for more details. Check out the [Community Creations](https://gitlab.com/allianceauth/community-creations) repo for more details.
Or if you have specific needs you can of course always develop your own plugin- apps and services. Please see the [Development](/development/index.md) chapter for details. Or if you have specific needs, you can always develop your own plugin-apps and services. Please see the [Development](/development/index.md) chapter for details.

View File

@@ -1,51 +1,49 @@
# Admin Site # Admin Site
The admin site allows administrators to configure, manage and trouble shoot Alliance Auth and all it's applications and services. E.g. you can create new groups and assign groups to users. The admin site allows administrators to configure, manage and troubleshoot Alliance Auth and all its applications and services. E.g., you can create new groups and assign groups to users.
You can open the admin site by clicking on "Admin" in the drop down menu for a user that has access. You can open the admin site by clicking on "Admin" in the drop-down menu for a user that has access.
![Admin Site](/_static/images/features/core/admin_site.png) ![Admin Site](/_static/images/features/core/admin_site.png)
## Setup for small to medium size installations ## Setup for small to medium size installations
For small to medium size alliances it is often sufficient to have no more then two superuser admins (admins that also are superusers). Having two admins usually makes sense, so you can have one primary and one backup. For small to medium size alliances, it is often sufficient to have no more than two superuser admins (admins that also are superusers). Having two admins usually makes sense, so you can have one primary and one backup.
```eval_rst :::{warning}
.. warning:: Superusers have read & write access to everything on your AA installation. Superuser also automatically have all permissions and therefore access to all features of your apps. Therefore, we recommend to be very careful to whom you give superuser privileges.
Superusers have read & write access to everything on your AA installation. Superusers also automatically have all permissions and therefore access to all features of your apps. Therefore we recommend to be very careful to whom you give superuser privileges. :::
```
## Setup for large installations ## Setup for large installations
For large alliances and coalitions you may want to have a couple of administrators to be able to distribute and handle the work load. However, having a larger number of superusers may be a security concern. For large alliances and coalitions, you may want to have a couple of administrators to be able to distribute and handle the work load. However, having a larger number of superusers may be a security concern.
As an alternative to superusers admins you can define staff admins. Staff admins can perform most of the daily admin work, but are not superusers and therefore can be restricted in what they can access. As an alternative to superusers admins, you can define staff admins. Staff admins can perform most of the daily admin work, but are not superusers and therefore can be restricted in what they can access.
To create a staff admin you need to do two things: To create a staff admin, you need to do two things:
1. Enable the `is_staff` property for a user 1. Enable the `is_staff` property for a user
1. Give the user permissions for admin tasks 1. Give the user permissions for admin tasks
```eval_rst :::{note}
.. note:: Note that staff admins have the following limitations:
Note that staff admins have the following limitations:
- Can not promote users to staff - Cannot promote users to staff
- Can not promote users to superuser - Cannot promote users to superuser
- Can not add/remove permissions for users, groups and states - Cannot add/remove permissions for users, groups and states
These limitations exist to prevent staff admins to promote themselves to quasi superusers. Only superusers can perform these actions. These limitations exist to prevent staff admins from promoting themselves to quasi superusers. Only superusers can perform these actions.
```
:::
### Staff property ### Staff property
Access to the admin site is restricted. Users needs to have the `is_staff` property to be able to open the site at all. The superuser that is created during the installation Access to the admin site is restricted. Users need to have the `is_staff` property to be able to open the site at all. The superuser created during the installation
process will automatically have access to the admin site. process will automatically have access to the admin site.
```eval_rst :::{hint}
.. hint:: Without any permissions, a "staff user" can open the admin site, but can neither view nor edit anything except for viewing the list of permissions.
Without any permissions a "staff user" can open the admin site, but can neither view nor edit anything except for viewing the list of permissions. :::
```
### Permissions for common admin tasks ### Permissions for common admin tasks
@@ -93,4 +91,4 @@ Here is a list of permissions a staff admin would need to perform some common ad
### Permissions for other apps ### Permissions for other apps
The permissions a staff admin needs to perform tasks for other applications depends on how the applications are configured. the default is to have four permissions (change, delete, edit view) for each model of the applications. The view permission is usually required to see the model list on the admin site and the other three permissions are required to perform the respective action to an object of that model. However, app developer can chose to define permissions differently. The permission a staff admin needs to perform tasks for other applications depends on how the applications are configured. The default is to have four permissions (change, delete, edit view) for each model of the applications. The view permission is usually required to see the model list on the admin site, and the other three permissions are required to perform the respective action to an object of that model. However, an app developer can choose to define permissions differently.

View File

@@ -6,7 +6,7 @@
Before you proceed, please read through this page and/or raise any concerns on the Alliance Auth discord. This data helps us make AA better. Before you proceed, please read through this page and/or raise any concerns on the Alliance Auth discord. This data helps us make AA better.
To Opt-Out, modify our pre-loaded token using the Admin dashboard */admin/analytics/analyticstokens/1/change/ To opt out, modify our preloaded token using the Admin dashboard */admin/analytics/analyticstokens/1/change/
Each of the three features Daily Stats, Celery Events and Page Views can be enabled/Disabled independently. Each of the three features Daily Stats, Celery Events and Page Views can be enabled/Disabled independently.
@@ -20,13 +20,13 @@ ANALYTICS_DISABLED = True
## What ## What
Alliance Auth has taken great care to anonymize the data sent. In order to identify _unique_ installs we generate a UUIDv4, a random mathematical construct which does not contain any identifying information [UUID - UUID Objects](https://docs.python.org/3/library/uuid.html#uuid.uuid4) Alliance Auth has taken great care to anonymize the data sent. To identify _unique_ installs, we generate a UUIDv4, a random mathematical construct which does not contain any identifying information [UUID - UUID Objects](https://docs.python.org/3/library/uuid.html#uuid.uuid4)
Analytics comes pre-loaded with our Google Analytics Token, and the Three Types of task can be opted out independently. Analytics can also be loaded with your _own_ GA token and the analytics module will act any/all tokens loaded. Analytics comes preloaded with our Google Analytics token, and the three types of tasks can be opted out independently. Analytics can also be loaded with your _own_ GA token, and the analytics module will act any/all tokens loaded.
Our Daily Stats contain the following: Our Daily Stats contain the following:
- A phone-in task to identify a servers existence - A phone-in task to identify a server's existence
- A task to send the Number of User models - A task to send the Number of User models
- A task to send the Number of Token Models - A task to send the Number of Token Models
- A task to send the Number of Installed Apps - A task to send the Number of Installed Apps
@@ -36,7 +36,7 @@ Our Daily Stats contain the following:
Our Celery Events contain the following: Our Celery Events contain the following:
- Unique Identifier (The UUID) - Unique Identifier (The UUID)
- Celery Namespace of the task eg allianceauth.eveonline - Celery Namespace of the task e.g., allianceauth.eveonline
- Celery Task - Celery Task
- Task Success or Exception - Task Success or Exception
- A context number for bulk tasks or sometimes a binary True/False - A context number for bulk tasks or sometimes a binary True/False
@@ -47,24 +47,24 @@ Our Page Views contain the following:
- Page Path - Page Path
- Page Title - Page Title
- The locale of the users browser - The locale of the users browser
- The User-Agent of the users browser - The User-Agent of the user's browser
- The Alliance Auth Version - The Alliance Auth Version
## Why ## Why
This data allows Alliance Auth development to gather accurate statistics on our install base, as well as how those installs are used. This data allows Alliance Auth development to gather accurate statistics on our installation base, as well as how those installations are used.
This allows us to better target our development time to commonly used modules and features and test them at the scales in use. This allows us to better target our development time to commonly used modules and features and test them at the scales in use.
## Where ## Where
This data is stored in a Team Google Analytics Dashboard. The Maintainers all have Management permissions here, and if you have contributed to the Alliance Auth project or third party applications feel free to ask in the Alliance Auth discord for access. This data is stored in a Team Google Analytics Dashboard. The Maintainers all have Management permissions here, and if you have contributed to the Alliance Auth project or third party applications, feel free to ask in the Alliance Auth discord for access.
## Using Analytics in my App ## Using Analytics in my App
### Analytics Event ### Analytics Event
```eval_rst ```{eval-rst}
.. automodule:: allianceauth.analytics.tasks .. automodule:: allianceauth.analytics.tasks
:members: analytics_event :members: analytics_event
:undoc-members: :undoc-members:

View File

@@ -1,10 +1,10 @@
# Dashboard # Dashboard
The dashboard is the main page of the **Alliance Auth** website and the first page every logged in user will see. The dashboard is the main page of the **Alliance Auth** website, and the first page every logged-in user will see.
The content of the dashboard is specific to the logged in user. It has a sidebar, which will display the list of apps a user currently as access to based on his permissions. And it also shows which character the user has registered and to which group he belongs. The content of the dashboard is specific to the logged-in user. It has a sidebar, which will display the list of apps a user currently as access to based on his permissions. And it also shows which character the user has registered and to which group he belongs.
For admin users the dashboard shows additional technical information about the AA instance. For admin users, the dashboard shows additional technical information about the AA instance.
![dashboard](/_static/images/features/core/dashboard/dashboard.png) ![dashboard](/_static/images/features/core/dashboard/dashboard.png)
@@ -13,7 +13,7 @@ For admin users the dashboard shows additional technical information about the
Here is a list of available settings for the dashboard. They can be configured by adding them to your AA settings file (``local.py``). Here is a list of available settings for the dashboard. They can be configured by adding them to your AA settings file (``local.py``).
Note that all settings are optional and the app will use the documented default settings if they are not used. Note that all settings are optional and the app will use the documented default settings if they are not used.
```eval_rst ```{eval-rst}
+-----------------------------------------------------+-------------------------------------------------------------------------+-----------+ +-----------------------------------------------------+-------------------------------------------------------------------------+-----------+
| Name | Description | Default | | Name | Description | Default |
+=====================================================+=========================================================================+===========+ +=====================================================+=========================================================================+===========+

View File

@@ -14,7 +14,7 @@ Here you have several options:
### Internal ### Internal
Users cannot see, join or request to join this group. This is primarily used for Auth's internally managed groups, though can be useful if you want to prevent users from managing their membership of this group themselves. This option will override the Hidden, Open and Public options when enabled. Users cannot see, join or request to join this group. This is primarily used for Auth's internally managed groups, though it can be useful if you want to prevent users from managing their membership of this group themselves. This option will override the Hidden, Open and Public options when enabled.
By default, every new group created will be an internal group. By default, every new group created will be an internal group.
@@ -36,28 +36,27 @@ Group is accessible to any registered user, even when they do not have permissio
The key difference is that the group is completely unmanaged by Auth. **Once a member joins they will not be removed unless they leave manually, you remove them manually, or their account is deliberately set inactive or deleted.** The key difference is that the group is completely unmanaged by Auth. **Once a member joins they will not be removed unless they leave manually, you remove them manually, or their account is deliberately set inactive or deleted.**
Most people won't have a use for public groups, though it can be useful if you wish to allow public access to some services. You can grant service permissions on a public group to allow this behavior. Most people won't have a use for public groups, though it can be useful if you wish to allow public access to some services. You can grant service permissions to a public group to allow this behavior.
### Restricted ### Restricted
When a group is restricted only superuser admins can directly add or remove them to/from users. The purpose of this property is prevent staff admins from assigning themselves to groups that are security sensitive. The "restricted" property can be combined with all the other properties. When a group is restricted, only superuser admins can directly add or remove them to/from users. The purpose of this property is to prevent staff admins from assigning themselves to groups that are security sensitive. The "restricted" property can be combined with all the other properties.
```eval_rst ```{eval-rst}
.. _ref-reserved-group-names: .. _ref-reserved-group-names:
``` ```
## Reserved group names ## Reserved group names
When using Alliance Auth to manage external services like Discord, Auth will automatically duplicate groups on those services. E.g. on Discord Auth will create roles of the same name as groups. However, there may be cases where you want to manage groups on external services by yourself or by another bot. For those cases you can define a list of reserved group names. Auth will ensure that you can not create groups with a reserved name. You will find this list on the admin site under groupmanagement. When using Alliance Auth to manage external services like Discord, Auth will automatically duplicate groups on those services. E.g., on Discord Auth will create roles of the same name as groups. However, there may be cases where you want to manage groups on external services by yourself or by another bot. For those cases, you can define a list of reserved group names. Auth will ensure that you cannot create groups with a reserved name. You will find this list on the admin site under groupmanagement.
```eval_rst :::{note}
.. note:: While this feature can help to avoid naming conflicts with groups on external services, the respective service component in Alliance Auth also needs to be built in such a way that it knows how to prevent these conflicts. Currently only the Discord and Teamspeak3 services have this ability.
While this feature can help to avoid naming conflicts with groups on external services, the respective service component in Alliance Auth also needs to be build in such a way that it knows how to prevent these conflicts. Currently only the Discord and Teamspeak3 services have this ability. :::
```
## Managing groups ## Managing groups
In order to access group management, users need to be either a superuser, granted the `auth | user | group_management ( Access to add members to groups within the alliance )` permission or a group leader (discussed later). To access group management, users need to be either a superuser, granted the `auth | user | group_management ( Access to add members to groups within the alliance )` permission or a group leader (discussed later).
### Group Requests ### Group Requests
@@ -65,7 +64,7 @@ When a user joins or leaves a group which is not marked as "Open", their group r
### Group Membership ### Group Membership
The group membership tab gives an overview of all of the non-internal groups. The group membership tab gives an overview of all the non-internal groups.
![Group overview](/_static/images/features/core/groupmanagement/group-membership.png) ![Group overview](/_static/images/features/core/groupmanagement/group-membership.png)
@@ -90,7 +89,7 @@ Group leaders have the same abilities as users with the `group_management` permi
- Approve requests for groups they are a leader of. - Approve requests for groups they are a leader of.
- View the Group Membership and Group Members of groups they are leaders of. - View the Group Membership and Group Members of groups they are leaders of.
This allows you to more finely control who has access to manage which groups. This allows you to more fine control who has access to manage which groups.
### Auto Leave ### Auto Leave
@@ -101,17 +100,16 @@ By default, in AA both requests and leaves for non-open groups must be approved
GROUPMANAGEMENT_AUTO_LEAVE = True GROUPMANAGEMENT_AUTO_LEAVE = True
``` ```
```eval_rst :::{note}
.. note:: Before you set `GROUPMANAGEMENT_AUTO_LEAVE = True`, make sure there are no pending leave requests, as this option will hide the "Leave Requests" tab.
Before you set `GROUPMANAGEMENT_AUTO_LEAVE = True`, make sure there are no pending leave requests, as this option will hide the "Leave Requests" tab. :::
```
## Settings ## Settings
Here is a list of available settings for Group Management. They can be configured by adding them to your AA settings file (``local.py``). Here is a list of available settings for Group Management. They can be configured by adding them to your AA settings file (``local.py``).
Note that all settings are optional and the app will use the documented default settings if they are not used. Note that all settings are optional and the app will use the documented default settings if they are not used.
```eval_rst ```{eval-rst}
+---------------------------------------------+---------------------------------------------------------------------------+------------+ +---------------------------------------------+---------------------------------------------------------------------------+------------+
| Name | Description | Default | | Name | Description | Default |
+=============================================+===========================================================================+============+ +=============================================+===========================================================================+============+
@@ -123,18 +121,17 @@ Note that all settings are optional and the app will use the documented default
## Permissions ## Permissions
In order to join a group other than a public group, the permission `groupmanagement.request_groups` (`Can request non-public groups` in the admin panel) must be active on their account, either via a group or directly applied to their User account. To join a group other than a public group, the permission `groupmanagement.request_groups` (`Can request non-public groups` in the admin panel) must be active on their account, either via a group or directly applied to their User account.
When a user loses this permission, they will be removed from all groups _except_ Public groups. When a user loses this permission, they will be removed from all groups _except_ Public groups.
```eval_rst :::{note}
.. note:: By default, the ``groupmanagement.request_groups`` permission is applied to the ``Member`` group. In most instances this, and perhaps adding it to the ``Blue`` group, should be all that is ever needed. It is unsupported and NOT advisable to apply this permission to a public group. See #697 for more information.
By default, the ``groupmanagement.request_groups`` permission is applied to the ``Member`` group. In most instances this, and perhaps adding it to the ``Blue`` group, should be all that is ever needed. It is unsupported and NOT advisable to apply this permission to a public group. See #697 for more information. :::
```
Group Management should be mostly done using group leaders, a series of permissions are included below for thoroughness: Group Management should be mostly done using group leaders, a series of permissions are included below for thoroughness:
```eval_rst ```{eval-rst}
+--------------------------------+-------------------+------------------------------------------------------------------------------------+ +--------------------------------+-------------------+------------------------------------------------------------------------------------+
| Permission | Admin Site | Auth Site | | Permission | Admin Site | Auth Site |
+================================+===================+====================================================================================+ +================================+===================+====================================================================================+

View File

@@ -1,15 +1,14 @@
# Core Features # Core Features
Managing access to applications and services is one of the core functions of **Alliance Auth**. The related key concepts and functionalities are describes in this section. Managing access to applications and services is one of the core functions of **Alliance Auth**. The related key concepts and functionalities are described in this section.
```eval_rst :::{toctree}
.. toctree:: :maxdepth: 1
:maxdepth: 1
dashboard dashboard
states states
groups groups
analytics analytics
notifications notifications
admin_site admin_site
``` :::

View File

@@ -1,14 +1,14 @@
# Notifications # Notifications
Alliance Auth has a build in notification system. The purpose of the notification system is to provide an easy and quick way to send messages to users of Auth. For example some apps are using it to inform users about results after long running tasks have completed and admins will automatically get notifications about system errors. Alliance Auth has a build in notification system. The purpose of the notification system is to provide an easy and quick way to send messages to users of Auth. For example, some apps are using it to inform users about results after long-running tasks have been completed, and admins will automatically get notifications about system errors.
The number of unread notifications is shown to the user in the top menu. And the user can click on the notification count to open the notifications app. The number of unread notifications is shown to the user in the top menu. And the user can click on the notification count to open the Notifications app.
![notification app](/_static/images/features/core/notifications.png) ![notification app](/_static/images/features/core/notifications.png)
## Settings ## Settings
The notifications app can be configured through settings. The Notifications app can be configured through settings.
- `NOTIFICATIONS_REFRESH_TIME`: The unread count in the top menu is automatically refreshed to keep the user informed about new notifications. This setting allows to set the time between each refresh in seconds. You can also set it to `0` to turn off automatic refreshing. Default: `30` - `NOTIFICATIONS_REFRESH_TIME`: The unread count in the top menu is automatically refreshed to keep the user informed about new notifications. This setting allows setting the time between each refresh in seconds. You can also set it to `0` to turn off automatic refreshing. Default: `30`
- `NOTIFICATIONS_MAX_PER_USER`: Maximum number of notifications that are stored per user. Older notifications are replaced by newer once. Default: `50` - `NOTIFICATIONS_MAX_PER_USER`: Maximum number of notifications that are stored per user. Newer replace older notifications. Default: `50`

Some files were not shown because too many files have changed in this diff Show More