Compare commits

...

69 Commits

Author SHA1 Message Date
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
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
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
Ariel Rin
12383d79c8 Version Bump 3.6.0 2023-08-01 21:37:25 +10:00
Ariel Rin
56e2875650 Merge branch 'pre-commit-update' into 'master'
`pre-commit` Update and File Permission Fixes (redone)

See merge request allianceauth/allianceauth!1523
2023-08-01 11:22:37 +00:00
Peter Pfeufer
d0118e6c0b [FIX] File permissions 2023-08-01 12:55:19 +02:00
Peter Pfeufer
7075ccdf7a [CHANGE] Django Upgrade checks applied 2023-08-01 12:52:05 +02:00
Ariel Rin
b2d540c010 Merge branch 'add-public-routes-feature' into 'master'
Add public routes feature

See merge request allianceauth/allianceauth!1514
2023-08-01 10:20:14 +00:00
Erik Kalkoken
7cb7e2c77b Add public routes feature 2023-08-01 10:20:13 +00:00
Ariel Rin
5d6a4ab1a9 Merge branch 'feature-show-running-tasks' into 'master'
Show running tasks on dashboard

See merge request allianceauth/allianceauth!1515
2023-08-01 10:15:43 +00:00
Erik Kalkoken
1122d617bd Show running tasks on dashboard 2023-08-01 10:15:42 +00:00
Ariel Rin
ef33501e45 Merge branch 'proper-favicon-support' into 'master'
Proper favicon support

See merge request allianceauth/allianceauth!1520
2023-08-01 10:14:21 +00:00
Ariel Rin
08fd86db8f Merge branch 'migrate-pep-621' into 'master'
Migrate to PEP 621

See merge request allianceauth/allianceauth!1513
2023-07-25 09:32:29 +00:00
Erik Kalkoken
c4193c15fc Migrate to PEP 621 2023-07-25 09:32:28 +00:00
Ariel Rin
903074080e Merge branch 'unknown_discord_group_patch' into 'master'
Discord: Don't fail on unknown groups, simply remove them.

See merge request allianceauth/allianceauth!1504
2023-07-25 09:27:19 +00:00
Ariel Rin
3046a26a02 Merge branch 'AA-Timer-Absolute' into 'master'
Adding Absolute Timers to base timerboard

See merge request allianceauth/allianceauth!1518
2023-07-25 09:26:10 +00:00
Hamish W
951c4135c2 Adding Absolute Timers to base timerboard 2023-07-25 09:26:10 +00:00
Ariel Rin
b256a0c5e1 Merge branch 'next-params' into 'master'
Encode Next Param for Login Redirection

See merge request allianceauth/allianceauth!1519
2023-07-25 09:02:31 +00:00
Ariel Rin
212b9b0f60 Merge branch 'stringify-crontab-arguments' into 'master'
[FIX] `crontab` arguments are of type `string`, not `int`

See merge request allianceauth/allianceauth!1517
2023-07-25 08:55:25 +00:00
Peter Pfeufer
fc29d7e80d [ADD] All modern favicon versions generated by realfavicongenerator.net 2023-07-19 12:10:02 +02:00
Peter Pfeufer
ec536c66a0 [ADD] Favicon redirect to Nginx docs 2023-07-19 11:40:45 +02:00
Peter Pfeufer
749ece45e2 [ADD] Favicon redirect to Apache2 docs 2023-07-19 11:39:04 +02:00
Peter Pfeufer
b04c8873d0 [ADD] Directive for default favicon 2023-07-19 11:37:07 +02:00
Aaron Kable
9a77175bf3 Allow get params from next at login 2023-07-17 10:25:36 +08:00
Peter Pfeufer
5d4c7b9030 [FIX] crontab arguments here as well 2023-07-14 19:22:29 +02:00
Aaron Kable
5f80259d57 fix test 2023-07-13 20:28:35 +08:00
Peter Pfeufer
dcd6bd1b36 [FIX] crontab arguments are of type string, not int 2023-07-12 13:11:18 +02:00
Ariel Rin
6f4dffe930 Version Bump 3.5.1 2023-07-11 22:38:26 +10:00
Ariel Rin
56d70e6c74 Add pkg-config to dockerfile 2023-07-11 22:36:15 +10:00
Aaron Kable
1cae20fe5f Dont fail on unknown groups, simply remove them. 2023-05-19 05:00:39 +08:00
152 changed files with 2228 additions and 1162 deletions

View File

@@ -1,6 +0,0 @@
[settings]
profile=django
sections=FUTURE,STDLIB,THIRDPARTY,DJANGO,ESI,FIRSTPARTY,LOCALFOLDER
known_esi=esi
known_django=django
skip_gitignore=true

View File

@@ -7,12 +7,33 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-case-conflict
- id: check-json
- id: check-xml
# Identify invalid files
- id: check-ast
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-xml
# git checks
- id: check-merge-conflict
- id: check-added-large-files
args: [ --maxkb=1000 ]
- id: detect-private-key
- id: check-case-conflict
# Python checks
# - id: check-docstring-first
- id: debug-statements
# - id: requirements-txt-fixer
- id: fix-encoding-pragma
args: [ --remove ]
- id: fix-byte-order-marker
# General quality checks
- id: mixed-line-ending
args: [ --fix=lf ]
- id: trailing-whitespace
args: [ --markdown-linebreak-ext=md ]
exclude: |
(?x)(
\.min\.css|
@@ -21,6 +42,7 @@ repos:
\.mo|
swagger\.json
)
- id: check-executables-have-shebangs
- id: end-of-file-fixer
exclude: |
(?x)(
@@ -30,13 +52,9 @@ repos:
\.mo|
swagger\.json
)
- id: mixed-line-ending
args: [ '--fix=lf' ]
- id: fix-encoding-pragma
args: [ '--remove' ]
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.7.1
rev: 2.7.2
hooks:
- id: editorconfig-checker
exclude: |
@@ -48,14 +66,14 @@ repos:
swagger\.json
)
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [ --target-version=4.0 ]
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.10.1
hooks:
- id: pyupgrade
args: [ --py38-plus ]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.2.0
hooks:
- id: setup-cfg-fmt
args: [ --include-version-classifiers ]

View File

@@ -1,5 +1,5 @@
[main]
host = https://www.transifex.com
host = https://app.transifex.com
lang_map = zh-Hans: zh_Hans
[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

@@ -1,7 +0,0 @@
include LICENSE
include README.md
include MANIFEST.in
graft allianceauth
global-exclude __pycache__
global-exclude *.py[co]

0
README.md Executable file → Normal file
View File

View File

@@ -1,7 +1,11 @@
"""An auth system for EVE Online to help in-game organizations
manage online service access.
"""
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '3.5.0'
__version__ = '3.8.0'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}'

View File

@@ -65,7 +65,7 @@ class StateBackend(ModelBackend):
# we've seen this character owner before. Re-attach to their old user account
user = records[0].user
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.
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.")

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

@@ -1,18 +1,28 @@
from django.conf.urls import include
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
from functools import wraps
from django.shortcuts import redirect
from typing import Callable, Iterable, Optional
from django.urls import include
from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.decorators import login_required
def user_has_main_character(user):
return bool(user.profile.main_character)
def decorate_url_patterns(urls, decorator):
def decorate_url_patterns(
urls, decorator: Callable, excluded_views: Optional[Iterable] = None
):
"""Decorate views given in url patterns except when they are explicitly excluded.
Args:
- urls: Django URL patterns
- decorator: Decorator to be added to each view
- exclude_views: Optional iterable of view names to be excluded
"""
url_list, app_name, namespace = include(urls)
def process_patterns(url_patterns):
@@ -22,6 +32,8 @@ def decorate_url_patterns(urls, decorator):
process_patterns(pattern.url_patterns)
else:
# this is a pattern
if excluded_views and pattern.lookup_str in excluded_views:
return
pattern.callback = decorator(pattern.callback)
process_patterns(url_list)

View File

@@ -1,8 +1,5 @@
from django.conf.urls import include
from allianceauth.authentication import views
from django.urls import re_path
from django.urls import path
from django.urls import include, re_path, path
urlpatterns = [
path('activate/complete/', views.activation_complete, name='registration_activation_complete'),

0
allianceauth/authentication/managers.py Executable file → Normal file
View File

0
allianceauth/authentication/models.py Executable file → Normal file
View File

View File

@@ -1,29 +1,34 @@
from collections import namedtuple
"""Counters for Task Statistics."""
import datetime as dt
from typing import NamedTuple, Optional
from .event_series import EventSeries
"""Global series for counting task events."""
# Global series for counting task events.
succeeded_tasks = EventSeries("SUCCEEDED_TASKS")
retried_tasks = EventSeries("RETRIED_TASKS")
failed_tasks = EventSeries("FAILED_TASKS")
_TaskCounts = namedtuple(
"_TaskCounts", ["succeeded", "retried", "failed", "total", "earliest_task", "hours"]
)
class _TaskCounts(NamedTuple):
succeeded: int
retried: int
failed: int
total: int
earliest_task: Optional[dt.datetime]
hours: int
def dashboard_results(hours: int) -> _TaskCounts:
"""Counts of all task events within the given timeframe."""
"""Counts of all task events within the given time frame."""
def earliest_if_exists(events: EventSeries, earliest: dt.datetime) -> list:
my_earliest = events.first_event(earliest=earliest)
return [my_earliest] if my_earliest else []
earliest = dt.datetime.utcnow() - dt.timedelta(hours=hours)
earliest_events = list()
earliest_events = []
succeeded_count = succeeded_tasks.count(earliest=earliest)
earliest_events += earliest_if_exists(succeeded_tasks, earliest)
retried_count = retried_tasks.count(earliest=earliest)

View File

@@ -1,61 +1,31 @@
"""Event series for Task Statistics."""
import datetime as dt
import logging
from typing import List, Optional
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__)
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:
"""API for recording and analyzing a series of events."""
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
def __init__(self, key_id: str, redis: Redis = None) -> None:
self._redis = get_redis_client() 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()
def __init__(self, key_id: str, redis: Optional[Redis] = None) -> None:
self._redis = get_redis_client_or_stub() if not redis else redis
self._key_id = str(key_id)
self.clear()
@property
def is_disabled(self):
"""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
def _key_counter(self):
@@ -73,8 +43,8 @@ class EventSeries:
"""
if not event_time:
event_time = dt.datetime.utcnow()
id = self._redis.incr(self._key_counter)
self._redis.zadd(self._key_sorted_set, {id: event_time.timestamp()})
my_id = self._redis.incr(self._key_counter)
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})
def all(self) -> List[dt.datetime]:
"""List of all known events."""
@@ -95,15 +65,15 @@ class EventSeries:
self._redis.delete(self._key_counter)
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:
- earliest: Date of first events to count(inclusive), or -infinite if not specified
- latest: Date of last events to count(inclusive), or +infinite if not specified
"""
min = "-inf" if not earliest else earliest.timestamp()
max = "+inf" if not latest else latest.timestamp()
return self._redis.zcount(self._key_sorted_set, min=min, max=max)
minimum = "-inf" if not earliest else earliest.timestamp()
maximum = "+inf" if not latest else latest.timestamp()
return self._redis.zcount(self._key_sorted_set, min=minimum, max=maximum)
def first_event(self, earliest: dt.datetime = None) -> Optional[dt.datetime]:
"""Date/Time of first event. Returns `None` if series has no events.
@@ -111,10 +81,10 @@ class EventSeries:
Args:
- earliest: Date of first events to count(inclusive), or any if not specified
"""
min = "-inf" if not earliest else earliest.timestamp()
minimum = "-inf" if not earliest else earliest.timestamp()
event = self._redis.zrangebyscore(
self._key_sorted_set,
min,
minimum,
"+inf",
withscores=True,
start=0,

View File

@@ -0,0 +1,49 @@
"""Helpers for Task Statistics."""
import logging
from redis import Redis, RedisError
from allianceauth.utils.cache import get_redis_client
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.
"""
IS_STUB = True
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
def get_redis_client_or_stub() -> Redis:
"""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,9 +1,7 @@
"""Signals for Task Statistics."""
from celery.signals import (
task_failure,
task_internal_error,
task_retry,
task_success,
worker_ready
task_failure, task_internal_error, task_retry, task_success, worker_ready,
)
from django.conf import settings
@@ -19,6 +17,7 @@ def reset_counters():
def is_enabled() -> bool:
"""Return True if task statistics are enabled, else return False."""
return not bool(
getattr(settings, "ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED", False)
)

View File

@@ -4,29 +4,30 @@ from django.test import TestCase
from django.utils.timezone import now
from allianceauth.authentication.task_statistics.counters import (
dashboard_results,
succeeded_tasks,
retried_tasks,
failed_tasks,
dashboard_results, failed_tasks, retried_tasks, succeeded_tasks,
)
class TestDashboardResults(TestCase):
def test_should_return_counts_for_given_timeframe_only(self):
def test_should_return_counts_for_given_time_frame_only(self):
# given
earliest_task = now() - dt.timedelta(minutes=15)
succeeded_tasks.clear()
succeeded_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
succeeded_tasks.add(earliest_task)
succeeded_tasks.add()
succeeded_tasks.add()
retried_tasks.clear()
retried_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
retried_tasks.add(now() - dt.timedelta(seconds=30))
retried_tasks.add()
failed_tasks.clear()
failed_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
failed_tasks.add()
# when
results = dashboard_results(hours=1)
# then

View File

@@ -1,48 +1,19 @@
import datetime as dt
from unittest.mock import patch
from pytz import utc
from redis import RedisError
from django.test import TestCase
from django.utils.timezone import now
from allianceauth.authentication.task_statistics.event_series import (
EventSeries,
_RedisStub,
)
from allianceauth.authentication.task_statistics.helpers import _RedisStub
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
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):
# given
events = EventSeries("dummy")
@@ -166,3 +137,15 @@ class TestEventSeries(TestCase):
results = events.all()
# then
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

@@ -17,16 +17,17 @@ from allianceauth.eveonline.tasks import update_character
@override_settings(
CELERY_ALWAYS_EAGER=True,ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
CELERY_ALWAYS_EAGER=True, ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
)
class TestTaskSignals(TestCase):
fixtures = ["disable_analytics"]
def test_should_record_successful_task(self):
# given
def setUp(self) -> None:
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
def test_should_record_successful_task(self):
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
@@ -39,10 +40,6 @@ class TestTaskSignals(TestCase):
self.assertEqual(failed_tasks.count(), 0)
def test_should_record_retried_task(self):
# given
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
@@ -55,10 +52,6 @@ class TestTaskSignals(TestCase):
self.assertEqual(retried_tasks.count(), 1)
def test_should_record_failed_task(self):
# given
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"

View File

@@ -5,7 +5,7 @@
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
{% get_language_info_list for LANGUAGES as 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 }})
</option>
{% endfor %}

View File

@@ -6,7 +6,7 @@
{% block page_title %}{% translate "Login" %}{% endblock %}
{% block middle_box_content %}
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next | urlencode}}{%endif%}">
<img class="img-responsive center-block" src="{% static 'allianceauth/authentication/img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" alt="{% translate 'Login with Eve SSO' %}">
</a>
{% endblock %}

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

@@ -4,16 +4,16 @@ from urllib import parse
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http.response import HttpResponse
from django.shortcuts import reverse
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse, URLPattern
from allianceauth.eveonline.models import EveCharacter
from allianceauth.tests.auth_utils import AuthUtils
from ..decorators import main_character_required
from ..models import CharacterOwnership
from ..decorators import decorate_url_patterns, main_character_required
from ..models import CharacterOwnership
MODULE_PATH = 'allianceauth.authentication'
@@ -66,3 +66,33 @@ class DecoratorTestCase(TestCase):
setattr(self.request, 'user', self.main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 200)
class TestDecorateUrlPatterns(TestCase):
def test_should_add_decorator_by_default(self):
# given
decorator = mock.MagicMock(name="decorator")
view = mock.MagicMock(name="view")
path = mock.MagicMock(spec=URLPattern, name="path")
path.callback = view
path.lookup_str = "my_lookup_str"
urls = [path]
urlconf_module = urls
# when
decorate_url_patterns(urlconf_module, decorator)
# then
self.assertEqual(path.callback, decorator(view))
def test_should_not_add_decorator_when_excluded(self):
# given
decorator = mock.MagicMock(name="decorator")
view = mock.MagicMock(name="view")
path = mock.MagicMock(spec=URLPattern, name="path")
path.callback = view
path.lookup_str = "my_lookup_str"
urls = [path]
urlconf_module = urls
# when
decorate_url_patterns(urlconf_module, decorator, excluded_views=["my_lookup_str"])
# then
self.assertEqual(path.callback, view)

View File

@@ -9,12 +9,8 @@ from django.core.cache import cache
from django.test import TestCase
from allianceauth.templatetags.admin_status import (
status_overview,
_fetch_list_from_gitlab,
_current_notifications,
_current_version_summary,
_fetch_notification_issues_from_gitlab,
_latests_versions
_current_notifications, _current_version_summary, _fetch_list_from_gitlab,
_fetch_notification_issues_from_gitlab, _latests_versions, status_overview,
)
MODULE_PATH = 'allianceauth.templatetags'
@@ -56,14 +52,10 @@ TEST_VERSION = '2.6.5'
class TestStatusOverviewTag(TestCase):
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status._fetch_celery_queue_length')
@patch(MODULE_PATH + '.admin_status._current_version_summary')
@patch(MODULE_PATH + '.admin_status._current_notifications')
def test_status_overview(
self,
mock_current_notifications,
mock_current_version_info,
mock_fetch_celery_queue_length
self, mock_current_notifications, mock_current_version_info
):
# given
notifications = {
@@ -82,7 +74,6 @@ class TestStatusOverviewTag(TestCase):
'latest_beta_version': '2.4.4a1',
}
mock_current_version_info.return_value = version_info
mock_fetch_celery_queue_length.return_value = 3
# when
result = status_overview()
# then
@@ -96,7 +87,6 @@ class TestStatusOverviewTag(TestCase):
self.assertEqual(result["latest_minor_version"], '2.4.0')
self.assertEqual(result["latest_patch_version"], '2.4.5')
self.assertEqual(result["latest_beta_version"], '2.4.4a1')
self.assertEqual(result["task_queue_length"], 3)
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'
),
path('dashboard/', views.dashboard, name='dashboard'),
path('task-counts/', views.task_counts, name='task_counts'),
]

View File

@@ -1,31 +1,31 @@
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.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.models import User
from django.core import signing
from django.core.mail import EmailMultiAlternatives
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.template.loader import render_to_string
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter
from esi.decorators import token_required
from esi.models import Token
from django_registration.backends.activation.views import (
RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView,
REGISTRATION_SALT
)
from django_registration.signals import user_registered
from allianceauth.eveonline.models import EveCharacter
from .models import CharacterOwnership
from .core.celery_workers import active_tasks_count, queued_tasks_count
from .forms import RegistrationForm
from .models import CharacterOwnership
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
@@ -61,6 +61,7 @@ def dashboard(request):
}
return render(request, 'authentication/dashboard.html', context)
@login_required
def token_management(request):
tokens = request.user.token_set.all()
@@ -70,6 +71,7 @@ def token_management(request):
}
return render(request, 'authentication/tokens.html', context)
@login_required
def token_delete(request, token_id=None):
try:
@@ -83,6 +85,7 @@ def token_delete(request, token_id=None):
messages.warning(request, "Token does not exist")
return redirect('authentication:token_management')
@login_required
def token_refresh(request, token_id=None):
try:
@@ -127,7 +130,7 @@ def main_character_change(request, token):
def add_character(request, token):
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
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:
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
return redirect('authentication:dashboard')
@@ -268,8 +271,11 @@ class ActivationView(BaseActivationView):
def validate_key(self, activation_key):
try:
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
dump = signing.loads(
activation_key,
salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400
)
return dump
except signing.BadSignature:
return None
@@ -299,3 +305,12 @@ def activation_complete(request):
def registration_closed(request):
messages.error(request, _('Registration of new accounts is not allowed at this time.'))
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

@@ -14,6 +14,7 @@ def sync_user_groups(modeladmin, request, queryset):
agc.update_all_states_group_membership()
@admin.register(AutogroupsConfig)
class AutogroupsConfigAdmin(admin.ModelAdmin):
formfield_overrides = {
models.CharField: {'strip': False}
@@ -36,6 +37,5 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
return actions
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
admin.site.register(ManagedCorpGroup)
admin.site.register(ManagedAllianceGroup)

0
allianceauth/eveonline/views.py Executable file → Normal file
View File

View File

@@ -1,6 +1,10 @@
import functools
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.db.models.functions import Lower
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -8,6 +12,39 @@ from .models import ReservedGroupName
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):
my_name = self.cleaned_data['name']
if ReservedGroupName.objects.filter(name__iexact=my_name).exists():

View File

@@ -1,8 +1,7 @@
from typing import Set
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.contrib.auth.models import Group, User
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -14,7 +13,7 @@ from allianceauth.notifications import notify
class GroupRequest(models.Model):
"""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)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
@@ -49,7 +48,7 @@ class RequestLog(models.Model):
request_type = models.BooleanField(null=True)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
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)
date = models.DateTimeField(auto_now_add=True)

View File

@@ -29,7 +29,7 @@
</a>
</li>
{% if not auto_leave %}
{% if not show_leave_tab %}
<li>
<a data-toggle="tab" href="#leave">
{% translate "Leave Requests" %}
@@ -102,7 +102,7 @@
{% endif %}
</div>
{% if not auto_leave %}
{% if not show_leave_tab %}
<div id="leave" class="tab-pane">
{% if leaverequests %}
<div class="table-responsive">

View File

@@ -6,22 +6,22 @@ from django.conf import settings
from django.contrib import admin
from django.contrib.admin.sites import AdminSite
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.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo
EveAllianceInfo, EveCharacter, EveCorporationInfo,
)
from allianceauth.tests.auth_utils import AuthUtils
from . import get_admin_change_view_url
from ..admin import HasLeaderFilter, GroupAdmin, Group
from ..admin import Group, GroupAdmin, HasLeaderFilter
from ..models import ReservedGroupName
from . import get_admin_change_view_url
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
from ..admin import IsAutoGroupFilter
else:
_has_auto_groups = False
@@ -621,21 +621,16 @@ class TestGroupAdmin2(TestCase):
response = self.client.post(
f"/admin/groupmanagement/group/{group.pk}/change/",
data={
"name": f"{group.name}",
"authgroup-TOTAL_FORMS": "1",
"authgroup-INITIAL_FORMS": "1",
"authgroup-MIN_NUM_FORMS": "0",
"authgroup-MAX_NUM_FORMS": "1",
"authgroup-0-description": "",
"authgroup-0-states": f"{member_state.pk}",
"name": group.name,
"users": [user_member.pk, user_guest.pk],
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 1,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
"authgroup-0-states": member_state.pk,
"authgroup-0-internal": "on",
"authgroup-0-hidden": "on",
"authgroup-0-group": f"{group.pk}",
"authgroup-__prefix__-description": "",
"authgroup-__prefix__-internal": "on",
"authgroup-__prefix__-hidden": "on",
"authgroup-__prefix__-group": f"{group.pk}",
"_save": "Save"
"authgroup-0-group": group.pk,
}
)
# then
@@ -644,6 +639,85 @@ class TestGroupAdmin2(TestCase):
self.assertIn(group, user_member.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):
@classmethod

View File

@@ -1,6 +1,7 @@
from django.test import RequestFactory, TestCase, override_settings
from django.urls import reverse
from allianceauth.groupmanagement.models import Group, GroupRequest
from allianceauth.tests.auth_utils import AuthUtils
from .. import views
@@ -16,6 +17,7 @@ class TestViews(TestCase):
self.factory = RequestFactory()
self.user = AuthUtils.create_user('Peter Parker')
self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne')
self.group = Group.objects.create(name="Example group")
# set permissions
AuthUtils.add_permission_to_user_by_name(
@@ -83,3 +85,19 @@ class TestViews(TestCase):
self.assertEqual(response.status_code, 200)
self.assertNotIn('<a data-toggle="tab" href="#leave">', 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)

13
allianceauth/groupmanagement/views.py Executable file → Normal file
View File

@@ -2,13 +2,12 @@ import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Count
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 allianceauth.notifications import notify
@@ -16,7 +15,6 @@ from allianceauth.notifications import notify
from .managers import GroupManager
from .models import GroupRequest, RequestLog
logger = logging.getLogger(__name__)
@@ -45,10 +43,15 @@ def group_management(request):
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
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 = {
'acceptrequests': acceptrequests,
'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)

2
allianceauth/hrapplications/admin.py Executable file → Normal file
View File

@@ -10,6 +10,7 @@ class ChoiceInline(admin.TabularInline):
verbose_name_plural = 'Choices (optional)'
verbose_name= 'Choice'
@admin.register(ApplicationQuestion)
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['title', 'help_text', 'multi_select']}),
@@ -18,6 +19,5 @@ class QuestionAdmin(admin.ModelAdmin):
admin.site.register(Application)
admin.site.register(ApplicationComment)
admin.site.register(ApplicationQuestion, QuestionAdmin)
admin.site.register(ApplicationForm)
admin.site.register(ApplicationResponse)

0
allianceauth/hrapplications/forms.py Executable file → Normal file
View File

0
allianceauth/hrapplications/models.py Executable file → Normal file
View File

0
allianceauth/hrapplications/views.py Executable file → Normal file
View File

View File

@@ -4,9 +4,9 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2020
# Joel Falknau <ozirascal@gmail.com>, 2021
# Peter Pfeufer, 2022
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# Peter Pfeufer, 2023
#
#, fuzzy
msgid ""
@@ -14,8 +14,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Peter Pfeufer, 2022\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Peter Pfeufer, 2023\n"
"Language-Team: German (https://app.transifex.com/alliance-auth/teams/107430/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -34,7 +34,8 @@ msgstr "Google Analytics V4"
#: allianceauth/authentication/decorators.py:37
msgid "A main character is required to perform that action. Add one below."
msgstr ""
"Für diese Aktion wird ein Hauptcharacter benötigt. Bitte füge einen hinzu."
"Zur Ausführung dieser Aktion ist ein Hauptcharakter erforderlich. Füge unten"
" einen hinzu."
#: allianceauth/authentication/forms.py:12
msgid "Email"
@@ -131,7 +132,7 @@ msgstr "Hauptcharakter ändern"
#: allianceauth/authentication/templates/authentication/dashboard.html:125
msgid "Group Memberships"
msgstr "Gruppen"
msgstr "Gruppenmitgliedschaften"
#: allianceauth/authentication/templates/authentication/dashboard.html:145
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:21
@@ -206,7 +207,7 @@ msgstr ""
#: allianceauth/authentication/views.py:83
#, python-format
msgid "Changed main character to %(char)s"
msgstr "Haupcharakter geändert zu %(char)s"
msgstr "Haupcharakter zu %(char)s geändert"
#: allianceauth/authentication/views.py:92
#, python-format
@@ -233,13 +234,12 @@ msgid ""
"Sent confirmation email. Please follow the link to confirm your email "
"address."
msgstr ""
"Bestätigungsmail gesendet. Bitte folge dem Link in der E-Mail zur "
"Bestätigung."
"Bestätigungs-E-Mail gesendet. Bitte folge dem Link, um Deine E-Mail-Adresse "
"zu bestätigen."
#: allianceauth/authentication/views.py:257
msgid "Confirmed your email address. Please login to continue."
msgstr ""
"Deine E-Mail Adresse wurde bestätigt. Bitte log Dich ein um fortzufahren."
msgstr "Deine E-Mail Adresse wurde bestätigt. Bitte einloggen zum Fortfahren."
#: allianceauth/authentication/views.py:262
msgid "Registration of new accounts is not allowed at this time."
@@ -274,7 +274,7 @@ msgstr "Hauptcharaktere"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:22
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:14
msgid "Members"
msgstr "Mitgliederzahl"
msgstr "Mitglieder"
#: allianceauth/corputils/templates/corputils/corpstats.html:35
msgid "Unregistered"
@@ -282,7 +282,7 @@ msgstr "Nicht registriert"
#: allianceauth/corputils/templates/corputils/corpstats.html:38
msgid "Last update:"
msgstr "Letzes Update:"
msgstr "Letzte Aktualisierung:"
#: allianceauth/corputils/templates/corputils/corpstats.html:74
#: allianceauth/corputils/templates/corputils/corpstats.html:112
@@ -382,11 +382,11 @@ msgstr "Charakter nicht registriert!"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
msgid "This character is not associated with an auth account."
msgstr "Dieser Charakter ist mit keinen Auth Konto verbunden."
msgstr "Dieser Charakter ist keinem Auth Konto zugeordnet."
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
msgid "Add it here"
msgstr "Füge es hier hinzu"
msgstr "Füge ihn hier hinzu"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
msgid "before attempting to click fleet attendance links."
@@ -394,7 +394,7 @@ msgstr "bevor Du versuchst auf FAT-Links zu klicken."
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:5
msgid "Create Fatlink"
msgstr "Erstelle FAT-Link"
msgstr "FAT-Link erstellen"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:9
#: allianceauth/optimer/templates/optimer/add.html:13
@@ -409,20 +409,20 @@ msgstr "Fehlerhafte Anfrage!"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:63
msgid "Create fatlink"
msgstr "Erstelle FAT-Link"
msgstr "FAT-Link erstellen"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:3
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:4
msgid "Fatlink view"
msgstr "FAT-Link sehen"
msgstr "FAT-Link ansehen"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:7
msgid "Edit fatlink"
msgstr "Editiere FAT-Link"
msgstr "FAT-Link bearbeiten"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:11
msgid "Delete fat"
msgstr "Lösche FAT"
msgstr "FAT löschen"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:17
msgid "Registered characters"
@@ -497,7 +497,7 @@ msgstr[1] "%(user)s hat diesen Monat %(links)s FAT-Links eingesammelt."
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:26
msgid "Times used"
msgstr "male genutzt"
msgstr "Wie oft genutzt"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:37
#, python-format
@@ -570,7 +570,7 @@ msgstr "FAT-Link Statistik"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:20
msgid "Ticker"
msgstr "Ticker: "
msgstr "Ticker"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:8
msgid "Participation data"
@@ -594,7 +594,7 @@ msgstr "Letzter FAT-Link"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:58
msgid "View statistics"
msgstr "Statistik"
msgstr "Statistiken ansehen"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:95
msgid "No created fatlinks on record."
@@ -713,8 +713,8 @@ msgid ""
"States listed here will have the ability to join this group provided they "
"have the proper permissions.<br>"
msgstr ""
"Hier gelistete Ränge können dieser Gruppe beitreten, vorausgesetzt sie haben"
" die entsprechenden Berechtigungen.<br>"
"Die hier aufgeführten Status können dieser Gruppe beitreten, sofern sie über"
" die entsprechenden Berechtigungen verfügen.<br>"
#: allianceauth/groupmanagement/models.py:171
msgid ""
@@ -814,7 +814,7 @@ msgstr "Gruppenmitglieder"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:113
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:21
msgid "Organization"
msgstr "Organization"
msgstr "Organisation"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:49
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:75
@@ -933,18 +933,18 @@ msgstr "Gruppenverwaltung"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
msgid "Join Requests"
msgstr "Beitrittsgesuche"
msgstr "Beitrittsanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:35
msgid "Leave Requests"
msgstr "Austrittsgesuche"
msgstr "Austrittsanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:57
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:114
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:18
#: allianceauth/services/modules/openfire/forms.py:6
msgid "Group"
msgstr "Gruppen"
msgstr "Gruppe"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:145
@@ -968,7 +968,7 @@ msgstr "Keine Gruppenaustrittsanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:8
#: allianceauth/templates/allianceauth/top-menu.html:8
msgid "Toggle navigation"
msgstr "Toggle Navigation"
msgstr "Navigation umschalten"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:19
msgid "Group Requests"
@@ -994,7 +994,7 @@ msgstr "Gruppe existiert nicht"
#: allianceauth/groupmanagement/views.py:195
#, python-format
msgid "Accepted application from %(mainchar)s to %(group)s."
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s zugestimmt."
msgstr "Beitrittsanfrage von %(mainchar)s zur Gruppe %(group)s akzeptiert."
#: allianceauth/groupmanagement/views.py:201
#: allianceauth/groupmanagement/views.py:232
@@ -1003,18 +1003,18 @@ msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to %(group)s."
msgstr ""
"Bei der Bearbeitung des Beitrittsgesuchs von %(mainchar)s zur Gruppe "
"Bei der Bearbeitung des Beitrittsanfrage von %(mainchar)s zur Gruppe "
"%(group)s ist ein unbehandelter Fehler aufgetreten."
#: allianceauth/groupmanagement/views.py:226
#, python-format
msgid "Rejected application from %(mainchar)s to %(group)s."
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s abgelehnt."
msgstr "Beitrittsanfrage von %(mainchar)s zur Gruppe %(group)s abgelehnt."
#: allianceauth/groupmanagement/views.py:261
#, python-format
msgid "Accepted application from %(mainchar)s to leave %(group)s."
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s akzeptiert."
msgstr "Austrittsanfrage von %(mainchar)s für Gruppe %(group)s akzeptiert."
#: allianceauth/groupmanagement/views.py:266
#: allianceauth/groupmanagement/views.py:298
@@ -1023,13 +1023,13 @@ msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to leave %(group)s."
msgstr ""
"Bei der Bearbeitung des Austrittsgesuchs von %(mainchar)s für Gruppe "
"Bei der Bearbeitung des Austrittsanfrage von %(mainchar)s für Gruppe "
"%(group)s ist ein unbehandelter Fehler aufgetreten."
#: allianceauth/groupmanagement/views.py:292
#, python-format
msgid "Rejected application from %(mainchar)s to leave %(group)s."
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s abgelehnt."
msgstr "Austrittsanfrage von %(mainchar)s für Gruppe %(group)s abgelehnt."
#: allianceauth/groupmanagement/views.py:336
#: allianceauth/groupmanagement/views.py:346
@@ -1042,7 +1042,7 @@ msgstr "Du bist bereits Mitglied dieser Gruppe."
#: allianceauth/groupmanagement/views.py:358
msgid "You already have a pending application for that group."
msgstr "Du hast Dich bereits für diese Gruppe beworben."
msgstr "Du hast bereits für diese Gruppe angefragt."
#: allianceauth/groupmanagement/views.py:367
#, python-format
@@ -1059,12 +1059,12 @@ msgstr "Du bist kein Mitglied dieser Gruppe"
#: allianceauth/groupmanagement/views.py:393
msgid "You already have a pending leave request for that group."
msgstr "Du hast bereits ein ausstehendes Austrittsgesuch für diese Gruppe."
msgstr "Du hast bereits eine ausstehendes Austrittsanfrage für diese Gruppe."
#: allianceauth/groupmanagement/views.py:409
#, python-format
msgid "Applied to leave group %(group)s."
msgstr "Austrittsgesuch für Gruppe %(group)s gesendet."
msgstr "Austrittsanfrage für Gruppe %(group)s gesendet."
#: allianceauth/hrapplications/auth_hooks.py:14
msgid "Applications"
@@ -1086,11 +1086,11 @@ msgstr "Wähle eine Corporation"
#: allianceauth/hrapplications/templates/hrapplications/corpchoice.html:10
msgid "Available Corps"
msgstr "Zur Auswahl stehende Corporations"
msgstr "Verfügbare Corporationen"
#: allianceauth/hrapplications/templates/hrapplications/corpchoice.html:22
msgid "No corps are accepting applications at this time."
msgstr "Zur Zeit nimmt keine Corp Bewerbungen entgegen."
msgstr "Zur Zeit nimmt keine Corp Bewerbungen an."
#: allianceauth/hrapplications/templates/hrapplications/create.html:4
#: allianceauth/hrapplications/templates/hrapplications/create.html:7
@@ -1186,7 +1186,7 @@ msgstr "Keine angesehenen Bewerbungen"
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:62
#: allianceauth/hrapplications/templates/hrapplications/view.html:134
msgid "Close"
msgstr "Geschlossen"
msgstr "Schließen"
#: allianceauth/hrapplications/templates/hrapplications/management.html:177
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:63
@@ -1200,7 +1200,7 @@ msgstr "Suche"
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:11
msgid "Application Search Results"
msgstr "Bewerbungen Suchergebnisse"
msgstr "Ergebnisse der Bewerbungssuche"
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:22
msgid "Application ID"
@@ -1342,12 +1342,12 @@ msgstr "Operationsart"
#: allianceauth/optimer/form.py:17
#: allianceauth/srp/templates/srp/management.html:38
msgid "Fleet Commander"
msgstr "Flottenkommandeur"
msgstr "Flottenkommandant"
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
#: allianceauth/srp/templates/srp/data.html:91
msgid "Additional Info"
msgstr "Zusätzliche Info"
msgstr "Zusätzliche Informationen"
#: allianceauth/optimer/form.py:23
msgid "(Optional) Describe the operation with a couple of short words."
@@ -1360,7 +1360,7 @@ msgstr "Operation erstellen"
#: allianceauth/optimer/templates/optimer/fleetoptable.html:12
msgid "Form Up System"
msgstr "Form Up System"
msgstr "Startsystem"
#: allianceauth/optimer/templates/optimer/fleetoptable.html:14
#: allianceauth/timerboard/templates/timerboard/view.html:36
@@ -1384,20 +1384,20 @@ msgstr "Flottenoperationen Zeiten"
#: allianceauth/optimer/templates/optimer/management.html:20
#: allianceauth/timerboard/templates/timerboard/view.html:22
msgid "Current Eve Time:"
msgstr "Momentane Eve Zeit"
msgstr "Aktuelle Eve Zeit"
#: allianceauth/optimer/templates/optimer/management.html:26
msgid "Next Fleet Operations"
msgstr "Anstehende Flottenoperationen"
msgstr "Anstehende Flotten"
#: allianceauth/optimer/templates/optimer/management.html:30
#: allianceauth/timerboard/templates/timerboard/view.html:362
msgid "No upcoming timers."
msgstr "Keine kommenden Timer."
msgstr "Keine bevorstehenden Timer."
#: allianceauth/optimer/templates/optimer/management.html:33
msgid "Past Fleet Operations"
msgstr "Vergangene Flottenoperationen"
msgstr "Vergangene Flotten"
#: allianceauth/optimer/templates/optimer/management.html:37
#: allianceauth/timerboard/templates/timerboard/view.html:535
@@ -1408,7 +1408,7 @@ msgstr "Keine vergangenen Timer."
#: allianceauth/optimer/templates/optimer/update.html:15
#: allianceauth/optimer/templates/optimer/update.html:27
msgid "Update Fleet Operation"
msgstr "Aktualisiere Flottenoperationen"
msgstr "Aktualisiere Flottenoperation"
#: allianceauth/optimer/templates/optimer/update.html:21
msgid "Fleet Operation Does Not Exist"
@@ -1432,7 +1432,7 @@ msgstr "Änderungen für Operation timer %(opname)s gespeichert."
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:4
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:8
msgid "Permissions Audit"
msgstr "Berechtigungsübersicht"
msgstr "Berechtigungsprüfung"
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
msgid "User / Character"
@@ -1494,11 +1494,11 @@ msgstr "Dienste"
#: allianceauth/services/forms.py:6
msgid "Name of Fleet:"
msgstr "SRP Flotte erstellen:"
msgstr "Name der Flotte:"
#: allianceauth/services/forms.py:7
msgid "Fleet Commander:"
msgstr "Flottenkommandeur:"
msgstr "Flottenkommandant:"
#: allianceauth/services/forms.py:8
msgid "Fleet Comms:"
@@ -1514,11 +1514,11 @@ msgstr "Schiffspriorität:"
#: allianceauth/services/forms.py:11
msgid "Formup Location:"
msgstr "Formup Location:"
msgstr "Startsystem:"
#: allianceauth/services/forms.py:12
msgid "Formup Time:"
msgstr "Formup Zeit:"
msgstr "Startzeit:"
#: allianceauth/services/forms.py:13
msgid "Expected Duration:"
@@ -1530,7 +1530,7 @@ msgstr "Grund:"
#: allianceauth/services/forms.py:15
msgid "Reimbursable?*"
msgstr "Erstattungsfähig?"
msgstr "Erstattungsfähig?*"
#: allianceauth/services/forms.py:15 allianceauth/services/forms.py:16
msgid "Yes"
@@ -1542,7 +1542,7 @@ msgstr "Nein"
#: allianceauth/services/forms.py:16
msgid "Important?*"
msgstr "Wichtig?"
msgstr "Wichtig?*"
#: allianceauth/services/forms.py:21 allianceauth/services/forms.py:31
msgid "Password"
@@ -1550,7 +1550,7 @@ msgstr "Passwort"
#: allianceauth/services/forms.py:26 allianceauth/services/forms.py:36
msgid "Password must be at least 8 characters long."
msgstr "Passwort muss mindestens 8 Zeichen lang sein"
msgstr "Das Passwort muss mindestens 8 Zeichen lang sein"
#: allianceauth/services/modules/discord/models.py:187
msgid "Discord Account Disabled"
@@ -1591,7 +1591,7 @@ msgstr "Discord Konto deaktiviert."
#: allianceauth/services/modules/discord/views.py:36
#: allianceauth/services/modules/discord/views.py:59
msgid "An error occurred while processing your Discord account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Discord Kontos."
msgstr "Es gab einen Fehler während der Verarbeitung Deines Discord Kontos."
#: allianceauth/services/modules/discord/views.py:102
msgid "Your Discord account has been successfully activated."
@@ -1607,7 +1607,7 @@ msgstr ""
#: allianceauth/services/modules/discourse/views.py:29
msgid "You are not authorized to access Discourse."
msgstr "Du bist nicht autorisiert auf Discorse zuzugreifen."
msgstr "Du bist nicht autorisiert auf Discourse zuzugreifen."
#: allianceauth/services/modules/discourse/views.py:34
msgid "You must have a main character set to access Discourse."
@@ -1619,14 +1619,14 @@ msgid ""
"No SSO payload or signature. Please contact support if this problem "
"persists."
msgstr ""
"Keine SSO-Nutzdaten oder Signaturen. Bitte wenden Sie sich an den Support, "
"wenn das Problem weiterhin besteht."
"Keine SSO-Nutzdaten oder Signaturen. Bitte wende Dich an den Support, wenn "
"das Problem weiterhin besteht."
#: allianceauth/services/modules/discourse/views.py:54
#: allianceauth/services/modules/discourse/views.py:62
msgid "Invalid payload. Please contact support if this problem persists."
msgstr ""
"Ungültige Nutzdaten. Bitte wenden Sie sich an den Support, wenn das Problem "
"Ungültige Nutzdaten. Bitte wenden Dich an den Support, wenn das Problem "
"weiterhin besteht."
#: allianceauth/services/modules/ips4/views.py:31
@@ -1638,7 +1638,7 @@ msgstr "IP4Suite Konto aktiviert."
#: allianceauth/services/modules/ips4/views.py:81
#: allianceauth/services/modules/ips4/views.py:101
msgid "An error occurred while processing your IPSuite4 account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines IPSuite4 Kontos."
msgstr "Es gab einen Fehler während der Verarbeitung Deines IPSuite4 Kontos."
#: allianceauth/services/modules/ips4/views.py:52
msgid "Reset IPSuite4 password."
@@ -1660,7 +1660,7 @@ msgstr "Jabber"
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:5
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:10
msgid "Jabber Broadcast"
msgstr "Jabber Übertragung"
msgstr "Jabber Ankündigung"
#: allianceauth/services/modules/openfire/auth_hooks.py:94
msgid "Fleet Broadcast Formatter"
@@ -1672,11 +1672,11 @@ msgstr "Nachricht"
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:16
msgid "Broadcast Sent!!"
msgstr "Übertragung gesendet!!"
msgstr "Ankündigung gesendet!!"
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:22
msgid "Broadcast"
msgstr "Übertragungen"
msgstr "Ankündigung"
#: allianceauth/services/modules/openfire/views.py:35
msgid "Activated jabber account."
@@ -1687,7 +1687,7 @@ msgstr "Jabber Konto aktiviert."
#: allianceauth/services/modules/openfire/views.py:76
#: allianceauth/services/modules/openfire/views.py:147
msgid "An error occurred while processing your jabber account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Jabber Kontos."
msgstr "Es gab einen Fehler während der Verarbeitung Deines Jabber Kontos."
#: allianceauth/services/modules/openfire/views.py:69
msgid "Reset jabber password."
@@ -1696,7 +1696,7 @@ msgstr "Jabber Passwort zurücksetzen."
#: allianceauth/services/modules/openfire/views.py:115
#, python-format
msgid "Sent jabber broadcast to %s"
msgstr "Sende Jabber Durchsage an %s"
msgstr "Sende Jabber Ankündigung an %s"
#: allianceauth/services/modules/openfire/views.py:144
msgid "Set jabber password."
@@ -1711,7 +1711,7 @@ msgstr "Forum Konto aktiviert."
#: allianceauth/services/modules/phpbb3/views.py:78
#: allianceauth/services/modules/phpbb3/views.py:101
msgid "An error occurred while processing your forum account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Forum Kontos."
msgstr "Es gab einen Fehler während der Verarbeitung Deines Forum Kontos."
#: allianceauth/services/modules/phpbb3/views.py:53
msgid "Deactivated forum account."
@@ -1734,7 +1734,7 @@ msgstr "SMF Konto aktiviert."
#: allianceauth/services/modules/smf/views.py:102
#: allianceauth/services/modules/smf/views.py:124
msgid "An error occurred while processing your SMF account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines SMF Kontos."
msgstr "Es gab einen Fehler während der Verarbeitung Deines SMF Kontos."
#: allianceauth/services/modules/smf/views.py:78
msgid "Deactivated SMF account."
@@ -1751,7 +1751,7 @@ msgstr "Setze SMF Passwort."
#: allianceauth/services/modules/teamspeak3/forms.py:14
#, python-format
msgid "Unable to locate user %s on server"
msgstr "Kann den Benutzer %s auf dem Server nicht finden"
msgstr "Der Benutzer %s konnte auf dem Server nicht gefunden werden"
#: allianceauth/services/modules/teamspeak3/templates/admin/teamspeak3/authts/change_list.html:8
msgid "Update TS3 groups"
@@ -1783,7 +1783,8 @@ msgstr "TeamSpeak3 Konto aktiviert."
#: allianceauth/services/modules/teamspeak3/views.py:74
#: allianceauth/services/modules/teamspeak3/views.py:100
msgid "An error occurred while processing your TeamSpeak3 account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines TeamSpeak3 Kontos."
msgstr ""
"Es gab einen Fehler während der Verarbeitung Deines TeamSpeak3 Kontos."
#: allianceauth/services/modules/teamspeak3/views.py:71
msgid "Deactivated TeamSpeak3 account."
@@ -1802,7 +1803,7 @@ msgstr "XenForo Konto aktiviert."
#: allianceauth/services/modules/xenforo/views.py:73
#: allianceauth/services/modules/xenforo/views.py:94
msgid "An error occurred while processing your XenForo account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines XenForo Kontos."
msgstr "Es gab einen Fehler während der Verarbeitung Deines XenForo Kontos."
#: allianceauth/services/modules/xenforo/views.py:50
msgid "Deactivated XenForo account."
@@ -1832,7 +1833,7 @@ msgstr "Formatieren"
#: allianceauth/services/templates/services/service_confirm_delete.html:12
#, python-format
msgid "Delete %(service_name)s Account?"
msgstr "Konto %(service_name)s löschen?"
msgstr " %(service_name)s Konto löschen?"
#: allianceauth/services/templates/services/service_confirm_delete.html:20
#, python-format
@@ -1856,7 +1857,7 @@ msgstr "%(service_name)s Passwort ändern"
#: allianceauth/services/templates/services/service_password.html:9
#, python-format
msgid "Set %(service_name)s Password"
msgstr "%(service_name)s Passwort"
msgstr "%(service_name)s Passwort setzen"
#: allianceauth/services/templates/services/service_password.html:17
msgid "Set Password"
@@ -1927,7 +1928,7 @@ msgstr "SRP Flotten Daten"
#: allianceauth/srp/templates/srp/data.html:50
msgid "SRP Fleet Data"
msgstr "SRP-Flotte Daten"
msgstr "SRP Flotte Daten"
#: allianceauth/srp/templates/srp/data.html:55
msgid "Mark Incomplete"
@@ -2005,7 +2006,7 @@ msgstr "Füge SRP Flotte hinzu"
#: allianceauth/srp/templates/srp/management.html:39
msgid "Fleet AAR"
msgstr "Flotten AAR"
msgstr "Flottenbericht"
#: allianceauth/srp/templates/srp/management.html:40
msgid "Fleet SRP Code"
@@ -2033,7 +2034,7 @@ msgstr "Deaktiviert"
#: allianceauth/srp/templates/srp/management.html:83
msgid "Completed"
msgstr "Fertig"
msgstr "Abgeschlossen"
#: allianceauth/srp/templates/srp/management.html:101
msgid "Are you sure you want to delete this SRP code and its contents?"
@@ -2086,7 +2087,7 @@ msgstr "SRP Link für %(fleetname)s aktiviert."
#: allianceauth/srp/views.py:140
#, python-format
msgid "Marked SRP fleet %(fleetname)s as completed."
msgstr "SRP Flotte %(fleetname)s als vollständig markiert."
msgstr "SRP Flotte %(fleetname)s als abgeschlossen markiert."
#: allianceauth/srp/views.py:153
#, python-format
@@ -2204,7 +2205,7 @@ msgstr "Testversion verfügbar"
#: allianceauth/templates/allianceauth/admin-status/overview.html:78
msgid "Task Queue"
msgstr "Warteschlange"
msgstr "Task-Warteschlange"
#: allianceauth/templates/allianceauth/admin-status/overview.html:81
#, python-format
@@ -2249,7 +2250,7 @@ msgstr "Ausloggen"
#: allianceauth/timerboard/form.py:53
msgid "Other"
msgstr "anderes"
msgstr "Anderes"
#: allianceauth/timerboard/form.py:54
#: allianceauth/timerboard/templates/timerboard/view.html:62
@@ -2353,7 +2354,7 @@ msgstr "Timer löschen"
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:19
#, python-format
msgid "Are you sure you want to delete timer \"%(object)s\"?"
msgstr "Bist Du sicher das Du Timer \"%(object)s\" löschen möchtest?"
msgstr "Bist Du sicher das Du Timer %(object)s löschen möchtest?"
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:5
#: allianceauth/timerboard/templates/timerboard/timer_create_form.html:13

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"POT-Creation-Date: 2023-11-08 23:55+1000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -26,7 +26,7 @@ msgstr ""
msgid "Google Analytics V4"
msgstr ""
#: allianceauth/authentication/decorators.py:37
#: allianceauth/authentication/decorators.py:49
msgid "A main character is required to perform that action. Add one below."
msgstr ""
@@ -39,63 +39,68 @@ msgstr ""
msgid "You are not allowed to add or remove these restricted groups: %s"
msgstr ""
#: allianceauth/authentication/models.py:80
#: allianceauth/authentication/models.py:71
msgid "English"
msgstr ""
#: allianceauth/authentication/models.py:81
#: allianceauth/authentication/models.py:72
msgid "German"
msgstr ""
#: allianceauth/authentication/models.py:82
#: allianceauth/authentication/models.py:73
msgid "Spanish"
msgstr ""
#: allianceauth/authentication/models.py:83
#: allianceauth/authentication/models.py:74
msgid "Chinese Simplified"
msgstr ""
#: allianceauth/authentication/models.py:84
#: allianceauth/authentication/models.py:75
msgid "Russian"
msgstr ""
#: allianceauth/authentication/models.py:85
#: allianceauth/authentication/models.py:76
msgid "Korean"
msgstr ""
#: allianceauth/authentication/models.py:86
#: allianceauth/authentication/models.py:77
msgid "French"
msgstr ""
#: allianceauth/authentication/models.py:87
#: allianceauth/authentication/models.py:78
msgid "Japanese"
msgstr ""
#: allianceauth/authentication/models.py:88
#: allianceauth/authentication/models.py:79
msgid "Italian"
msgstr ""
#: allianceauth/authentication/models.py:91
msgid "Language"
#: allianceauth/authentication/models.py:80
msgid "Ukrainian"
msgstr ""
#: allianceauth/authentication/models.py:96
msgid "Language"
msgstr ""
#: allianceauth/authentication/models.py:101
#: allianceauth/templates/allianceauth/night-toggle.html:6
msgid "Night Mode"
msgstr ""
#: allianceauth/authentication/models.py:110
#: allianceauth/authentication/models.py:115
#, python-format
msgid "State changed to: %s"
msgstr ""
#: allianceauth/authentication/models.py:111
#: allianceauth/authentication/models.py:116
#, python-format
msgid "Your user's state is now: %(state)s"
msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard.html:4
#: allianceauth/authentication/templates/authentication/dashboard.html:7
#: allianceauth/authentication/templates/authentication/tokens.html:4
#: allianceauth/templates/allianceauth/side-menu.html:10
msgid "Dashboard"
msgstr ""
@@ -151,8 +156,49 @@ msgstr ""
msgid "Alliance"
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/templates/allianceauth/top-menu-user-dropdown.html:58
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:69
msgid "Login"
msgstr ""
@@ -184,47 +230,47 @@ msgstr ""
msgid "Invalid or expired activation link."
msgstr ""
#: allianceauth/authentication/views.py:77
#: allianceauth/authentication/views.py:118
#, python-format
msgid ""
"Cannot change main character to %(char)s: character owned by a different "
"account."
msgstr ""
#: allianceauth/authentication/views.py:83
#: allianceauth/authentication/views.py:124
#, python-format
msgid "Changed main character to %(char)s"
msgstr ""
#: allianceauth/authentication/views.py:92
#: allianceauth/authentication/views.py:133
#, python-format
msgid "Added %(name)s to your account."
msgstr ""
#: allianceauth/authentication/views.py:94
#: allianceauth/authentication/views.py:135
#, python-format
msgid "Failed to add %(name)s to your account: they already have an account."
msgstr ""
#: allianceauth/authentication/views.py:133
#: allianceauth/authentication/views.py:174
msgid "Unable to authenticate as the selected character."
msgstr ""
#: allianceauth/authentication/views.py:197
#: allianceauth/authentication/views.py:238
msgid "Registration token has expired."
msgstr ""
#: allianceauth/authentication/views.py:252
#: allianceauth/authentication/views.py:296
msgid ""
"Sent confirmation email. Please follow the link to confirm your email "
"address."
msgstr ""
#: allianceauth/authentication/views.py:257
#: allianceauth/authentication/views.py:301
msgid "Confirmed your email address. Please login to continue."
msgstr ""
#: allianceauth/authentication/views.py:262
#: allianceauth/authentication/views.py:306
msgid "Registration of new accounts is not allowed at this time."
msgstr ""
@@ -267,19 +313,6 @@ msgstr ""
msgid "Last update:"
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/search.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
@@ -611,36 +644,41 @@ msgstr ""
msgid "Group Management"
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."
msgstr ""
#: allianceauth/groupmanagement/forms.py:25
#: allianceauth/groupmanagement/forms.py:62
msgid "(auto)"
msgstr ""
#: allianceauth/groupmanagement/forms.py:34
#: allianceauth/groupmanagement/forms.py:71
msgid "There already exists a group with that name."
msgstr ""
#: allianceauth/groupmanagement/models.py:105
#: allianceauth/groupmanagement/models.py:104
msgid ""
"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 "
"Hidden and Open options when selected.</b>"
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."
msgstr ""
#: allianceauth/groupmanagement/models.py:119
#: allianceauth/groupmanagement/models.py:118
msgid ""
"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."
msgstr ""
#: allianceauth/groupmanagement/models.py:126
#: allianceauth/groupmanagement/models.py:125
msgid ""
"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 "
@@ -648,65 +686,65 @@ msgid ""
"authenticated."
msgstr ""
#: allianceauth/groupmanagement/models.py:135
#: allianceauth/groupmanagement/models.py:134
msgid ""
"Group is restricted. This means that adding or removing users for this group "
"requires a superuser admin."
msgstr ""
#: allianceauth/groupmanagement/models.py:144
#: allianceauth/groupmanagement/models.py:143
msgid ""
"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>"
msgstr ""
#: allianceauth/groupmanagement/models.py:154
#: allianceauth/groupmanagement/models.py:153
msgid ""
"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 "
"groups.<br>"
msgstr ""
#: allianceauth/groupmanagement/models.py:163
#: allianceauth/groupmanagement/models.py:162
msgid ""
"States listed here will have the ability to join this group provided they "
"have the proper permissions.<br>"
msgstr ""
#: allianceauth/groupmanagement/models.py:171
#: allianceauth/groupmanagement/models.py:170
msgid ""
"Short description <i>(max. 512 characters)</i> of the group shown to users."
msgstr ""
#: allianceauth/groupmanagement/models.py:178
#: allianceauth/groupmanagement/models.py:177
msgid "Can request non-public groups"
msgstr ""
#: allianceauth/groupmanagement/models.py:209
#: allianceauth/groupmanagement/models.py:208
msgid "name"
msgstr ""
#: allianceauth/groupmanagement/models.py:212
#: allianceauth/groupmanagement/models.py:211
msgid "Name that can not be used for groups."
msgstr ""
#: allianceauth/groupmanagement/models.py:215
#: allianceauth/groupmanagement/models.py:214
msgid "reason"
msgstr ""
#: allianceauth/groupmanagement/models.py:215
#: allianceauth/groupmanagement/models.py:214
msgid "Reason why this name is reserved."
msgstr ""
#: allianceauth/groupmanagement/models.py:218
#: allianceauth/groupmanagement/models.py:217
msgid "created by"
msgstr ""
#: allianceauth/groupmanagement/models.py:223
#: allianceauth/groupmanagement/models.py:222
msgid "created at"
msgstr ""
#: allianceauth/groupmanagement/models.py:223
#: allianceauth/groupmanagement/models.py:222
msgid "Date when this entry was created"
msgstr ""
@@ -933,86 +971,86 @@ msgstr ""
msgid "Group Membership"
msgstr ""
#: allianceauth/groupmanagement/views.py:163
#: allianceauth/groupmanagement/views.py:166
#, python-format
msgid "Removed user %(user)s from group %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:165
#: allianceauth/groupmanagement/views.py:168
msgid "User does not exist in that group"
msgstr ""
#: allianceauth/groupmanagement/views.py:168
#: allianceauth/groupmanagement/views.py:171
msgid "Group does not exist"
msgstr ""
#: allianceauth/groupmanagement/views.py:195
#: allianceauth/groupmanagement/views.py:198
#, python-format
msgid "Accepted application from %(mainchar)s to %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:201
#: allianceauth/groupmanagement/views.py:232
#: allianceauth/groupmanagement/views.py:204
#: allianceauth/groupmanagement/views.py:235
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:226
#: allianceauth/groupmanagement/views.py:229
#, python-format
msgid "Rejected application from %(mainchar)s to %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:261
#: allianceauth/groupmanagement/views.py:264
#, python-format
msgid "Accepted application from %(mainchar)s to leave %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:266
#: allianceauth/groupmanagement/views.py:298
#: allianceauth/groupmanagement/views.py:269
#: allianceauth/groupmanagement/views.py:301
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to leave %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:292
#: allianceauth/groupmanagement/views.py:295
#, python-format
msgid "Rejected application from %(mainchar)s to leave %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:336
#: allianceauth/groupmanagement/views.py:346
#: allianceauth/groupmanagement/views.py:339
#: allianceauth/groupmanagement/views.py:349
msgid "You cannot join that group"
msgstr ""
#: allianceauth/groupmanagement/views.py:341
#: allianceauth/groupmanagement/views.py:344
msgid "You are already a member of that group."
msgstr ""
#: allianceauth/groupmanagement/views.py:358
#: allianceauth/groupmanagement/views.py:361
msgid "You already have a pending application for that group."
msgstr ""
#: allianceauth/groupmanagement/views.py:367
#: allianceauth/groupmanagement/views.py:370
#, python-format
msgid "Applied to group %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:377
#: allianceauth/groupmanagement/views.py:380
msgid "You cannot leave that group"
msgstr ""
#: allianceauth/groupmanagement/views.py:381
#: allianceauth/groupmanagement/views.py:384
msgid "You are not a member of that group"
msgstr ""
#: allianceauth/groupmanagement/views.py:393
#: allianceauth/groupmanagement/views.py:396
msgid "You already have a pending leave request for that group."
msgstr ""
#: allianceauth/groupmanagement/views.py:409
#: allianceauth/groupmanagement/views.py:412
#, python-format
msgid "Applied to leave group %(group)s."
msgstr ""
@@ -1074,16 +1112,6 @@ msgstr ""
msgid "Username"
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:99
#: allianceauth/hrapplications/templates/hrapplications/management.html:143
@@ -1422,10 +1450,6 @@ msgstr ""
msgid "Code Name"
msgstr ""
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:35
msgid "Users"
msgstr ""
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:41
msgid "States"
msgstr ""
@@ -2146,11 +2170,11 @@ msgid ""
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:95
#, python-format
msgid ""
"\n"
" %(queue_length)s queued tasks\n"
" "
msgid "running"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:96
msgid "queued"
msgstr ""
#: allianceauth/templates/allianceauth/top-menu-admin.html:9
@@ -2166,11 +2190,11 @@ msgid "AA Support Discord"
msgstr ""
#: 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"
msgstr ""
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:56
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:67
msgid "Logout"
msgstr ""
@@ -2226,22 +2250,30 @@ msgid "Objective"
msgstr ""
#: allianceauth/timerboard/form.py:64
msgid "Days Remaining"
msgid "Absolute Timer"
msgstr ""
#: allianceauth/timerboard/form.py:65
msgid "Hours Remaining"
msgid "Date and Time"
msgstr ""
#: allianceauth/timerboard/form.py:66
msgid "Days Remaining"
msgstr ""
#: allianceauth/timerboard/form.py:67
msgid "Minutes Remaining"
msgid "Hours Remaining"
msgstr ""
#: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining"
msgstr ""
#: allianceauth/timerboard/form.py:71
msgid "Important"
msgstr ""
#: allianceauth/timerboard/form.py:70
#: allianceauth/timerboard/form.py:72
msgid "Corp-Restricted"
msgstr ""

View File

@@ -4,11 +4,11 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# frank1210 <francolopez_16@hotmail.com>, 2021
# Joel Falknau <ozirascal@gmail.com>, 2021
# Young Anexo, 2023
# Fegpawn Kaundur, 2023
# frank1210 <francolopez_16@hotmail.com>, 2023
# trenus, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# Young Anexo, 2023
#
#, fuzzy
msgid ""
@@ -16,8 +16,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: trenus, 2023\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Young Anexo, 2023\n"
"Language-Team: Spanish (https://app.transifex.com/alliance-auth/teams/107430/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"

View File

@@ -4,14 +4,14 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# François LACROIX-DURANT <umbre@fallenstarscreations.com>, 2020
# Philippe Querin-Laporte <philippe.querin@hotmail.com>, 2020
# Keven D. <theenarki@gmail.com>, 2020
# Idea ., 2021
# Mickael PATTE, 2021
# Geoffrey Fabbro, 2021
# Keven D. <theenarki@gmail.com>, 2023
# rockclodbuster, 2023
# Geoffrey Fabbro, 2023
# Mohssine Daghghar, 2023
# Ludovick Fortin, 2023
# François LACROIX-DURANT <umbre@fallenstarscreations.com>, 2023
# Mickael PATTE, 2023
# Philippe Querin-Laporte <philippe.querin@hotmail.com>, 2023
# Idea ., 2023
#
#, fuzzy
msgid ""
@@ -19,8 +19,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Ludovick Fortin, 2023\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Idea ., 2023\n"
"Language-Team: French (France) (https://app.transifex.com/alliance-auth/teams/107430/fr_FR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,9 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Foch Petain <brigadier.rockforward@gmail.com>, 2020
# Foch Petain <brigadier.rockforward@gmail.com>, 2023
# kotaneko, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
#
#, fuzzy
msgid ""
@@ -13,8 +14,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: kotaneko, 2023\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Joel Falknau <ozirascal@gmail.com>, 2023\n"
"Language-Team: Japanese (https://app.transifex.com/alliance-auth/teams/107430/ja/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"

View File

@@ -4,13 +4,13 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# None None <khd1226543@gmail.com>, 2020
# Seowon Jung <seowon@hawaii.edu>, 2020
# Olgeda Choi <undead.choi@gmail.com>, 2020
# Lahty <js03js70@gmail.com>, 2020
# Joel Falknau <ozirascal@gmail.com>, 2020
# ThatRagingKid, 2022
# jackfrost, 2022
# None None <khd1226543@gmail.com>, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# Seowon Jung <seowon@hawaii.edu>, 2023
# Olgeda Choi <undead.choi@gmail.com>, 2023
# ThatRagingKid, 2023
# Lahty <js03js70@gmail.com>, 2023
# jackfrost, 2023
#
#, fuzzy
msgid ""
@@ -18,8 +18,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: jackfrost, 2022\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: jackfrost, 2023\n"
"Language-Team: Korean (Korea) (https://app.transifex.com/alliance-auth/teams/107430/ko_KR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"

View File

@@ -4,9 +4,9 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Alexander Gess <de.alex.gess@gmail.com>, 2020
# Yuriy K <thedjcooltv@gmail.com>, 2020
# Андрей Зубков <and.vareba81@gmail.com>, 2020
# Yuriy K <thedjcooltv@gmail.com>, 2023
# Андрей Зубков <and.vareba81@gmail.com>, 2023
# Alexander Gess <de.alex.gess@gmail.com>, 2023
# Filipp Chertiev <f@fzfx.ru>, 2023
#
#, fuzzy
@@ -15,7 +15,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Filipp Chertiev <f@fzfx.ru>, 2023\n"
"Language-Team: Russian (https://app.transifex.com/alliance-auth/teams/107430/ru/)\n"
"MIME-Version: 1.0\n"

View File

@@ -4,6 +4,7 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Denys Ivchenko, 2023
# Kristof Swensen, 2023
#
#, fuzzy
@@ -12,7 +13,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Kristof Swensen, 2023\n"
"Language-Team: Ukrainian (https://app.transifex.com/alliance-auth/teams/107430/uk/)\n"
"MIME-Version: 1.0\n"
@@ -32,7 +33,7 @@ msgstr "Google Analytics V4"
#: allianceauth/authentication/decorators.py:37
msgid "A main character is required to perform that action. Add one below."
msgstr ""
"Для виконання цієї дії потрібен головний персонаж. Додайте його нижче."
"Для виконання цієї дії потрібен основний персонаж. Додайте його нижче."
#: allianceauth/authentication/forms.py:12
msgid "Email"
@@ -124,7 +125,7 @@ msgstr "Додати персонажа"
#: allianceauth/authentication/templates/authentication/dashboard.html:115
msgid "Change Main"
msgstr "Змінити головного персонажа"
msgstr "Змінити основного персонажа"
#: allianceauth/authentication/templates/authentication/dashboard.html:125
msgid "Group Memberships"
@@ -352,7 +353,7 @@ msgstr "Не вдалося зібрати статистику корпорац
#: allianceauth/fleetactivitytracking/auth_hooks.py:9
msgid "Fleet Activity Tracking"
msgstr "Відстеження активності флоту"
msgstr "Відстеження активності флотів"
#: allianceauth/fleetactivitytracking/forms.py:6 allianceauth/srp/form.py:8
#: allianceauth/srp/templates/srp/management.html:35
@@ -456,7 +457,7 @@ msgstr "Корабель"
#: allianceauth/timerboard/templates/timerboard/view.html:202
#: allianceauth/timerboard/templates/timerboard/view.html:375
msgid "Eve Time"
msgstr "Час в грі"
msgstr "Ігровий час"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:33
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:36
@@ -560,16 +561,16 @@ msgstr "Fats"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:4
msgid "Fatlink Corp Statistics"
msgstr "Статистика корпорації Fatlink"
msgstr "Статистика фатів корпорації"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:24
msgid "Average fats"
msgstr "Середній показник fats"
msgstr "Середній показник фатів"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:4
msgid "Fatlink statistics"
msgstr "Статистика Fatlink"
msgstr "Статистика фатів"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:20
msgid "Ticker"
@@ -625,7 +626,7 @@ msgid ""
"Cannot register the fleet participation for {character.character_name}. The "
"character needs to be online."
msgstr ""
"Не можна зареєструвати участь в флоті для {character.character_name}. "
"Не вдалося зареєструвати участь в флоті для {character.character_name}. "
"Персонаж повинен бути в мережі."
#: allianceauth/groupmanagement/auth_hooks.py:17
@@ -659,8 +660,7 @@ msgstr ""
#: allianceauth/groupmanagement/models.py:113
msgid "Group is hidden from users but can still join with the correct link."
msgstr ""
"Група прихована від користувачів, але можна приєднатися з правильним "
"посиланням."
"Група прихована від користувачів, але можна приєднатися за посиланням."
#: allianceauth/groupmanagement/models.py:119
msgid ""
@@ -1045,7 +1045,7 @@ msgstr "Ви вже є членом цієї групи."
#: allianceauth/groupmanagement/views.py:358
msgid "You already have a pending application for that group."
msgstr "У вас вже є очікуюча заявка на вступ до цієї групи."
msgstr "Ви вже подали заявку на вступ до цієї групи."
#: allianceauth/groupmanagement/views.py:367
#, python-format
@@ -1062,7 +1062,7 @@ msgstr "Ви не є учасником цієї групи"
#: allianceauth/groupmanagement/views.py:393
msgid "You already have a pending leave request for that group."
msgstr "Ви вже маєте очікувану запит на вихід з цієї групи."
msgstr "Ви вже подали запит на вихід з цієї групи."
#: allianceauth/groupmanagement/views.py:409
#, python-format
@@ -1321,7 +1321,7 @@ msgstr "Всі прочитані повідомлення видалено."
#: allianceauth/optimer/auth_hooks.py:10
msgid "Fleet Operations"
msgstr "Операції флоту"
msgstr "Флотові операції"
#: allianceauth/optimer/form.py:12
#: allianceauth/optimer/templates/optimer/fleetoptable.html:11
@@ -1345,7 +1345,7 @@ msgstr "Тип операції"
#: allianceauth/optimer/form.py:17
#: allianceauth/srp/templates/srp/management.html:38
msgid "Fleet Commander"
msgstr "Командувач флоту"
msgstr "Командир флоту"
#: allianceauth/optimer/form.py:22 allianceauth/srp/form.py:14
#: allianceauth/srp/templates/srp/data.html:91
@@ -1400,7 +1400,7 @@ msgstr "Немає наступних таймерів."
#: allianceauth/optimer/templates/optimer/management.html:33
msgid "Past Fleet Operations"
msgstr "Минулі флотові операції"
msgstr "Завершені флотові операції"
#: allianceauth/optimer/templates/optimer/management.html:37
#: allianceauth/timerboard/templates/timerboard/view.html:535
@@ -1484,7 +1484,7 @@ msgstr "Стани"
#: allianceauth/services/abstract.py:72
msgid "That service account already exists"
msgstr "Такий обліковий запис сервісу вже існує"
msgstr "Такий сервісний обліковий запис вже існує"
#: allianceauth/services/abstract.py:103
#, python-brace-format
@@ -1505,7 +1505,7 @@ msgstr "Командир флоту:"
#: allianceauth/services/forms.py:8
msgid "Fleet Comms:"
msgstr "Комунікації флоту:"
msgstr "Голосовий канал флоту:"
#: allianceauth/services/forms.py:9
msgid "Fleet Type:"
@@ -1545,7 +1545,7 @@ msgstr "Ні"
#: allianceauth/services/forms.py:16
msgid "Important?*"
msgstr "Важливо?*"
msgstr "Важливий?*"
#: allianceauth/services/forms.py:21 allianceauth/services/forms.py:31
msgid "Password"
@@ -1614,7 +1614,7 @@ msgstr "Ви не маєте прав на доступ до Discourse."
#: allianceauth/services/modules/discourse/views.py:34
msgid "You must have a main character set to access Discourse."
msgstr "Ви повинні мати головний персонаж, щоб отримати доступ до Discourse."
msgstr "Ви повинні мати основний персонаж, щоб отримати доступ до Discourse."
#: allianceauth/services/modules/discourse/views.py:44
msgid ""
@@ -1702,7 +1702,7 @@ msgstr "Відправлено трансляцію Jabber на %s"
#: allianceauth/services/modules/openfire/views.py:144
msgid "Set jabber password."
msgstr "Встановлення пароля Jabber."
msgstr "Встановити пароль Jabber."
#: allianceauth/services/modules/phpbb3/views.py:34
msgid "Activated forum account."
@@ -1713,7 +1713,7 @@ msgstr "Активований обліковий запис форуму."
#: allianceauth/services/modules/phpbb3/views.py:78
#: allianceauth/services/modules/phpbb3/views.py:101
msgid "An error occurred while processing your forum account."
msgstr "Виникла помилка під час обробки вашого облікового запису форуму."
msgstr "Виникла помилка під час обробки вашого облікового запису на форумі."
#: allianceauth/services/modules/phpbb3/views.py:53
msgid "Deactivated forum account."
@@ -1721,11 +1721,11 @@ msgstr "Деактивований обліковий запис форуму."
#: allianceauth/services/modules/phpbb3/views.py:70
msgid "Reset forum password."
msgstr "Скидання пароля форуму."
msgstr "Скинути пароль форуму."
#: allianceauth/services/modules/phpbb3/views.py:98
msgid "Set forum password."
msgstr "Встановлення пароля форуму."
msgstr "Встановити пароль форуму."
#: allianceauth/services/modules/smf/views.py:52
msgid "Activated SMF account."
@@ -1744,11 +1744,11 @@ msgstr "Деактивований обліковий запис SMF."
#: allianceauth/services/modules/smf/views.py:95
msgid "Reset SMF password."
msgstr "Скидання пароля SMF."
msgstr "Скинути пароль SMF."
#: allianceauth/services/modules/smf/views.py:121
msgid "Set SMF password."
msgstr "Встановлення пароля SMF."
msgstr "Встановити пароль SMF."
#: allianceauth/services/modules/teamspeak3/forms.py:14
#, python-format
@@ -1761,7 +1761,7 @@ msgstr "Оновити групи TS3"
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:5
msgid "Verify Teamspeak"
msgstr "Перевірте Teamspeak"
msgstr "Перевірити Teamspeak"
#: allianceauth/services/modules/teamspeak3/templates/services/teamspeak3/teamspeakjoin.html:10
msgid "Verify Teamspeak Identity"
@@ -1869,11 +1869,11 @@ msgstr "Керування послугами"
#: allianceauth/services/templates/services/services.html:9
msgid "Available Services"
msgstr "Доступні послуги"
msgstr "Доступні сервіси"
#: allianceauth/services/templates/services/services.html:14
msgid "Service"
msgstr "Послуга"
msgstr "Сервіс"
#: allianceauth/services/templates/services/services.html:16
msgid "Domain"
@@ -1881,7 +1881,7 @@ msgstr "Домен"
#: allianceauth/srp/auth_hooks.py:13
msgid "Ship Replacement"
msgstr "Компенсація за корабель"
msgstr "Компенсації"
#: allianceauth/srp/form.py:9
#: allianceauth/srp/templates/srp/management.html:36

View File

@@ -4,9 +4,10 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Joel Falknau <ozirascal@gmail.com>, 2020
# Jesse . <sgeine@hotmail.com>, 2020
# Aaron BuBu <351793078@qq.com>, 2020
# Jesse . <sgeine@hotmail.com>, 2023
# Aaron BuBu <351793078@qq.com>, 2023
# Joel Falknau <ozirascal@gmail.com>, 2023
# Shen Yang, 2023
#
#, fuzzy
msgid ""
@@ -14,8 +15,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-09 18:20+1000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Aaron BuBu <351793078@qq.com>, 2020\n"
"PO-Revision-Date: 2023-10-08 09:23+0000\n"
"Last-Translator: Shen Yang, 2023\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -46,48 +47,48 @@ msgstr ""
#: allianceauth/authentication/models.py:80
msgid "English"
msgstr ""
msgstr "英语"
#: allianceauth/authentication/models.py:81
msgid "German"
msgstr ""
msgstr "德语"
#: allianceauth/authentication/models.py:82
msgid "Spanish"
msgstr ""
msgstr "西班牙语"
#: allianceauth/authentication/models.py:83
msgid "Chinese Simplified"
msgstr ""
msgstr "简体中文"
#: allianceauth/authentication/models.py:84
msgid "Russian"
msgstr ""
msgstr "俄语"
#: allianceauth/authentication/models.py:85
msgid "Korean"
msgstr ""
msgstr "韩语"
#: allianceauth/authentication/models.py:86
msgid "French"
msgstr ""
msgstr "法语"
#: allianceauth/authentication/models.py:87
msgid "Japanese"
msgstr ""
msgstr "日语"
#: allianceauth/authentication/models.py:88
msgid "Italian"
msgstr ""
msgstr "意大利语"
#: allianceauth/authentication/models.py:91
msgid "Language"
msgstr ""
msgstr "语言"
#: allianceauth/authentication/models.py:96
#: allianceauth/templates/allianceauth/night-toggle.html:6
msgid "Night Mode"
msgstr ""
msgstr "夜间模式"
#: allianceauth/authentication/models.py:110
#, python-format
@@ -696,7 +697,7 @@ msgstr ""
#: allianceauth/groupmanagement/models.py:215
msgid "reason"
msgstr ""
msgstr "原因"
#: allianceauth/groupmanagement/models.py:215
msgid "Reason why this name is reserved."
@@ -754,7 +755,7 @@ msgstr "操作者"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:48
msgid "Removed"
msgstr ""
msgstr "已移除"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:60
msgid "All times displayed are EVE/UTC."
@@ -1198,11 +1199,11 @@ msgstr "添加评论"
#: allianceauth/notifications/models.py:21
msgid "danger"
msgstr ""
msgstr "危险"
#: allianceauth/notifications/models.py:22
msgid "warning"
msgstr ""
msgstr "警告"
#: allianceauth/notifications/models.py:23
msgid "info"
@@ -1343,7 +1344,7 @@ msgstr "当前EVE游戏内时间"
#: allianceauth/optimer/templates/optimer/management.html:26
msgid "Next Fleet Operations"
msgstr ""
msgstr "下一个舰队任务"
#: allianceauth/optimer/templates/optimer/management.html:30
#: allianceauth/timerboard/templates/timerboard/view.html:362
@@ -1352,7 +1353,7 @@ msgstr "没有快到的时间节点,歇一会吧"
#: allianceauth/optimer/templates/optimer/management.html:33
msgid "Past Fleet Operations"
msgstr ""
msgstr "过去的舰队任务"
#: allianceauth/optimer/templates/optimer/management.html:37
#: allianceauth/timerboard/templates/timerboard/view.html:535
@@ -2257,15 +2258,15 @@ msgstr ""
#: allianceauth/timerboard/models.py:15
msgid "Shield"
msgstr ""
msgstr "护盾"
#: allianceauth/timerboard/models.py:16
msgid "Armor"
msgstr ""
msgstr "装甲"
#: allianceauth/timerboard/models.py:17
msgid "Hull"
msgstr ""
msgstr "结构"
#: allianceauth/timerboard/models.py:18
msgid "Final"
@@ -2273,11 +2274,11 @@ msgstr ""
#: allianceauth/timerboard/models.py:19
msgid "Anchoring"
msgstr ""
msgstr "铆钉"
#: allianceauth/timerboard/models.py:20
msgid "Unanchoring"
msgstr ""
msgstr "解锚"
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:11
msgid "Delete Timer"

View File

@@ -1,3 +1 @@
from .core import notify # noqa: F401
default_app_config = 'allianceauth.notifications.apps.NotificationsConfig'

View File

@@ -15,18 +15,22 @@ class NotificationAdmin(admin.ModelAdmin):
ordering = ("-timestamp", )
search_fields = ["user__username", "user__profile__main_character__character_name"]
@admin.display(
ordering="user__profile__main_character__character_name"
)
def _main(self, obj):
try:
return obj.user.profile.main_character
except AttributeError:
return obj.user
_main.admin_order_field = "user__profile__main_character__character_name"
@admin.display(
ordering="user__profile__state__name"
)
def _state(self, obj):
return obj.user.profile.state
_state.admin_order_field = "user__profile__state__name"
def has_change_permission(self, request, obj=None):
return False

View File

@@ -13,6 +13,10 @@ app = Celery('{{ project_name }}')
# the configuration object to child processes.
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 )
app.conf.broker_transport_options = {
'priority_steps': list(range(10)), # setup que to have 10 steps

View File

@@ -41,23 +41,23 @@ CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
CELERYBEAT_SCHEDULE = {
'esi_cleanup_callbackredirect': {
'task': 'esi.tasks.cleanup_callbackredirect',
'schedule': crontab(minute=0, hour='*/4'),
'schedule': crontab(minute='0', hour='*/4'),
},
'esi_cleanup_token': {
'task': 'esi.tasks.cleanup_token',
'schedule': crontab(minute=0, hour=0),
'schedule': crontab(minute='0', hour='0'),
},
'run_model_update': {
'task': 'allianceauth.eveonline.tasks.run_model_update',
'schedule': crontab(minute=0, hour="*/6"),
'schedule': crontab(minute='0', hour="*/6"),
},
'check_all_character_ownership': {
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
'schedule': crontab(minute=0, hour='*/4'),
'schedule': crontab(minute='0', hour='*/4'),
},
'analytics_daily_stats': {
'task': 'allianceauth.analytics.tasks.analytics_daily_stats',
'schedule': crontab(minute=0, hour=2),
'schedule': crontab(minute='0', hour='2'),
}
}

View File

@@ -32,6 +32,13 @@ INSTALLED_APPS += [
# To change the logging level for extensions, uncomment the following line.
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
# By default, apps are prevented from having public views for security reasons.
# To allow specific apps to have public views, add them to APPS_WITH_PUBLIC_VIEWS
# » The format is the same as in INSTALLED_APPS
# » 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
DATABASES['default'] = {

View File

@@ -1,9 +1,8 @@
from django.conf.urls import include
from allianceauth import urls
from django.urls import re_path
from django.urls import include, path
urlpatterns = [
re_path(r'', include(urls)),
path('', include(urls)),
]
handler500 = 'allianceauth.views.Generic500Redirect'

View File

@@ -66,6 +66,8 @@ class NameFormatConfigAdmin(admin.ModelAdmin):
form = NameFormatConfigForm
list_display = ('service_name', 'get_state_display_string')
@admin.display(
description='States'
)
def get_state_display_string(self, obj):
return ', '.join([state.name for state in obj.states.all()])
get_state_display_string.short_description = 'States'

View File

@@ -1,15 +1,17 @@
from django.conf.urls import include
from django.urls import re_path
from string import Formatter
from typing import Iterable, Optional
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
from django.urls import include, re_path
from django.utils.functional import cached_property
from django.conf import settings
from string import Formatter
from allianceauth.hooks import get_hooks
from .models import NameFormatConfig
def get_extension_logger(name):
"""
Takes the name of a plugin/extension and generates a child logger of the extensions logger
@@ -156,8 +158,32 @@ class MenuItemHook:
class UrlHook:
def __init__(self, urls, namespace, base_url):
"""A hook for registering the URLs of a Django app.
Args:
- urls: The urls module to include
- namespace: The URL namespace to apply. This is usually just the app name.
- base_url: The URL prefix to match against in regex form.
Example ``r'^app_name/'``.
This prefix will be applied in front of all URL patterns included.
It is possible to use the same prefix as existing apps (or no prefix at all),
but standard URL resolution ordering applies
(hook URLs are the last ones registered).
- excluded_views: Optional list of views to be excluded
from auto-decorating them with the
default ``main_character_required`` decorator, e.g. to make them public.
Views must be specified by their qualified name,
e.g. ``["example.views.my_public_view"]``
"""
def __init__(
self,
urls,
namespace: str,
base_url: str,
excluded_views : Optional[Iterable[str]] = None
):
self.include_pattern = re_path(base_url, include(urls, namespace=namespace))
self.excluded_views = set(excluded_views or [])
class NameFormatter:

View File

@@ -6,6 +6,7 @@ from ...admin import ServicesUserAdmin
from . import __title__
from .models import DiscordUser
from .utils import LoggerAddTag
from .auth_hooks import DiscordService
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
@@ -27,6 +28,6 @@ class DiscordUserAdmin(ServicesUserAdmin):
@admin.display(description='Discord Username', ordering='username')
def _username(self, obj):
if obj.username and obj.discriminator:
return f'{obj.username}#{obj.discriminator}'
return ''
return DiscordService.get_discord_username(
username=obj.username, discriminator=obj.discriminator
)

View File

@@ -11,6 +11,9 @@ from .models import DiscordUser
from .urls import urlpatterns
from .utils import LoggerAddTag
from . import tasks, __title__
from .app_settings import (
DISCORD_SYNC_NAMES
)
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
@@ -30,6 +33,29 @@ class DiscordService(ServicesHook):
self.access_perm = 'discord.access_discord'
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:
if self.user_has_account(user):
logger.debug('Deleting user %s %s account', user, self.name)
@@ -43,10 +69,19 @@ class DiscordService(ServicesHook):
user_has_account = True
username = request.user.discord.username
discriminator = request.user.discord.discriminator
if username and discriminator:
discord_username = f'{username}#{discriminator}'
else:
discord_username = ''
discord_username = self.get_discord_username(
username=username, discriminator=discriminator
)
# 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:
discord_username = ''
user_has_account = False
@@ -67,17 +102,18 @@ class DiscordService(ServicesHook):
return has_perms
def sync_nickname(self, user):
logger.debug('Syncing %s nickname for user %s', self.name, user)
if self.user_has_account(user):
tasks.update_nickname.apply_async(
kwargs={
'user_pk': user.pk,
# since the new nickname is not yet in the DB we need to
# provide it manually to the task
'nickname': user_formatted_nick(user)
},
priority=SINGLE_TASK_PRIORITY
)
if DISCORD_SYNC_NAMES:
logger.debug('Syncing %s nickname for user %s', self.name, user)
if self.user_has_account(user):
tasks.update_nickname.apply_async(
kwargs={
'user_pk': user.pk,
# since the new nickname is not yet in the DB we need to
# provide it manually to the task
'nickname': user_formatted_nick(user)
},
priority=SINGLE_TASK_PRIORITY
)
def sync_nicknames_bulk(self, users: list):
"""Sync nickname for a list of users in bulk.

View File

@@ -588,16 +588,17 @@ class DiscordClient:
return None # User is no longer a member
guild_roles = RolesSet(self.guild_roles(guild_id=guild_id))
logger.debug('Current guild roles: %s', guild_roles.ids())
_roles = set(member_info.roles)
if not guild_roles.has_roles(member_info.roles):
guild_roles = RolesSet(
self.guild_roles(guild_id=guild_id, use_cache=False)
)
if not guild_roles.has_roles(member_info.roles):
role_ids = set(member_info.roles).difference(guild_roles.ids())
raise RuntimeError(
f'Discord user {user_id} has unknown roles: {role_ids}'
)
return guild_roles.subset(member_info.roles)
logger.warning(f'Discord user {user_id} has unknown roles: {role_ids}')
for _r in role_ids:
_roles.remove(_r)
return guild_roles.subset(_roles)
@classmethod
def _is_member_unknown_error(cls, r: requests.Response) -> bool:

View File

@@ -899,8 +899,8 @@ class TestGuildMemberRoles(NoSocketsTestCase):
mock_guild_roles.return_value = {role_a, role_b}
client = DiscordClientStub(TEST_BOT_TOKEN, mock_redis)
# when/then
with self.assertRaises(RuntimeError):
client.guild_member_roles(TEST_GUILD_ID, TEST_USER_ID)
roles = client.guild_member_roles(TEST_GUILD_ID, TEST_USER_ID)
self.assertEqual(roles, RolesSet([role_a]))
# TODO: Re-enable after adding Discord general error handling
# def test_should_raise_exception_if_member_info_is_invalid(

View File

View File

@@ -81,11 +81,18 @@ class TestDiscordService(NoSocketsTestCase):
self.assertFalse(DiscordUser.objects.filter(user=self.none_member).exists())
@patch(MODULE_PATH + '.tasks.update_nickname')
@patch(MODULE_PATH + '.auth_hooks.DISCORD_SYNC_NAMES', True)
def test_sync_nickname(self, mock_update_nickname):
service = self.service()
service.sync_nickname(self.member)
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')
def test_sync_nicknames_bulk(self, mock_update_nicknames_bulk):
service = self.service()
@@ -150,3 +157,23 @@ class TestDiscordService(NoSocketsTestCase):
self.assertTemplateUsed(service.service_ctrl_template)
self.assertIn('/discord/reset/', 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.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(
self, requests_mocker
):
@@ -185,6 +186,7 @@ class TestServiceFeatures(TransactionTestCase):
self.assertTrue(nick_updated)
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(
self, requests_mocker
):

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
app_name = 'example'

View File

@@ -2,8 +2,7 @@ from django.contrib import admin
from .models import Ips4User
@admin.register(Ips4User)
class Ips4UserAdmin(admin.ModelAdmin):
list_display = ('user', 'username', 'id')
search_fields = ('user__username', 'username', 'id')
admin.site.register(Ips4User, Ips4UserAdmin)

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

0
allianceauth/services/modules/openfire/manager.py Executable file → Normal file
View File

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

0
allianceauth/services/modules/phpbb3/manager.py Executable file → Normal file
View File

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -36,10 +36,12 @@ class AuthTSgroupAdmin(admin.ModelAdmin):
kwargs['queryset'] = TSgroup.objects.exclude(ts_group_name__in=ReservedGroupName.objects.values_list('name', flat=True))
return super().formfield_for_manytomany(db_field, request, **kwargs)
@admin.display(
description='ts groups'
)
def _ts_group(self, obj):
return [x for x in obj.ts_group.all().order_by('ts_group_id')]
_ts_group.short_description = 'ts groups'
# _ts_group.admin_order_field = 'profile__state'

0
allianceauth/services/modules/teamspeak3/manager.py Executable file → Normal file
View File

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

0
allianceauth/services/modules/teamspeak3/util/ts3.py Executable file → Normal file
View File

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

0
allianceauth/services/templates/services/services.html Executable file → Normal file
View File

View File

@@ -0,0 +1,30 @@
from unittest import TestCase
from allianceauth.services.hooks import UrlHook
from allianceauth.groupmanagement import urls
class TestUrlHook(TestCase):
def test_can_create_simple_hook(self):
# when
obj = UrlHook(urls, "groupmanagement", r"^groupmanagement/")
# then
self.assertEqual(obj.include_pattern.app_name, "groupmanagement")
self.assertFalse(obj.excluded_views)
def test_can_create_hook_with_excluded_views(self):
# when
obj = UrlHook(
urls,
"groupmanagement",
r"^groupmanagement/",
["groupmanagement.views.group_management"],
)
# then
self.assertEqual(obj.include_pattern.app_name, "groupmanagement")
self.assertIn("groupmanagement.views.group_management", obj.excluded_views)
def test_should_raise_error_when_called_with_invalid_excluded_views(self):
# when/then
with self.assertRaises(TypeError):
UrlHook(urls, "groupmanagement", r"^groupmanagement/", 99)

View File

@@ -1,4 +1,4 @@
from django.conf.urls import include
from django.urls import include
from allianceauth.hooks import get_hooks
from django.urls import path

0
allianceauth/services/views.py Executable file → Normal file
View File

0
allianceauth/srp/__init__.py Executable file → Normal file
View File

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