Compare commits

..

14 Commits

Author SHA1 Message Date
Joel Falknau
63fb449060 django now doesnt like unsaved models, fair 2024-08-24 15:42:11 +10:00
Joel Falknau
235675fa9b django.utils.timezone.utc was removed. (shame it was handy) 2024-08-24 15:41:37 +10:00
Joel Falknau
a065f043eb correct index for new page name 2024-08-24 15:09:12 +10:00
Joel Falknau
0839920032 use defined pygments 2024-08-24 15:09:02 +10:00
Joel Falknau
29ad4acff7 bring docs up to python 312 across the board 2024-08-24 15:08:41 +10:00
Joel Falknau
3a5b84d1f9 remove \u2060 from image name 2024-08-24 13:42:55 +10:00
Joel Falknau
bbcb94021e run pyupgrade 2024-08-23 13:55:25 +10:00
Joel Falknau
d50f13528b add python 3.13 RC to tox allow fail 2024-08-22 12:58:54 +10:00
Joel Falknau
c88521af88 django-celery-beat >=2.7.0 2024-08-22 12:54:31 +10:00
Joel Falknau
2bd5ff8723 Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v5.x 2024-08-12 13:23:34 +10:00
Joel Falknau
84484cebcb move to django 5.1.x 2024-08-12 13:20:27 +10:00
Joel Falknau
5ee34fcb2d bring docker major up 2024-08-12 13:20:11 +10:00
Joel Falknau
046473def1 use 3.12 in builds 2024-08-12 13:19:57 +10:00
Joel Falknau
6aaba2bf3d Drop Python 3.8 and 3.9 2024-08-12 13:19:08 +10:00
217 changed files with 5055 additions and 12986 deletions

View File

@@ -51,30 +51,6 @@ secret_detection:
stage: gitlab stage: gitlab
before_script: [] before_script: []
test-3.8-core:
<<: *only-default
image: python:3.8-bookworm
script:
- tox -e py38-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.9-core:
<<: *only-default
image: python:3.9-bookworm
script:
- tox -e py39-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.10-core: test-3.10-core:
<<: *only-default <<: *only-default
image: python:3.10-bookworm image: python:3.10-bookworm
@@ -111,29 +87,18 @@ test-3.12-core:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
test-3.8-all: test-3.13-core:
<<: *only-default <<: *only-default
image: python:3.8-bookworm image: python:3.13-rc-bookworm
script: script:
- tox -e py38-all - tox -e py313-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.9-all:
<<: *only-default
image: python:3.9-bookworm
script:
- tox -e py39-all
artifacts: artifacts:
when: always when: always
reports: reports:
coverage_report: coverage_report:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
allow_failure: true
test-3.10-all: test-3.10-all:
<<: *only-default <<: *only-default
@@ -172,9 +137,22 @@ test-3.12-all:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
test-3.13-all:
<<: *only-default
image: python:3.13-rc-bookworm
script:
- tox -e py313-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
build-test: build-test:
stage: test stage: test
image: python:3.11-bookworm image: python:3.12-bookworm
before_script: before_script:
- python -m pip install --upgrade pip - python -m pip install --upgrade pip
@@ -193,13 +171,13 @@ build-test:
test-docs: test-docs:
<<: *only-default <<: *only-default
image: python:3.11-bookworm image: python:3.12-bookworm
script: script:
- tox -e docs - tox -e docs
deploy_production: deploy_production:
stage: deploy stage: deploy
image: python:3.11-bookworm image: python:3.12-bookworm
before_script: before_script:
- python -m pip install --upgrade pip - python -m pip install --upgrade pip
@@ -215,10 +193,10 @@ deploy_production:
build-image: build-image:
before_script: [] before_script: []
image: docker:24.0 image: docker:27.0
stage: docker stage: docker
services: services:
- docker:24.0-dind - docker:27-dind
script: | script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -) CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
@@ -239,10 +217,10 @@ build-image:
build-image-dev: build-image-dev:
before_script: [] before_script: []
image: docker:24.0 image: docker:27
stage: docker stage: docker
services: services:
- docker:24.0-dind - docker:27-dind
script: | script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -) CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
@@ -260,10 +238,10 @@ build-image-dev:
build-image-mr: build-image-mr:
before_script: [] before_script: []
image: docker:24.0 image: docker:27
stage: docker stage: docker
services: services:
- docker:24.0-dind - docker:27-dind
script: | script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -) CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA

View File

@@ -3,41 +3,22 @@
# Update this file: # Update this file:
# pre-commit autoupdate # pre-commit autoupdate
# Set the default language versions for the hooks
default_language_version:
python: python3 # Force all Python hooks to use Python 3
node: 22.12.0 # Force all Node hooks to use Node 22.12.0
# Globally exclude files
# https://pre-commit.com/#top_level-exclude
exclude: |
(?x)(
LICENSE|
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json|
static/(.*)/libs/
)
repos: repos:
# Code Upgrades # Code Upgrades
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.19.1 rev: v3.15.2
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py310-plus]
- repo: https://github.com/adamchainz/django-upgrade - repo: https://github.com/adamchainz/django-upgrade
rev: 1.22.2 rev: 1.17.0
hooks: hooks:
- id: django-upgrade - id: django-upgrade
args: [--target-version=4.2] args: [--target-version=4.2]
# Formatting # Formatting
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 rev: v4.6.0
hooks: hooks:
# Identify invalid files # Identify invalid files
- id: check-ast - id: check-ast
@@ -52,9 +33,9 @@ repos:
- id: detect-private-key - id: detect-private-key
- id: check-case-conflict - id: check-case-conflict
# Python checks # Python checks
# - id: check-docstring-first # - id: check-docstring-first
- id: debug-statements - id: debug-statements
# - id: requirements-txt-fixer # - id: requirements-txt-fixer
- id: fix-encoding-pragma - id: fix-encoding-pragma
args: [--remove] args: [--remove]
- id: fix-byte-order-marker - id: fix-byte-order-marker
@@ -63,22 +44,45 @@ repos:
args: [--fix=lf] args: [--fix=lf]
- id: trailing-whitespace - id: trailing-whitespace
args: [--markdown-linebreak-ext=md] args: [--markdown-linebreak-ext=md]
exclude: |
(?x)(
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json
)
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: |
(?x)(
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python - repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 3.2.0 rev: 2.7.3
hooks: hooks:
- id: editorconfig-checker - id: editorconfig-checker
exclude: |
(?x)(
LICENSE|
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/igorshubovych/markdownlint-cli - repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.44.0 rev: v0.41.0
hooks: hooks:
- id: markdownlint - id: markdownlint
language: node
args: args:
- --disable=MD013 - --disable=MD013
# Infrastructure # Infrastructure
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.0 rev: 2.1.3
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt
name: pyproject.toml formatter name: pyproject.toml formatter
@@ -86,9 +90,9 @@ repos:
args: args:
- --indent=4 - --indent=4
additional_dependencies: additional_dependencies:
- tox==4.24.1 # https://github.com/tox-dev/tox/releases/latest - tox==4.15.0 # https://github.com/tox-dev/tox/releases/latest
- repo: https://github.com/abravalheri/validate-pyproject - repo: https://github.com/abravalheri/validate-pyproject
rev: v0.23 rev: v0.18
hooks: hooks:
- id: validate-pyproject - id: validate-pyproject
name: Validate pyproject.toml name: Validate pyproject.toml

View File

@@ -7,11 +7,11 @@ version: 2
# Set the version of Python and other tools you might need # Set the version of Python and other tools you might need
build: build:
os: ubuntu-22.04 os: ubuntu-24.04
apt_packages: apt_packages:
- redis - redis
tools: tools:
python: "3.11" python: "3.12"
jobs: jobs:
post_system_dependencies: post_system_dependencies:
- redis-server --daemonize yes - redis-server --daemonize yes

View File

@@ -5,7 +5,7 @@
[![django](https://img.shields.io/pypi/djversions/allianceauth?label=django)](https://pypi.org/project/allianceauth/) [![django](https://img.shields.io/pypi/djversions/allianceauth?label=django)](https://pypi.org/project/allianceauth/)
[![version](https://img.shields.io/pypi/v/allianceauth?label=release)](https://pypi.org/project/allianceauth/) [![version](https://img.shields.io/pypi/v/allianceauth?label=release)](https://pypi.org/project/allianceauth/)
[![pipeline status](https://gitlab.com/allianceauth/allianceauth/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master) [![pipeline status](https://gitlab.com/allianceauth/allianceauth/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Documentation Status](https://readthedocs.org/projects/allianceauth/badge/?version=latest)](https://allianceauth.readthedocs.io/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/allianceauth/badge/?version=latest)](http://allianceauth.readthedocs.io/?badge=latest)
[![coverage report](https://gitlab.com/allianceauth/allianceauth/badges/master/coverage.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master) [![coverage report](https://gitlab.com/allianceauth/allianceauth/badges/master/coverage.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](https://discord.gg/fjnHAmk) [![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](https://discord.gg/fjnHAmk)
@@ -14,7 +14,7 @@ An auth system for EVE Online to help in-game organizations manage online servic
## Content ## Content
- [Overview](#overview) - [Overview](#overview)
- [Documentation](https://allianceauth.rtfd.io) - [Documentation](http://allianceauth.rtfd.io)
- [Support](#support) - [Support](#support)
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases) - [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
- [Developer Team](#development-team) - [Developer Team](#development-team)
@@ -38,7 +38,7 @@ Main features:
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization - English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](https://allianceauth.rtfd.io). For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
## Screenshot ## Screenshot

View File

@@ -5,7 +5,7 @@ manage online service access.
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
__version__ = '4.6.4' __version__ = '4.2.2'
__title__ = 'Alliance Auth' __title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth' __url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}' NAME = f'{__title__} v{__version__}'

View File

@@ -1,16 +1,15 @@
from django.contrib import admin from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsTokens from .models import AnalyticsIdentifier, AnalyticsTokens
from solo.admin import SingletonModelAdmin
@admin.register(AnalyticsIdentifier) @admin.register(AnalyticsIdentifier)
class AnalyticsIdentifierAdmin(SingletonModelAdmin): class AnalyticsIdentifierAdmin(admin.ModelAdmin):
search_fields = ['identifier', ] search_fields = ['identifier', ]
list_display = ['identifier', ] list_display = ('identifier',)
@admin.register(AnalyticsTokens) @admin.register(AnalyticsTokens)
class AnalyticsTokensAdmin(admin.ModelAdmin): class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ] search_fields = ['name', ]
list_display = ['name', 'type', ] list_display = ('name', 'type',)

View File

@@ -1,17 +0,0 @@
# Generated by Django 4.2.16 on 2024-12-11 02:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('analytics', '0009_remove_analyticstokens_ignore_paths_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='analyticsidentifier',
options={'verbose_name': 'Analytics Identifier'},
),
]

View File

@@ -1,19 +1,23 @@
from typing import Literal
from django.db import models from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from solo.models import SingletonModel
from uuid import uuid4 from uuid import uuid4
class AnalyticsIdentifier(SingletonModel): class AnalyticsIdentifier(models.Model):
identifier = models.UUIDField(default=uuid4, editable=False) identifier = models.UUIDField(
default=uuid4,
editable=False)
def __str__(self) -> Literal['Analytics Identifier']: def save(self, *args, **kwargs):
return "Analytics Identifier" if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object
class Meta: raise ValidationError('There is can be only one \
verbose_name = "Analytics Identifier" AnalyticsIdentifier instance')
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super().save(*args, **kwargs)
class AnalyticsTokens(models.Model): class AnalyticsTokens(models.Model):

View File

@@ -5,7 +5,6 @@ from django.apps import apps
from celery import shared_task from celery import shared_task
from .models import AnalyticsTokens, AnalyticsIdentifier from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import ( from .utils import (
existence_baremetal_or_docker,
install_stat_addons, install_stat_addons,
install_stat_tokens, install_stat_tokens,
install_stat_users) install_stat_users)
@@ -68,8 +67,8 @@ def analytics_event(namespace: str,
value=value).apply_async(priority=9) value=value).apply_async(priority=9)
@shared_task @shared_task()
def analytics_daily_stats() -> None: def analytics_daily_stats():
"""Celery Task: Do not call directly """Celery Task: Do not call directly
Gathers a series of daily statistics Gathers a series of daily statistics
@@ -78,7 +77,6 @@ def analytics_daily_stats() -> None:
users = install_stat_users() users = install_stat_users()
tokens = install_stat_tokens() tokens = install_stat_tokens()
addons = install_stat_addons() addons = install_stat_addons()
existence_type = existence_baremetal_or_docker()
logger.debug("Running Daily Analytics Upload") logger.debug("Running Daily Analytics Upload")
analytics_event(namespace='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
@@ -86,11 +84,6 @@ def analytics_daily_stats() -> None:
label='existence', label='existence',
value=1, value=1,
event_type='Stats') event_type='Stats')
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label=existence_type,
value=1,
event_type='Stats')
analytics_event(namespace='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
task='send_install_stats', task='send_install_stats',
label='users', label='users',
@@ -106,6 +99,7 @@ def analytics_daily_stats() -> None:
label='addons', label='addons',
value=addons, value=addons,
event_type='Stats') event_type='Stats')
for appconfig in apps.get_app_configs(): for appconfig in apps.get_app_configs():
if appconfig.label in [ if appconfig.label in [
"django_celery_beat", "django_celery_beat",
@@ -141,7 +135,7 @@ def analytics_daily_stats() -> None:
event_type='Stats') event_type='Stats')
@shared_task @shared_task()
def send_ga_tracking_celery_event( def send_ga_tracking_celery_event(
measurement_id: str, measurement_id: str,
secret: str, secret: str,
@@ -171,7 +165,7 @@ def send_ga_tracking_celery_event(
} }
payload = { payload = {
'client_id': AnalyticsIdentifier.get_solo().identifier.hex, 'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
"user_properties": { "user_properties": {
"allianceauth_version": { "allianceauth_version": {
"value": __version__ "value": __version__

View File

@@ -1,8 +1,9 @@
from allianceauth.analytics.models import AnalyticsIdentifier from allianceauth.analytics.models import AnalyticsIdentifier
from django.core.exceptions import ValidationError
from django.test.testcases import TestCase from django.test.testcases import TestCase
from uuid import uuid4 from uuid import UUID, uuid4
# Identifiers # Identifiers
@@ -13,4 +14,14 @@ uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
class TestAnalyticsIdentifier(TestCase): class TestAnalyticsIdentifier(TestCase):
def test_identifier_random(self): def test_identifier_random(self):
self.assertNotEqual(AnalyticsIdentifier.get_solo(), uuid4) self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4)
def test_identifier_singular(self):
AnalyticsIdentifier.objects.all().delete()
AnalyticsIdentifier.objects.create(identifier=uuid_1)
# Yeah i have multiple asserts here, they all do the same thing
with self.assertRaises(ValidationError):
AnalyticsIdentifier.objects.create(identifier=uuid_2)
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
self.assertEqual(AnalyticsIdentifier.objects.get(
pk=1).identifier, UUID(uuid_1))

View File

@@ -1,4 +1,3 @@
import os
from django.apps import apps from django.apps import apps
from allianceauth.authentication.models import User from allianceauth.authentication.models import User
from esi.models import Token from esi.models import Token
@@ -35,16 +34,3 @@ def install_stat_addons() -> int:
The Number of Installed Apps""" The Number of Installed Apps"""
addons = len(list(apps.get_app_configs())) addons = len(list(apps.get_app_configs()))
return addons return addons
def existence_baremetal_or_docker() -> str:
"""Checks the Installation Type of an install
Returns
-------
str
existence_baremetal or existence_docker"""
docker_tag = os.getenv('AA_DOCKER_TAG')
if docker_tag:
return "existence_docker"
return "existence_baremetal"

View File

@@ -1,4 +1,5 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.core.checks import Warning, Error, register
class AllianceAuthConfig(AppConfig): class AllianceAuthConfig(AppConfig):

View File

@@ -12,7 +12,7 @@ from django.conf import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def active_tasks_count() -> Optional[int]: def active_tasks_count() -> int | None:
"""Return count of currently active tasks """Return count of currently active tasks
or None if celery workers are not online. or None if celery workers are not online.
""" """
@@ -20,7 +20,7 @@ def active_tasks_count() -> Optional[int]:
return _tasks_count(inspect.active()) return _tasks_count(inspect.active())
def _tasks_count(data: dict) -> Optional[int]: def _tasks_count(data: dict) -> int | None:
"""Return count of tasks in data from celery inspect API.""" """Return count of tasks in data from celery inspect API."""
try: try:
tasks = itertools.chain(*data.values()) tasks = itertools.chain(*data.values())
@@ -29,7 +29,7 @@ def _tasks_count(data: dict) -> Optional[int]:
return len(list(tasks)) return len(list(tasks))
def queued_tasks_count() -> Optional[int]: def queued_tasks_count() -> int | None:
"""Return count of queued tasks. Return None if there was an error.""" """Return count of queued tasks. Return None if there was an error."""
try: try:
with current_app.connection_or_acquire() as conn: with current_app.connection_or_acquire() as conn:

View File

@@ -2,7 +2,8 @@ from django.urls import include
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from functools import wraps from functools import wraps
from typing import Callable, Iterable, Optional from typing import Optional
from collections.abc import Callable, Iterable
from django.urls import include from django.urls import include
from django.contrib import messages from django.contrib import messages
@@ -17,7 +18,7 @@ def user_has_main_character(user):
def decorate_url_patterns( def decorate_url_patterns(
urls, decorator: Callable, excluded_views: Optional[Iterable] = None urls, decorator: Callable, excluded_views: Iterable | None = None
): ):
"""Decorate views given in url patterns except when they are explicitly excluded. """Decorate views given in url patterns except when they are explicitly excluded.

View File

@@ -1,18 +0,0 @@
# Generated by Django 4.2 on 2024-09-13 09:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_userprofile_language'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='language',
field=models.CharField(blank=True, choices=[('en', 'English'), ('cs-cz', 'Czech'), ('de', 'German'), ('es', 'Spanish'), ('it-it', 'Italian'), ('ja', 'Japanese'), ('ko-kr', 'Korean'), ('fr-fr', 'French'), ('ru', 'Russian'), ('nl-nl', 'Dutch'), ('pl-pl', 'Polish'), ('uk', 'Ukrainian'), ('zh-hans', 'Simplified Chinese')], default='', max_length=10, verbose_name='Language'),
),
]

View File

@@ -67,20 +67,18 @@ class UserProfile(models.Model):
""" """
Choices for UserProfile.language Choices for UserProfile.language
""" """
# Sorted by Language Code alphabetical order + English at top
ENGLISH = 'en', _('English') ENGLISH = 'en', _('English')
CZECH = 'cs-cz', _("Czech") # Not yet at 50% translated
GERMAN = 'de', _('German') GERMAN = 'de', _('German')
SPANISH = 'es', _('Spanish') SPANISH = 'es', _('Spanish')
ITALIAN = 'it-it', _('Italian') CHINESE = 'zh-hans', _('Chinese Simplified')
JAPANESE = 'ja', _('Japanese')
KOREAN = 'ko-kr', _('Korean')
FRENCH = 'fr-fr', _('French')
RUSSIAN = 'ru', _('Russian') RUSSIAN = 'ru', _('Russian')
DUTCH = 'nl-nl', _("Dutch") KOREAN = 'ko', _('Korean')
POLISH = 'pl-pl', _("Polish") FRENCH = 'fr', _('French')
JAPANESE = 'ja', _('Japanese')
ITALIAN = 'it', _('Italian')
UKRAINIAN = 'uk', _('Ukrainian') UKRAINIAN = 'uk', _('Ukrainian')
CHINESE = 'zh-hans', _('Simplified Chinese') POLISH = 'pl', _("Polish")
user = models.OneToOneField( user = models.OneToOneField(
User, User,

View File

@@ -16,7 +16,7 @@ class _TaskCounts(NamedTuple):
retried: int retried: int
failed: int failed: int
total: int total: int
earliest_task: Optional[dt.datetime] earliest_task: dt.datetime | None
hours: int hours: int

View File

@@ -17,7 +17,7 @@ class EventSeries:
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES" _ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
def __init__(self, key_id: str, redis: Optional[Redis] = None) -> None: def __init__(self, key_id: str, redis: Redis | None = None) -> None:
self._redis = get_redis_client_or_stub() if not redis else redis self._redis = get_redis_client_or_stub() if not redis else redis
self._key_id = str(key_id) self._key_id = str(key_id)
self.clear() self.clear()
@@ -46,7 +46,7 @@ class EventSeries:
my_id = self._redis.incr(self._key_counter) my_id = self._redis.incr(self._key_counter)
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()}) self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})
def all(self) -> List[dt.datetime]: def all(self) -> list[dt.datetime]:
"""List of all known events.""" """List of all known events."""
return [ return [
event[1] event[1]
@@ -75,7 +75,7 @@ class EventSeries:
maximum = "+inf" if not latest else latest.timestamp() maximum = "+inf" if not latest else latest.timestamp()
return self._redis.zcount(self._key_sorted_set, min=minimum, max=maximum) return self._redis.zcount(self._key_sorted_set, min=minimum, max=maximum)
def first_event(self, earliest: dt.datetime = None) -> Optional[dt.datetime]: def first_event(self, earliest: dt.datetime = None) -> dt.datetime | None:
"""Date/Time of first event. Returns `None` if series has no events. """Date/Time of first event. Returns `None` if series has no events.
Args: Args:

View File

@@ -1,6 +1,5 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
@@ -14,7 +13,7 @@
{% block content %} {% block content %}
<div> <div>
<p class="mb-3"> <p class="mb-3">
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://developers.eveonline.com/authorized-apps where possible."|urlize %} {% translate "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."|urlize %}
</p> </p>
<table class="table w-100" id="table_tokens"> <table class="table w-100" id="table_tokens">
@@ -51,23 +50,20 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include "bundles/datatables-js-bs5.html" %} {% include "bundles/datatables-js-bs5.html" %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
let grp = 2; let grp = 2;
$('#table_tokens').DataTable({ const table = $('#table_tokens').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
'columnDefs': [{orderable: false, targets: [0, 1]}, { 'columnDefs': [{orderable: false, targets: [0, 1]}, {
'visible': false, 'visible': false,
'targets': grp 'targets': grp
}], }],
'order': [[grp, 'asc']], 'order': [[grp, 'asc']],
'drawCallback': function (settings) { 'drawCallback': function (settings) {
const api = this.api(); var api = this.api();
const rows = api.rows({page: 'current'}).nodes(); var rows = api.rows({page: 'current'}).nodes();
let last = null; var last = null;
api.column(grp, {page: 'current'}) api.column(grp, {page: 'current'})
.data() .data()
.each((group, i) => { .each((group, i) => {

View File

@@ -5,11 +5,11 @@
{% csrf_token %} {% csrf_token %}
<select class="form-select" onchange="this.form.submit()" class="form-control" id="lang-select" name="language"> <select class="form-select" onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
{% get_available_languages as LANGUAGES %} {% get_language_info_list for LANGUAGES as languages %}
{% for lang_code, lang_name in LANGUAGES %} {% for language in languages %}
<option lang="{{ lang_code }}" value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}> <option lang="{{ language.code }}" value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ lang_code|language_name_local|capfirst }} ({{ lang_code }}) {{ language.name_local|capfirst }} ({{ language.code }})
</option> </option>
{% endfor %} {% endfor %}
</select> </select>

View File

@@ -29,7 +29,7 @@
</p> </p>
<p class="text-center"> <p class="text-center">
<a class="text-reset" href="https://developers.eveonline.com/authorized-apps" target="_blank" rel="noopener noreferrer"> <a class="text-reset" href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
{% translate "Manage ESI Applications" %} {% translate "Manage ESI Applications" %}
</a> </a>
</p> </p>

View File

@@ -1,8 +1,3 @@
"""
Django system checks for Alliance Auth
"""
from typing import List
from django import db from django import db
from django.core.checks import CheckMessage, Error, register, Warning from django.core.checks import CheckMessage, Error, register, Warning
from allianceauth.utils.cache import get_redis_client from allianceauth.utils.cache import get_redis_client
@@ -11,7 +6,7 @@ from packaging.version import InvalidVersion, Version as Pep440Version
from celery import current_app from celery import current_app
from django.conf import settings from django.conf import settings
from sqlite3.dbapi2 import sqlite_version_info from sqlite3.dbapi2 import sqlite_version_info
import datetime
""" """
A = System Packages A = System Packages
B = Configuration B = Configuration
@@ -19,476 +14,143 @@ B = Configuration
@register() @register()
def django_settings(app_configs, **kwargs) -> List[CheckMessage]: def django_settings(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that Django settings are correctly configured
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
# Check for SITE_URL
if hasattr(settings, "SITE_URL"): if hasattr(settings, "SITE_URL"):
# Check if SITE_URL is empty if settings.SITE_URL[-1] == "/":
if settings.SITE_URL == "": errors.append(Warning("'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth.", hint="", id="allianceauth.checks.B005"))
errors.append(
Error(
msg="'SITE_URL' is empty.",
hint="Make sure to set 'SITE_URL' to the URL of your Auth instance. (Without trailing slash)",
id="allianceauth.checks.B011",
)
)
# Check if SITE_URL has a trailing slash
elif settings.SITE_URL[-1] == "/":
errors.append(
Warning(
msg="'SITE_URL' has a trailing slash. This may lead to incorrect links being generated by Auth.",
hint="",
id="allianceauth.checks.B005",
)
)
# SITE_URL not found
else: else:
errors.append( errors.append(Error("No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.", hint="", id="allianceauth.checks.B006"))
Error(
msg="No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.",
hint="",
id="allianceauth.checks.B006",
)
)
# Check for CSRF_TRUSTED_ORIGINS
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"): if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
# Check if SITE_URL is not in CSRF_TRUSTED_ORIGINS
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS: if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
errors.append( errors.append(Warning("'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.", hint="", id="allianceauth.checks.B007"))
Warning(
msg="'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.",
hint="",
id="allianceauth.checks.B007",
)
)
# CSRF_TRUSTED_ORIGINS not found
else: else:
errors.append( errors.append(Error("No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified", hint="", id="allianceauth.checks.B008"))
Error(
msg="No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified",
hint="",
id="allianceauth.checks.B008",
)
)
# Check for ESI_USER_CONTACT_EMAIL
if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
# Check if ESI_USER_CONTACT_EMAIL is empty
if settings.ESI_USER_CONTACT_EMAIL == "":
errors.append(
Error(
msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
hint="",
id="allianceauth.checks.B009",
)
)
# ESI_USER_CONTACT_EMAIL not found
else:
errors.append(
Error(
msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
hint="",
id="allianceauth.checks.B010",
)
)
return errors return errors
@register() @register()
def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]: def system_package_redis(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that Redis is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools"
errors: List[CheckMessage] = []
try: try:
redis_version = Pep440Version(get_redis_client().info()["redis_version"]) redis_version = Pep440Version(get_redis_client().info()['redis_version'])
except InvalidVersion: except InvalidVersion:
errors.append(Warning("Unable to confirm Redis Version")) errors.append(Warning("Unable to confirm Redis Version"))
return errors return errors
if ( if redis_version.major == 7 and redis_version.minor == 2 and timezone.now() > timezone.datetime(year=2025, month=8, day=31, tzinfo=datetime.timezone.utc):
redis_version.major == 7 errors.append(Error(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint="https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools", id="allianceauth.checks.A001"))
and redis_version.minor == 2
and timezone.now()
> timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc)
):
errors.append(
Error(
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A001",
)
)
elif redis_version.major == 7 and redis_version.minor == 0: elif redis_version.major == 7 and redis_version.minor == 0:
errors.append( errors.append(Warning(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint="https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools", id="allianceauth.checks.A002"))
Warning(
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A002",
)
)
elif redis_version.major == 6 and redis_version.minor == 2: elif redis_version.major == 6 and redis_version.minor == 2:
errors.append( errors.append(Warning(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint="https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools", id="allianceauth.checks.A018"))
Warning(
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A018",
)
)
elif redis_version.major in [6, 5]: elif redis_version.major in [6, 5]:
errors.append( errors.append(Error(f"Redis {redis_version.public} EOL", hint="https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools", id="allianceauth.checks.A003"))
Error(
msg=f"Redis {redis_version.public} EOL",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A003",
)
)
return errors return errors
@register() @register()
def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]: def system_package_mysql(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that MySQL is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
mysql_quick_guide_link = "https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/"
errors: List[CheckMessage] = []
for connection in db.connections.all(): for connection in db.connections.all():
if connection.vendor == "mysql": if connection.vendor == "mysql":
try: try:
mysql_version = Pep440Version( mysql_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
".".join(str(i) for i in connection.mysql_version)
)
except InvalidVersion: except InvalidVersion:
errors.append(Warning("Unable to confirm MySQL Version")) errors.append(Warning("Unable to confirm MySQL Version"))
return errors return errors
# MySQL 8 # MySQL 8
if mysql_version.major == 8: if mysql_version.major == 8 and mysql_version.minor == 4 and timezone.now() > timezone.datetime(year=2032, month=4, day=30, tzinfo=datetime.timezone.utc):
if mysql_version.minor == 4 and timezone.now() > timezone.datetime( errors.append(Error(f"MySQL {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A004"))
year=2032, month=4, day=30, tzinfo=timezone.utc elif mysql_version.major == 8 and mysql_version.minor == 3:
): errors.append(Warning(f"MySQL {mysql_version.public} Non LTS", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A005"))
errors.append( elif mysql_version.major == 8 and mysql_version.minor == 2:
Error( errors.append(Warning(f"MySQL {mysql_version.public} Non LTS", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A006"))
msg=f"MySQL {mysql_version.public} EOL", elif mysql_version.major == 8 and mysql_version.minor == 1:
hint=mysql_quick_guide_link, errors.append(Error(f"MySQL {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A007"))
id="allianceauth.checks.A004", elif mysql_version.major == 8 and mysql_version.minor == 0 and timezone.now() > timezone.datetime(year=2026, month=4, day=30, tzinfo=datetime.timezone.utc):
) errors.append(Error(f"MySQL {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A008"))
) elif mysql_version.major < 8: # This will also catch Mariadb 5.x
elif mysql_version.minor == 3: errors.append(Error(f"MySQL or MariaDB {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A009"))
errors.append(
Warning(
msg=f"MySQL {mysql_version.public} Non LTS",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A005",
)
)
elif mysql_version.minor == 2:
errors.append(
Warning(
msg=f"MySQL {mysql_version.public} Non LTS",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A006",
)
)
elif mysql_version.minor == 1:
errors.append(
Error(
msg=f"MySQL {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A007",
)
)
elif mysql_version.minor == 0 and timezone.now() > timezone.datetime(
year=2026, month=4, day=30, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MySQL {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A008",
)
)
# MySQL below 8
# This will also catch Mariadb 5.x
elif mysql_version.major < 8:
errors.append(
Error(
msg=f"MySQL or MariaDB {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A009",
)
)
return errors return errors
@register() @register()
def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]: def system_package_mariadb(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that MariaDB is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
mariadb_download_link = "https://mariadb.org/download/?t=repo-config"
errors: List[CheckMessage] = []
for connection in db.connections.all(): for connection in db.connections.all():
# TODO: Find a way to determine MySQL vs. MariaDB if connection.vendor == "mysql": # Still to find a way to determine MySQL vs MariaDB
if connection.vendor == "mysql":
try: try:
mariadb_version = Pep440Version( mariadb_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
".".join(str(i) for i in connection.mysql_version)
)
except InvalidVersion: except InvalidVersion:
errors.append(Warning("Unable to confirm MariaDB Version")) errors.append(Warning("Unable to confirm MariaDB Version"))
return errors return errors
# MariaDB 11 # MariaDB 11
if mariadb_version.major == 11: if mariadb_version.major == 11 and mariadb_version.minor == 4 and timezone.now() > timezone.datetime(year=2029, month=5, day=19, tzinfo=datetime.timezone.utc):
if mariadb_version.minor == 4 and timezone.now() > timezone.datetime( errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A010"))
year=2029, month=5, day=19, tzinfo=timezone.utc elif mariadb_version.major == 11 and mariadb_version.minor == 2:
): errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A018"))
errors.append( if timezone.now() > timezone.datetime(year=2024, month=11, day=21, tzinfo=datetime.timezone.utc):
Error( errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A011"))
msg=f"MariaDB {mariadb_version.public} EOL", elif mariadb_version.major == 11 and mariadb_version.minor == 1:
hint=mariadb_download_link, errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A019"))
id="allianceauth.checks.A010", if timezone.now() > timezone.datetime(year=2024, month=8, day=21, tzinfo=datetime.timezone.utc):
) errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A012"))
) elif mariadb_version.major == 11 and mariadb_version.minor in [0, 3]: # Demote versions down here once EOL
elif mariadb_version.minor == 2: errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config.", id="allianceauth.checks.A013"))
errors.append(
Warning(
msg=f"MariaDB {mariadb_version.public} Non LTS",
hint=mariadb_download_link,
id="allianceauth.checks.A018",
)
)
if timezone.now() > timezone.datetime(
year=2024, month=11, day=21, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A011",
)
)
elif mariadb_version.minor == 1:
errors.append(
Warning(
msg=f"MariaDB {mariadb_version.public} Non LTS",
hint=mariadb_download_link,
id="allianceauth.checks.A019",
)
)
if timezone.now() > timezone.datetime(
year=2024, month=8, day=21, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A012",
)
)
# Demote versions down here once EOL
elif mariadb_version.minor in [0, 3]:
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A013",
)
)
# MariaDB 10 # MariaDB 10
elif mariadb_version.major == 10: elif mariadb_version.major == 10 and mariadb_version.minor == 11 and timezone.now() > timezone.datetime(year=2028, month=2, day=10, tzinfo=datetime.timezone.utc):
if mariadb_version.minor == 11 and timezone.now() > timezone.datetime( errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config.", id="allianceauth.checks.A014"))
year=2028, month=2, day=10, tzinfo=timezone.utc elif mariadb_version.major == 10 and mariadb_version.minor == 6 and timezone.now() > timezone.datetime(year=2026, month=7, day=6, tzinfo=datetime.timezone.utc):
): errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A0015"))
errors.append( elif mariadb_version.major == 10 and mariadb_version.minor == 5 and timezone.now() > timezone.datetime(year=2025, month=6, day=24, tzinfo=datetime.timezone.utc):
Error( errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A016"))
msg=f"MariaDB {mariadb_version.public} EOL", elif mariadb_version.major == 10 and mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]: # Demote versions down here once EOL
hint=mariadb_download_link, errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A017"))
id="allianceauth.checks.A014",
)
)
elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime(
year=2026, month=7, day=6, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A0015",
)
)
elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime(
year=2025, month=6, day=24, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A016",
)
)
# Demote versions down here once EOL
elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]:
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A017",
)
)
return errors return errors
@register() @register()
def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]: def system_package_sqlite(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that SQLite is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
for connection in db.connections.all(): for connection in db.connections.all():
if connection.vendor == "sqlite": if connection.vendor == "sqlite":
try: try:
sqlite_version = Pep440Version( sqlite_version = Pep440Version(".".join(str(i) for i in sqlite_version_info))
".".join(str(i) for i in sqlite_version_info)
)
except InvalidVersion: except InvalidVersion:
errors.append(Warning("Unable to confirm SQLite Version")) errors.append(Warning("Unable to confirm SQLite Version"))
return errors return errors
if sqlite_version.major == 3 and sqlite_version.minor < 27: if sqlite_version.major == 3 and sqlite_version.minor < 27:
errors.append( errors.append(Error(f"SQLite {sqlite_version.public} Unsupported by Django", hint="https://pkgs.org/download/sqlite3", id="allianceauth.checks.A020"))
Error(
msg=f"SQLite {sqlite_version.public} Unsupported by Django",
hint="https://pkgs.org/download/sqlite3",
id="allianceauth.checks.A020",
)
)
return errors return errors
@register() @register()
def sql_settings(app_configs, **kwargs) -> List[CheckMessage]: def sql_settings(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that SQL settings are correctly configured
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
for connection in db.connections.all(): for connection in db.connections.all():
if connection.vendor == "mysql": if connection.vendor == "mysql":
try: try:
if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4": if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
errors.append( errors.append(Error(f"SQL Charset is not set to utf8mb4 DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
Error(
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
id="allianceauth.checks.B001",
)
)
except KeyError: except KeyError:
errors.append( errors.append(Error(f"SQL Charset is not set to utf8mb4 DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
Error(
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
id="allianceauth.checks.B001",
)
)
# This hasn't actually been set on AA yet # This hasn't actually been set on AA yet
# try: # try:
# if ( # if connection.settings_dict["OPTIONS"]["collation"] != "utf8mb4_unicode_ci":
# connection.settings_dict["OPTIONS"]["collation"] # errors.append(Error(f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
# != "utf8mb4_unicode_ci"
# ):
# errors.append(
# Error(
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
# id="allianceauth.checks.B001",
# )
# )
# except KeyError: # except KeyError:
# errors.append( # errors.append(Error(f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
# Error(
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
# id="allianceauth.checks.B001",
# )
# )
# if connection.vendor == "sqlite": # if connection.vendor == "sqlite":
@@ -496,58 +158,20 @@ def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
@register() @register()
def celery_settings(app_configs, **kwargs) -> List[CheckMessage]: def celery_settings(app_configs, **kwargs) -> list[CheckMessage]:
""" errors: list[CheckMessage] = []
Check that Celery settings are correctly configured
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
try: try:
if current_app.conf.broker_transport_options != { if current_app.conf.broker_transport_options != {'priority_steps': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'queue_order_strategy': 'priority'}:
"priority_steps": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], errors.append(Error("Celery Priorities are not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
"queue_order_strategy": "priority",
}:
errors.append(
Error(
msg="Celery Priorities are not set correctly",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
id="allianceauth.checks.B003",
)
)
except KeyError: except KeyError:
errors.append( errors.append(Error("Celery Priorities are not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
Error(
msg="Celery Priorities are not set",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
id="allianceauth.checks.B003",
)
)
try: try:
if not current_app.conf.broker_connection_retry_on_startup: if current_app.conf.broker_connection_retry_on_startup != True:
errors.append( errors.append(Error("Celery broker_connection_retry_on_startup not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
Error(
msg="Celery broker_connection_retry_on_startup not set correctly",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
id="allianceauth.checks.B004",
)
)
except KeyError: except KeyError:
errors.append( errors.append(Error("Celery broker_connection_retry_on_startup not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
Error(
msg="Celery broker_connection_retry_on_startup not set",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
id="allianceauth.checks.B004",
)
)
return errors return errors

View File

@@ -1,6 +1,5 @@
{% extends 'corputils/base.html' %} {% extends 'corputils/base.html' %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
@@ -239,12 +238,9 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
$('#table-mains').DataTable({ $('#table-mains').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"columnDefs": [ "columnDefs": [
{ "sortable": false, "targets": [1] }, { "sortable": false, "targets": [1] },
], ],
@@ -253,7 +249,6 @@
}); });
$('#table-members').DataTable({ $('#table-members').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"columnDefs": [ "columnDefs": [
{ "searchable": false, "targets": [0, 2] }, { "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] }, { "sortable": false, "targets": [0, 2] },
@@ -264,7 +259,6 @@
}); });
$('#table-unregistered').DataTable({ $('#table-unregistered').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"columnDefs": [ "columnDefs": [
{ "searchable": false, "targets": [0, 2] }, { "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] }, { "sortable": false, "targets": [0, 2] },

View File

@@ -1,6 +1,5 @@
{% extends "corputils/base.html" %} {% extends "corputils/base.html" %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% block member_data %} {% block member_data %}
@@ -43,12 +42,9 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
$('#table-search').DataTable({ $('#table-search').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"stateSave": true, "stateSave": true,
"stateDuration": 0 "stateDuration": 0
}); });

View File

@@ -1,3 +0,0 @@
"""
Alliance Auth Crontab Utilities
"""

View File

@@ -1,14 +0,0 @@
"""
Crontab App Config
"""
from django.apps import AppConfig
class CrontabConfig(AppConfig):
"""
Crontab App Config
"""
name = "allianceauth.crontab"
label = "crontab"

View File

@@ -1,29 +0,0 @@
# Generated by Django 4.2.16 on 2025-01-20 06:16
import allianceauth.crontab.models
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='CronOffset',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('minute', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Minute Offset')),
('hour', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Hour Offset')),
('day_of_month', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Day of Month Offset')),
('month_of_year', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Month of Year Offset')),
('day_of_week', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Day of Week Offset')),
],
options={
'verbose_name': 'Cron Offsets',
},
),
]

View File

@@ -1,23 +0,0 @@
from random import random
from django.db import models
from django.utils.translation import gettext_lazy as _
from solo.models import SingletonModel
def random_default() -> float:
return random()
class CronOffset(SingletonModel):
minute = models.FloatField(_("Minute Offset"), default=random_default)
hour = models.FloatField(_("Hour Offset"), default=random_default)
day_of_month = models.FloatField(_("Day of Month Offset"), default=random_default)
month_of_year = models.FloatField(_("Month of Year Offset"), default=random_default)
day_of_week = models.FloatField(_("Day of Week Offset"), default=random_default)
def __str__(self) -> str:
return "Cron Offsets"
class Meta:
verbose_name = "Cron Offsets"

View File

@@ -1,70 +0,0 @@
from django.core.exceptions import ObjectDoesNotExist
from django_celery_beat.schedulers import (
DatabaseScheduler
)
from django_celery_beat.models import CrontabSchedule
from django.db.utils import OperationalError, ProgrammingError
from celery import schedules
from celery.utils.log import get_logger
from allianceauth.crontab.models import CronOffset
from allianceauth.crontab.utils import offset_cron
logger = get_logger(__name__)
class OffsetDatabaseScheduler(DatabaseScheduler):
"""
Customization of Django Celery Beat, Database Scheduler
Takes the Celery Schedule from local.py and applies our AA Framework Cron Offset, if apply_offset is true
Otherwise it passes it through as normal
"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
def update_from_dict(self, mapping):
s = {}
try:
cron_offset = CronOffset.get_solo()
except (OperationalError, ProgrammingError, ObjectDoesNotExist) as exc:
# This is just incase we haven't migrated yet or something
logger.warning(
"OffsetDatabaseScheduler: Could not fetch CronOffset (%r). "
"Defering to DatabaseScheduler",
exc
)
return super().update_from_dict(mapping)
for name, entry_fields in mapping.items():
try:
apply_offset = entry_fields.pop("apply_offset", False) # Ensure this pops before django tries to save to ORM
entry = self.Entry.from_entry(name, app=self.app, **entry_fields)
if apply_offset:
entry_fields.update({"apply_offset": apply_offset}) # Reapply this as its gets pulled from config inconsistently.
schedule_obj = entry.schedule
if isinstance(schedule_obj, schedules.crontab):
offset_cs = CrontabSchedule.from_schedule(offset_cron(schedule_obj))
offset_cs, created = CrontabSchedule.objects.get_or_create(
minute=offset_cs.minute,
hour=offset_cs.hour,
day_of_month=offset_cs.day_of_month,
month_of_year=offset_cs.month_of_year,
day_of_week=offset_cs.day_of_week,
timezone=offset_cs.timezone,
)
entry.schedule = offset_cron(schedule_obj) # This gets passed into Celery Beats Memory, important to keep it in sync with the model/DB
entry.model.crontab = offset_cs
entry.model.save()
logger.debug(f"Offset applied for '{name}' due to 'apply_offset' = True.")
if entry.model.enabled:
s[name] = entry
except Exception as e:
logger.exception("Error updating schedule for %s: %r", name, e)
self.schedule.update(s)

View File

@@ -1,63 +0,0 @@
from unittest.mock import patch
from django.test import TestCase
from allianceauth.crontab.models import CronOffset
class CronOffsetModelTest(TestCase):
def test_cron_offset_is_singleton(self):
"""
Test that CronOffset is indeed a singleton and that
multiple calls to get_solo() return the same instance.
"""
offset1 = CronOffset.get_solo()
offset2 = CronOffset.get_solo()
# They should be the exact same object in memory
self.assertEqual(offset1.pk, offset2.pk)
def test_default_values_random(self):
"""
Test that the default values are set via random_default() when
no explicit value is provided. We'll patch 'random.random' to
produce predictable output.
"""
with patch('allianceauth.crontab.models.random', return_value=0.1234):
# Force creation of a new CronOffset by clearing the existing one
CronOffset.objects.all().delete()
offset = CronOffset.get_solo() # This triggers creation
# All fields should be 0.1234, because we patched random()
self.assertAlmostEqual(offset.minute, 0.1234)
self.assertAlmostEqual(offset.hour, 0.1234)
self.assertAlmostEqual(offset.day_of_month, 0.1234)
self.assertAlmostEqual(offset.month_of_year, 0.1234)
self.assertAlmostEqual(offset.day_of_week, 0.1234)
def test_update_offset_values(self):
"""
Test that we can update the offsets and retrieve them.
"""
offset = CronOffset.get_solo()
offset.minute = 0.5
offset.hour = 0.25
offset.day_of_month = 0.75
offset.month_of_year = 0.99
offset.day_of_week = 0.33
offset.save()
# Retrieve again to ensure changes persist
saved_offset = CronOffset.get_solo()
self.assertEqual(saved_offset.minute, 0.5)
self.assertEqual(saved_offset.hour, 0.25)
self.assertEqual(saved_offset.day_of_month, 0.75)
self.assertEqual(saved_offset.month_of_year, 0.99)
self.assertEqual(saved_offset.day_of_week, 0.33)
def test_str_representation(self):
"""
Verify the __str__ method returns 'Cron Offsets'.
"""
offset = CronOffset.get_solo()
self.assertEqual(str(offset), "Cron Offsets")

View File

@@ -1,80 +0,0 @@
# myapp/tests/test_tasks.py
import logging
from unittest.mock import patch
from django.test import TestCase
from django.db import ProgrammingError
from celery.schedules import crontab
from allianceauth.crontab.utils import offset_cron
from allianceauth.crontab.models import CronOffset
logger = logging.getLogger(__name__)
class TestOffsetCron(TestCase):
def test_offset_cron_normal(self):
"""
Test that offset_cron modifies the minute/hour fields
based on the CronOffset values when everything is normal.
"""
# We'll create a mock CronOffset instance
mock_offset = CronOffset(minute=0.5, hour=0.5)
# Our initial crontab schedule
original_schedule = crontab(
minute=[0, 5, 55],
hour=[0, 3, 23],
day_of_month='*',
month_of_year='*',
day_of_week='*'
)
# Patch CronOffset.get_solo to return our mock offset
with patch('allianceauth.crontab.models.CronOffset.get_solo', return_value=mock_offset):
new_schedule = offset_cron(original_schedule)
# Check the new minute/hour
# minute 0 -> 0 + round(60 * 0.5) = 30 % 60 = 30
# minute 5 -> 5 + 30 = 35 % 60 = 35
# minute 55 -> 55 + 30 = 85 % 60 = 25 --> sorted => 25,30,35
self.assertEqual(new_schedule._orig_minute, '25,30,35')
# hour 0 -> 0 + round(24 * 0.5) = 12 % 24 = 12
# hour 3 -> 3 + 12 = 15 % 24 = 15
# hour 23 -> 23 + 12 = 35 % 24 = 11 --> sorted => 11,12,15
self.assertEqual(new_schedule._orig_hour, '11,12,15')
# Check that other fields are unchanged
self.assertEqual(new_schedule._orig_day_of_month, '*')
self.assertEqual(new_schedule._orig_month_of_year, '*')
self.assertEqual(new_schedule._orig_day_of_week, '*')
def test_offset_cron_programming_error(self):
"""
Test that if a ProgrammingError is raised (e.g. before migrations),
offset_cron just returns the original schedule.
"""
original_schedule = crontab(minute=[0, 15, 30], hour=[1, 2, 3])
# Force get_solo to raise ProgrammingError
with patch('allianceauth.crontab.models.CronOffset.get_solo', side_effect=ProgrammingError()):
new_schedule = offset_cron(original_schedule)
# Should return the original schedule unchanged
self.assertEqual(new_schedule, original_schedule)
def test_offset_cron_unexpected_exception(self):
"""
Test that if any other exception is raised, offset_cron
also returns the original schedule, and logs the error.
"""
original_schedule = crontab(minute='0', hour='0')
# Force get_solo to raise a generic Exception
with patch('allianceauth.crontab.models.CronOffset.get_solo', side_effect=Exception("Something bad")):
new_schedule = offset_cron(original_schedule)
# Should return the original schedule unchanged
self.assertEqual(new_schedule, original_schedule)

View File

@@ -1,50 +0,0 @@
from celery.schedules import crontab
import logging
from allianceauth.crontab.models import CronOffset
from django.db import ProgrammingError
logger = logging.getLogger(__name__)
def offset_cron(schedule: crontab) -> crontab:
"""Take a crontab and apply a series of precalculated offsets to spread out tasks execution on remote resources
Args:
schedule (crontab): celery.schedules.crontab()
Returns:
crontab: A crontab with offsetted Minute and Hour fields
"""
try:
cron_offset = CronOffset.get_solo()
# Stops this shit from happening 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23
# It is only cosmetic, but still annoying
if schedule._orig_minute == '*':
new_minute = '*'
else:
new_minute = [(m + (round(60 * cron_offset.minute))) % 60 for m in schedule.minute]
if schedule._orig_hour == '*':
new_hour = '*'
else:
new_hour = [(m + (round(24 * cron_offset.hour))) % 24 for m in schedule.hour]
return crontab(
minute=",".join(str(m) for m in sorted(new_minute)),
hour=",".join(str(h) for h in sorted(new_hour)),
day_of_month=schedule._orig_day_of_month,
month_of_year=schedule._orig_month_of_year,
day_of_week=schedule._orig_day_of_week)
except ProgrammingError as e:
# If this is called before migrations are run hand back the default schedule
# These offsets are stored in a Singleton Model,
logger.error(e)
return schedule
except Exception as e:
# We absolutely cant fail to hand back a schedule
logger.error(e)
return schedule

View File

@@ -1,3 +0,0 @@
"""
Initializes the custom_css module.
"""

View File

@@ -1,25 +0,0 @@
"""
Admin classes for custom_css app
"""
# Django
from django.contrib import admin
# Django Solos
from solo.admin import SingletonModelAdmin
# Alliance Auth Custom CSS
from allianceauth.custom_css.models import CustomCSS
from allianceauth.custom_css.forms import CustomCSSAdminForm
@admin.register(CustomCSS)
class CustomCSSAdmin(SingletonModelAdmin):
"""
Custom CSS Admin
"""
form = CustomCSSAdminForm
# Leave this here for when we decide to add syntax highlighting to the CSS editor
# change_form_template = 'custom_css/admin/change_form.html'

View File

@@ -1,13 +0,0 @@
"""
Django app configuration for custom_css
"""
# Django
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CustomCSSConfig(AppConfig):
name = "allianceauth.custom_css"
label = "custom_css"
verbose_name = _("Custom CSS")

View File

@@ -1,29 +0,0 @@
"""
Forms for custom_css app
"""
# Alliance Auth Custom CSS
from allianceauth.custom_css.models import CustomCSS
from allianceauth.custom_css.widgets import CssEditorWidget
# Django
from django import forms
class CustomCSSAdminForm(forms.ModelForm):
"""
Form for editing custom CSS
"""
class Meta:
model = CustomCSS
fields = ("css",)
widgets = {
"css": CssEditorWidget(
attrs={
"style": "width: 90%; height: 100%;",
"data-editor": "code-highlight",
"data-language": "css",
}
)
}

View File

@@ -1,42 +0,0 @@
# Generated by Django 4.2.15 on 2024-08-14 11:25
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="CustomCSS",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"css",
models.TextField(
blank=True,
help_text="This CSS will be added to the site after the default CSS.",
null=True,
verbose_name="Your custom CSS",
),
),
("timestamp", models.DateTimeField(auto_now=True)),
],
options={
"verbose_name": "Custom CSS",
"verbose_name_plural": "Custom CSS",
"default_permissions": (),
},
),
]

View File

@@ -1,143 +0,0 @@
"""
Models for the custom_css app
"""
import os
import re
# Django Solo
from solo.models import SingletonModel
# Django
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
class CustomCSS(SingletonModel):
"""
Model for storing custom CSS for the site
"""
css = models.TextField(
blank=True,
null=True,
verbose_name=_("Your custom CSS"),
help_text=_("This CSS will be added to the site after the default CSS."),
)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
"""
Meta for CustomCSS
"""
default_permissions = ()
verbose_name = _("Custom CSS")
verbose_name_plural = _("Custom CSS")
def __str__(self) -> str:
"""
String representation of CustomCSS
:return:
:rtype:
"""
return str(_("Custom CSS"))
def save(self, *args, **kwargs):
"""
Save method for CustomCSS
:param args:
:type args:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
self.pk = 1
if self.css and len(self.css.replace(" ", "")) > 0:
# Write the custom CSS to a file
custom_css_file = open(
f"{settings.STATIC_ROOT}allianceauth/custom-styles.css", "w+"
)
custom_css_file.write(self.compress_css())
custom_css_file.close()
else:
# Remove the custom CSS file
try:
os.remove(f"{settings.STATIC_ROOT}allianceauth/custom-styles.css")
except FileNotFoundError:
pass
super().save(*args, **kwargs)
def compress_css(self) -> str:
"""
Compress CSS
:return:
:rtype:
"""
css = self.css
new_css = ""
# Remove comments
css = re.sub(pattern=r"\s*/\*\s*\*/", repl="$$HACK1$$", string=css)
css = re.sub(pattern=r"/\*[\s\S]*?\*/", repl="", string=css)
css = css.replace("$$HACK1$$", "/**/")
# url() doesn't need quotes
css = re.sub(pattern=r'url\((["\'])([^)]*)\1\)', repl=r"url(\2)", string=css)
# Spaces may be safely collapsed as generated content will collapse them anyway.
css = re.sub(pattern=r"\s+", repl=" ", string=css)
# Shorten collapsable colors: #aabbcc to #abc
css = re.sub(
pattern=r"#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3(\s|;)",
repl=r"#\1\2\3\4",
string=css,
)
# Fragment values can loose zeros
css = re.sub(
pattern=r":\s*0(\.\d+([cm]m|e[mx]|in|p[ctx]))\s*;", repl=r":\1;", string=css
)
for rule in re.findall(pattern=r"([^{]+){([^}]*)}", string=css):
# We don't need spaces around operators
selectors = [
re.sub(
pattern=r"(?<=[\[\(>+=])\s+|\s+(?=[=~^$*|>+\]\)])",
repl=r"",
string=selector.strip(),
)
for selector in rule[0].split(",")
]
# Order is important, but we still want to discard repetitions
properties = {}
porder = []
for prop in re.findall(pattern="(.*?):(.*?)(;|$)", string=rule[1]):
key = prop[0].strip().lower()
if key not in porder:
porder.append(key)
properties[key] = prop[1].strip()
# output rule if it contains any declarations
if properties:
new_css += "{}{{{}}}".format(
",".join(selectors),
"".join([f"{key}:{properties[key]};" for key in porder])[:-1],
)
return new_css

View File

@@ -1,48 +0,0 @@
{% extends "admin/change_form.html" %}
{% block field_sets %}
{% for fieldset in adminform %}
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %}
{% for field in line %}
<div>
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
<div class="flex-container{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% elif field.is_checkbox %} checkbox-row{% endif %}">
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
</div>
{% if field.field.help_text %}
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
<div>{{ field.field.help_text|safe }}</div>
</div>
{% endif %}
</div>
{% endfor %}
{% if not line.fields|length == 1 %}</div>{% endif %}
</div>
{% endfor %}
</fieldset>
{% endfor %}
{% endblock %}
{% block after_field_sets %}{% endblock %}

View File

@@ -1,3 +0,0 @@
{% load custom_css %}
{% custom_css_static 'allianceauth/custom-styles.css' %}

View File

@@ -1,3 +0,0 @@
"""
Init file for custom_css templatetags
"""

View File

@@ -1,48 +0,0 @@
"""
Custom template tags for custom_css app
"""
# Alliance Auth Custom CSS
from allianceauth.custom_css.models import CustomCSS
# Django
from django.conf import settings
from django.template.defaulttags import register
from django.templatetags.static import static
from django.utils.safestring import mark_safe
from pathlib import Path
@register.simple_tag
def custom_css_static(path: str) -> str:
"""
Versioned static URL
This is to make sure to break the browser cache on CSS updates.
Example: /static/allianceauth/custom-styles.css?v=1234567890
:param path:
:type path:
:return:
:rtype:
"""
try:
Path(f"{settings.STATIC_ROOT}{path}").resolve(strict=True)
except FileNotFoundError:
return ""
else:
try:
custom_css = CustomCSS.objects.get(pk=1)
except CustomCSS.DoesNotExist:
return ""
else:
custom_css_changed = custom_css.timestamp.timestamp()
custom_css_version = (
str(custom_css_changed).replace(" ", "").replace(":", "").replace("-", "")
) # remove spaces, colons, and dashes
static_url = static(path)
versioned_url = static_url + "?v=" + custom_css_version
return mark_safe(f'<link rel="stylesheet" href="{versioned_url}">')

View File

@@ -1,38 +0,0 @@
"""
Form widgets for custom_css app
"""
# Django
from django import forms
# Alliance Auth
from allianceauth.custom_css.models import CustomCSS
class CssEditorWidget(forms.Textarea):
"""
Widget for editing CSS
"""
def __init__(self, attrs=None):
default_attrs = {"class": "custom-css-editor"}
if attrs:
default_attrs.update(attrs)
super().__init__(default_attrs)
# For when we want to add some sort of syntax highlight to it, which is not that
# easy to do on a textarea field though.
# `highlight.js` is just used as an example here, and doesn't work on a textarea field.
# class Media:
# css = {
# "all": (
# "/static/custom_css/libs/highlight.js/11.10.0/styles/github.min.css",
# )
# }
# js = (
# "/static/custom_css/libs/highlight.js/11.10.0/highlight.min.js",
# "/static/custom_css/libs/highlight.js/11.10.0/languages/css.min.js",
# "/static/custom_css/javascript/custom-css.min.js",
# )

View File

@@ -10,7 +10,7 @@ from . import (
) )
_BASE_URL = 'https://evemaps.dotlan.net' _BASE_URL = 'http://evemaps.dotlan.net'
def _build_url(category: str, name: str) -> str: def _build_url(category: str, name: str) -> str:

View File

@@ -31,29 +31,29 @@ class TestDotlan(TestCase):
def test_alliance_url(self): def test_alliance_url(self):
self.assertEqual( self.assertEqual(
dotlan.alliance_url('Wayne Enterprices'), dotlan.alliance_url('Wayne Enterprices'),
'https://evemaps.dotlan.net/alliance/Wayne_Enterprices' 'http://evemaps.dotlan.net/alliance/Wayne_Enterprices'
) )
def test_corporation_url(self): def test_corporation_url(self):
self.assertEqual( self.assertEqual(
dotlan.corporation_url('Wayne Technology'), dotlan.corporation_url('Wayne Technology'),
'https://evemaps.dotlan.net/corp/Wayne_Technology' 'http://evemaps.dotlan.net/corp/Wayne_Technology'
) )
self.assertEqual( self.assertEqual(
dotlan.corporation_url('Crédit Agricole'), dotlan.corporation_url('Crédit Agricole'),
'https://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole' 'http://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole'
) )
def test_region_url(self): def test_region_url(self):
self.assertEqual( self.assertEqual(
dotlan.region_url('Black Rise'), dotlan.region_url('Black Rise'),
'https://evemaps.dotlan.net/map/Black_Rise' 'http://evemaps.dotlan.net/map/Black_Rise'
) )
def test_solar_system_url(self): def test_solar_system_url(self):
self.assertEqual( self.assertEqual(
dotlan.solar_system_url('Jita'), dotlan.solar_system_url('Jita'),
'https://evemaps.dotlan.net/system/Jita' 'http://evemaps.dotlan.net/system/Jita'
) )

View File

@@ -235,7 +235,7 @@ class EveCharacter(models.Model):
return self.corporation_id == DOOMHEIM_CORPORATION_ID return self.corporation_id == DOOMHEIM_CORPORATION_ID
@property @property
def alliance(self) -> Union[EveAllianceInfo, None]: def alliance(self) -> EveAllianceInfo | None:
""" """
Pseudo foreign key from alliance_id to EveAllianceInfo Pseudo foreign key from alliance_id to EveAllianceInfo
:raises: EveAllianceInfo.DoesNotExist :raises: EveAllianceInfo.DoesNotExist
@@ -255,7 +255,7 @@ class EveCharacter(models.Model):
return EveCorporationInfo.objects.get(corporation_id=self.corporation_id) return EveCorporationInfo.objects.get(corporation_id=self.corporation_id)
@property @property
def faction(self) -> Union[EveFactionInfo, None]: def faction(self) -> EveFactionInfo | None:
""" """
Pseudo foreign key from faction_id to EveFactionInfo Pseudo foreign key from faction_id to EveFactionInfo
:raises: EveFactionInfo.DoesNotExist :raises: EveFactionInfo.DoesNotExist

View File

@@ -1,5 +1,4 @@
import logging import logging
from random import randint
from celery import shared_task from celery import shared_task
@@ -10,8 +9,7 @@ from . import providers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TASK_PRIORITY = 7 TASK_PRIORITY = 7
CHARACTER_AFFILIATION_CHUNK_SIZE = 500 CHUNK_SIZE = 500
EVEONLINE_TASK_JITTER = 600
def chunks(lst, n): def chunks(lst, n):
@@ -21,13 +19,13 @@ def chunks(lst, n):
@shared_task @shared_task
def update_corp(corp_id: int) -> None: def update_corp(corp_id):
"""Update given corporation from ESI""" """Update given corporation from ESI"""
EveCorporationInfo.objects.update_corporation(corp_id) EveCorporationInfo.objects.update_corporation(corp_id)
@shared_task @shared_task
def update_alliance(alliance_id: int) -> None: def update_alliance(alliance_id):
"""Update given alliance from ESI""" """Update given alliance from ESI"""
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance() EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
@@ -39,30 +37,23 @@ def update_character(character_id: int) -> None:
@shared_task @shared_task
def run_model_update() -> None: def run_model_update():
"""Update all alliances, corporations and characters from ESI""" """Update all alliances, corporations and characters from ESI"""
# Queue update tasks for Known Corporation Models #update existing corp models
for corp in EveCorporationInfo.objects.all().values('corporation_id'): for corp in EveCorporationInfo.objects.all().values('corporation_id'):
update_corp.apply_async( update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
args=[corp['corporation_id']],
priority=TASK_PRIORITY,
countdown=randint(1, EVEONLINE_TASK_JITTER))
# Queue update tasks for Known Alliance Models # update existing alliance models
for alliance in EveAllianceInfo.objects.all().values('alliance_id'): for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
update_alliance.apply_async( update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
args=[alliance['alliance_id']],
priority=TASK_PRIORITY,
countdown=randint(1, EVEONLINE_TASK_JITTER))
# Queue update tasks for Known Character Models # update existing character models
character_ids = EveCharacter.objects.all().values_list('character_id', flat=True) character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
for character_ids_chunk in chunks(character_ids, CHARACTER_AFFILIATION_CHUNK_SIZE): for character_ids_chunk in chunks(character_ids, CHUNK_SIZE):
update_character_chunk.apply_async( update_character_chunk.apply_async(
args=[character_ids_chunk], args=[character_ids_chunk], priority=TASK_PRIORITY
priority=TASK_PRIORITY, )
countdown=randint(1, EVEONLINE_TASK_JITTER))
@shared_task @shared_task
@@ -77,9 +68,8 @@ def update_character_chunk(character_ids_chunk: list):
logger.info("Failed to bulk update characters. Attempting single updates") logger.info("Failed to bulk update characters. Attempting single updates")
for character_id in character_ids_chunk: for character_id in character_ids_chunk:
update_character.apply_async( update_character.apply_async(
args=[character_id], args=[character_id], priority=TASK_PRIORITY
priority=TASK_PRIORITY, )
countdown=randint(1, EVEONLINE_TASK_JITTER))
return return
affiliations = { affiliations = {
@@ -117,5 +107,5 @@ def update_character_chunk(character_ids_chunk: list):
if corp_changed or alliance_changed or name_changed: if corp_changed or alliance_changed or name_changed:
update_character.apply_async( update_character.apply_async(
args=[character.get('character_id')], args=[character.get('character_id')], priority=TASK_PRIORITY
priority=TASK_PRIORITY) )

View File

@@ -723,5 +723,5 @@ class TestEveSwaggerProvider(TestCase):
my_client = my_provider.client my_client = my_provider.client
operation = my_client.Universe.get_universe_factions() operation = my_client.Universe.get_universe_factions()
self.assertEqual( self.assertEqual(
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0 dummy@example.net' operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0'
) )

View File

@@ -84,7 +84,7 @@ class TestUpdateTasks(TestCase):
@override_settings(CELERY_ALWAYS_EAGER=True) @override_settings(CELERY_ALWAYS_EAGER=True)
@patch('allianceauth.eveonline.providers.esi_client_factory') @patch('allianceauth.eveonline.providers.esi_client_factory')
@patch('allianceauth.eveonline.tasks.providers') @patch('allianceauth.eveonline.tasks.providers')
@patch('allianceauth.eveonline.tasks.CHARACTER_AFFILIATION_CHUNK_SIZE', 2) @patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
class TestRunModelUpdate(TransactionTestCase): class TestRunModelUpdate(TransactionTestCase):
def test_should_run_updates(self, mock_providers, mock_esi_client_factory): def test_should_run_updates(self, mock_providers, mock_esi_client_factory):
# given # given
@@ -139,7 +139,7 @@ class TestRunModelUpdate(TransactionTestCase):
@patch('allianceauth.eveonline.tasks.update_character', wraps=update_character) @patch('allianceauth.eveonline.tasks.update_character', wraps=update_character)
@patch('allianceauth.eveonline.providers.esi_client_factory') @patch('allianceauth.eveonline.providers.esi_client_factory')
@patch('allianceauth.eveonline.tasks.providers') @patch('allianceauth.eveonline.tasks.providers')
@patch('allianceauth.eveonline.tasks.CHARACTER_AFFILIATION_CHUNK_SIZE', 2) @patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
class TestUpdateCharacterChunk(TestCase): class TestUpdateCharacterChunk(TestCase):
@staticmethod @staticmethod
def _updated_character_ids(spy_update_character) -> set: def _updated_character_ids(spy_update_character) -> set:

View File

@@ -13,7 +13,7 @@ from allianceauth.framework.api.user import get_sentinel_user
def get_main_character_from_evecharacter( def get_main_character_from_evecharacter(
character: EveCharacter, character: EveCharacter,
) -> Optional[EveCharacter]: ) -> EveCharacter | None:
""" """
Get the main character for a given EveCharacter or None when no main character is set Get the main character for a given EveCharacter or None when no main character is set

View File

@@ -34,7 +34,7 @@ def get_all_characters_from_user(user: User) -> list:
return characters return characters
def get_main_character_from_user(user: User) -> Optional[EveCharacter]: def get_main_character_from_user(user: User) -> EveCharacter | None:
""" """
Get the main character from a user Get the main character from a user

View File

@@ -13,15 +13,6 @@
} }
} }
/* Image overflow fix
------------------------------------------------------------------------------------- */
@media all {
img {
max-width: 100%;
height: auto;
}
}
/* Side Navigation /* Side Navigation
------------------------------------------------------------------------------------- */ ------------------------------------------------------------------------------------- */
@media all { @media all {
@@ -49,14 +40,14 @@
} }
/* Chevron icons */ /* Chevron icons */
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-down, #sidebar-menu [data-bs-toggle="collapse"] > i.fa-chevron-down,
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-right { #sidebar-menu [data-bs-toggle="collapse"].collapsed > i.fa-chevron-right {
display: block; display: block;
width: 16px; width: 16px;
} }
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-right, #sidebar-menu [data-bs-toggle="collapse"] > i.fa-chevron-right,
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-down { #sidebar-menu [data-bs-toggle="collapse"].collapsed > i.fa-chevron-down {
display: none; display: none;
} }
} }

View File

@@ -49,7 +49,7 @@ class GroupsMenuItem(MenuItemHook):
MenuItemHook.__init__( MenuItemHook.__init__(
self, self,
text=_("Groups"), text=_("Groups"),
classes="fa-solid fa-users", classes="fa-solid fa-user",
url_name="groupmanagement:groups", url_name="groupmanagement:groups",
order=25, order=25,
navactive=[ navactive=[

View File

@@ -181,7 +181,7 @@ class AuthGroup(models.Model):
def __str__(self): def __str__(self):
return self.group.name return self.group.name
def group_request_approvers(self) -> Set[User]: def group_request_approvers(self) -> set[User]:
"""Return all users who can approve a group request.""" """Return all users who can approve a group request."""
return set( return set(
self.group_leaders.all() self.group_leaders.all()

View File

@@ -1,6 +1,5 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load aa_i18n %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load navactive %} {% load navactive %}
@@ -74,8 +73,6 @@
{% include 'bundles/moment-js.html' with locale=True %} {% include 'bundles/moment-js.html' with locale=True %}
{% include 'bundles/filterdropdown-js.html' %} {% include 'bundles/filterdropdown-js.html' %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$.fn.dataTable.moment = (format, locale) => { $.fn.dataTable.moment = (format, locale) => {
const types = $.fn.dataTable.ext.type; const types = $.fn.dataTable.ext.type;
@@ -97,7 +94,6 @@
$.fn.dataTable.moment('YYYY-MMM-D, HH:mm'); $.fn.dataTable.moment('YYYY-MMM-D, HH:mm');
$('#log-entries').DataTable({ $('#log-entries').DataTable({
language: {url: '{{ DT_LANG_PATH }}'},
order: [[0, 'desc'], [1, 'asc']], order: [[0, 'desc'], [1, 'asc']],
filterDropDown: filterDropDown:
{ {

View File

@@ -1,7 +1,6 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %} {% load static %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% load evelinks %} {% load evelinks %}
{% load navactive %} {% load navactive %}
@@ -87,12 +86,9 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
$('#tab_group_members').DataTable({ $('#tab_group_members').DataTable({
language: {url: '{{ DT_LANG_PATH }}'},
order: [[0, "asc"]], order: [[0, "asc"]],
columnDefs: [ columnDefs: [
{ {

View File

@@ -1,7 +1,6 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %} {% load static %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
@@ -57,7 +56,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if g.group.authgroup.group_leader_groups.all.count %} {% if g.group.authgroup.group_leaders.all.count %}
{% for group in g.group.authgroup.group_leader_groups.all %} {% for group in g.group.authgroup.group_leader_groups.all %}
<span class="my-1 me-1 badge bg-secondary">{{group.name}}</span> <span class="my-1 me-1 badge bg-secondary">{{group.name}}</span>
{% endfor %} {% endfor %}
@@ -104,13 +103,9 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
$('#groupsTable').DataTable({ $('#groupsTable').DataTable();
language: {url: '{{ DT_LANG_PATH }}'},
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -5,7 +5,7 @@ from typing import Optional
class ApplicationManager(models.Manager): class ApplicationManager(models.Manager):
def pending_requests_count_for_user(self, user: User) -> Optional[int]: def pending_requests_count_for_user(self, user: User) -> int | None:
"""Returns the number of pending group requests for the given user""" """Returns the number of pending group requests for the given user"""
if user.is_superuser: if user.is_superuser:
return self.filter(approved__isnull=True).count() return self.filter(approved__isnull=True).count()

Binary file not shown.

View File

@@ -11,14 +11,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-13 19:57+1000\n" "POT-Creation-Date: 2024-05-12 19:15+1000\n"
"PO-Revision-Date: 2023-11-08 13:50+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: Tomas Skarecky <t.skarecky@gmail.com>, 2024\n" "Last-Translator: Tomas Skarecky <t.skarecky@gmail.com>, 2024\n"
"Language-Team: Czech (Czech Republic) (https://app.transifex.com/alliance-auth/teams/107430/cs_CZ/)\n" "Language-Team: Czech (https://app.transifex.com/alliance-auth/teams/107430/cs/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: cs_CZ\n" "Language: cs\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#: allianceauth/analytics/models.py:26 #: allianceauth/analytics/models.py:26
@@ -54,90 +54,66 @@ msgstr ""
"přístupem:%s" "přístupem:%s"
#: allianceauth/authentication/models.py:71 #: allianceauth/authentication/models.py:71
#: allianceauth/project_template/project_name/settings/base.py:99
msgid "English" msgid "English"
msgstr "Angličtina" msgstr "Angličtina"
#: allianceauth/authentication/models.py:72 #: allianceauth/authentication/models.py:72
msgid "Czech"
msgstr ""
#: allianceauth/authentication/models.py:73
#: allianceauth/project_template/project_name/settings/base.py:101
msgid "German" msgid "German"
msgstr "Němčina" msgstr "Němčina"
#: allianceauth/authentication/models.py:74 #: allianceauth/authentication/models.py:73
#: allianceauth/project_template/project_name/settings/base.py:102
msgid "Spanish" msgid "Spanish"
msgstr "Španělština" msgstr "Španělština"
#: allianceauth/authentication/models.py:74
msgid "Chinese Simplified"
msgstr "Zjednodušená čínština"
#: allianceauth/authentication/models.py:75 #: allianceauth/authentication/models.py:75
#: allianceauth/project_template/project_name/settings/base.py:103
msgid "Italian"
msgstr "Italština"
#: allianceauth/authentication/models.py:76
#: allianceauth/project_template/project_name/settings/base.py:104
msgid "Japanese"
msgstr "Japonština"
#: allianceauth/authentication/models.py:77
#: allianceauth/project_template/project_name/settings/base.py:105
msgid "Korean"
msgstr "Korejština"
#: allianceauth/authentication/models.py:78
#: allianceauth/project_template/project_name/settings/base.py:106
msgid "French"
msgstr "Francouzština"
#: allianceauth/authentication/models.py:79
#: allianceauth/project_template/project_name/settings/base.py:109
msgid "Russian" msgid "Russian"
msgstr "Ruština" msgstr "Ruština"
#: allianceauth/authentication/models.py:76
msgid "Korean"
msgstr "Korejština"
#: allianceauth/authentication/models.py:77
msgid "French"
msgstr "Francouzština"
#: allianceauth/authentication/models.py:78
msgid "Japanese"
msgstr "Japonština"
#: allianceauth/authentication/models.py:79
msgid "Italian"
msgstr "Italština"
#: allianceauth/authentication/models.py:80 #: allianceauth/authentication/models.py:80
#: allianceauth/project_template/project_name/settings/base.py:107
msgid "Dutch"
msgstr ""
#: allianceauth/authentication/models.py:81
#: allianceauth/project_template/project_name/settings/base.py:108
msgid "Polish"
msgstr ""
#: allianceauth/authentication/models.py:82
#: allianceauth/project_template/project_name/settings/base.py:110
msgid "Ukrainian" msgid "Ukrainian"
msgstr "Ukrajinština" msgstr "Ukrajinština"
#: allianceauth/authentication/models.py:83 #: allianceauth/authentication/models.py:96
#: allianceauth/project_template/project_name/settings/base.py:111
msgid "Simplified Chinese"
msgstr ""
#: allianceauth/authentication/models.py:99
#: allianceauth/menu/templates/menu/menu-user.html:42 #: allianceauth/menu/templates/menu/menu-user.html:42
msgid "Language" msgid "Language"
msgstr "Jazyk" msgstr "Jazyk"
#: allianceauth/authentication/models.py:104 #: allianceauth/authentication/models.py:101
#: allianceauth/templates/allianceauth/night-toggle.html:6 #: allianceauth/templates/allianceauth/night-toggle.html:6
msgid "Night Mode" msgid "Night Mode"
msgstr "Noční režim" msgstr "Noční režim"
#: allianceauth/authentication/models.py:108 #: allianceauth/authentication/models.py:105
#: allianceauth/menu/templates/menu/menu-user.html:46 #: allianceauth/menu/templates/menu/menu-user.html:46
msgid "Theme" msgid "Theme"
msgstr "Motiv" msgstr "Motiv"
#: allianceauth/authentication/models.py:125 #: allianceauth/authentication/models.py:122
#, python-format #, python-format
msgid "State changed to: %s" msgid "State changed to: %s"
msgstr "Status změněn na: %s" msgstr "Status změněn na: %s"
#: allianceauth/authentication/models.py:126 #: allianceauth/authentication/models.py:123
#, python-format #, python-format
msgid "Your user's state is now: %(state)s" msgid "Your user's state is now: %(state)s"
msgstr "Váš uživatelský status je nyní: %(state)s" msgstr "Váš uživatelský status je nyní: %(state)s"
@@ -149,27 +125,27 @@ msgstr "Váš uživatelský status je nyní: %(state)s"
msgid "Dashboard" msgid "Dashboard"
msgstr "" msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:5 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:7
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33
#: allianceauth/hrapplications/templates/hrapplications/view.html:54 #: allianceauth/hrapplications/templates/hrapplications/view.html:54
msgid "Characters" msgid "Characters"
msgstr "Postavy" msgstr "Postavy"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:11 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:13
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:12 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:4 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:4
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:6 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:6
msgid "Add Character" msgid "Add Character"
msgstr "Přidat postavu" msgstr "Přidat postavu"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:16
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:15 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:17
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:10 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:10
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:12 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:12
msgid "Change Main" msgid "Change Main"
msgstr "Změnit postavu" msgstr "Změnit postavu"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:22 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31
@@ -178,12 +154,12 @@ msgstr "Změnit postavu"
msgid "Name" msgid "Name"
msgstr "Jméno" msgstr "Jméno"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:23 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:25
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33
msgid "Corp" msgid "Corp"
msgstr "Korporace" msgstr "Korporace"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:26
#: allianceauth/corputils/templates/corputils/corpstats.html:125 #: allianceauth/corputils/templates/corputils/corpstats.html:125
#: allianceauth/hrapplications/templates/hrapplications/view.html:63 #: allianceauth/hrapplications/templates/hrapplications/view.html:63
msgid "Alliance" msgid "Alliance"
@@ -193,7 +169,7 @@ msgstr "Aliance"
msgid "Membership" msgid "Membership"
msgstr "Členství" msgstr "Členství"
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:10 #: allianceauth/authentication/templates/authentication/dashboard_groups.html:8
msgid "State:" msgid "State:"
msgstr "Stav:" msgstr "Stav:"
@@ -433,19 +409,6 @@ msgstr "Vybraná korporace již má statistický modul"
msgid "Failed to gather corporation statistics with selected token." msgid "Failed to gather corporation statistics with selected token."
msgstr "Nepodařilo se získat statistiky korporace pomocí vybraného tokenu." msgstr "Nepodařilo se získat statistiky korporace pomocí vybraného tokenu."
#: allianceauth/custom_css/apps.py:13 allianceauth/custom_css/models.py:36
#: allianceauth/custom_css/models.py:37 allianceauth/custom_css/models.py:47
msgid "Custom CSS"
msgstr ""
#: allianceauth/custom_css/models.py:25
msgid "Your custom CSS"
msgstr ""
#: allianceauth/custom_css/models.py:26
msgid "This CSS will be added to the site after the default CSS."
msgstr ""
#: allianceauth/fleetactivitytracking/auth_hooks.py:10 #: allianceauth/fleetactivitytracking/auth_hooks.py:10
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11
@@ -538,8 +501,8 @@ msgstr "Uživatel"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:37 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:37
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:42 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:42
#: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:37 #: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:59
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16
#: allianceauth/timerboard/templates/timerboard/timertable.html:9 #: allianceauth/timerboard/templates/timerboard/timertable.html:9
msgid "System" msgid "System"
msgstr "Systém" msgstr "Systém"
@@ -863,7 +826,7 @@ msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:15 #: allianceauth/optimer/templates/optimer/dashboard.ops.html:15
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
msgid "Type" msgid "Type"
msgstr "" msgstr ""
@@ -955,7 +918,7 @@ msgid "Hidden"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:16 #: allianceauth/templates/allianceauth/admin-status/overview.html:19
msgid "Open" msgid "Open"
msgstr "" msgstr ""
@@ -1431,16 +1394,16 @@ msgstr ""
msgid "Super User" msgid "Super User"
msgstr "" msgstr ""
#: allianceauth/menu/templates/menu/menu-user.html:70 #: allianceauth/menu/templates/menu/menu-user.html:68
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
msgstr "" msgstr ""
#: allianceauth/menu/templates/menu/menu-user.html:82 #: allianceauth/menu/templates/menu/menu-user.html:80
msgid "Sign Out" msgid "Sign Out"
msgstr "" msgstr ""
#: allianceauth/menu/templates/menu/menu-user.html:86 #: allianceauth/menu/templates/menu/menu-user.html:84
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:17 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:17
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:18 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:18
msgid "Sign In" msgid "Sign In"
@@ -1590,7 +1553,7 @@ msgid "Form Up System"
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17 #: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:18 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
msgid "EVE Time" msgid "EVE Time"
msgstr "" msgstr ""
@@ -1785,17 +1748,17 @@ msgid ""
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:26 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:26
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:22 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:20
msgid "Activate" msgid "Activate"
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:34 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:32
msgid "Reset Password" msgid "Reset Password"
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:38 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:38
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:40 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:38
msgid "Deactivate" msgid "Deactivate"
msgstr "" msgstr ""
@@ -1876,12 +1839,12 @@ msgstr ""
msgid "Deactivated IPSuite4 account." msgid "Deactivated IPSuite4 account."
msgstr "" msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:28 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:26
#: allianceauth/services/templates/services/service_password.html:26 #: allianceauth/services/templates/services/service_password.html:26
msgid "Set Password" msgid "Set Password"
msgstr "" msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:46 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:44
msgid "Connect" msgid "Connect"
msgstr "" msgstr ""
@@ -2444,56 +2407,56 @@ msgstr ""
msgid "Your Server received an ESI error response code of " msgid "Your Server received an ESI error response code of "
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:8 #: allianceauth/templates/allianceauth/admin-status/overview.html:11
msgid "Alliance Auth Notifications" msgid "Alliance Auth Notifications"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:18 #: allianceauth/templates/allianceauth/admin-status/overview.html:21
msgid "Closed" msgid "Closed"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:24 #: allianceauth/templates/allianceauth/admin-status/overview.html:27
msgid "No notifications at this time" msgid "No notifications at this time"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:33 #: allianceauth/templates/allianceauth/admin-status/overview.html:36
msgid "Powered by GitLab" msgid "Powered by GitLab"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:39 #: allianceauth/templates/allianceauth/admin-status/overview.html:42
msgid "Support Discord" msgid "Support Discord"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:53 #: allianceauth/templates/allianceauth/admin-status/overview.html:59
#: allianceauth/templates/allianceauth/admin-status/overview.html:57 #: allianceauth/templates/allianceauth/admin-status/overview.html:63
msgid "Software Version" msgid "Software Version"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:60 #: allianceauth/templates/allianceauth/admin-status/overview.html:66
msgid "Current" msgid "Current"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:67 #: allianceauth/templates/allianceauth/admin-status/overview.html:73
msgid "Latest Stable" msgid "Latest Stable"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:72 #: allianceauth/templates/allianceauth/admin-status/overview.html:78
msgid "Update available" msgid "Update available"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:80 #: allianceauth/templates/allianceauth/admin-status/overview.html:86
msgid "Latest Pre-Release" msgid "Latest Pre-Release"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:85 #: allianceauth/templates/allianceauth/admin-status/overview.html:91
msgid "Pre-Release available" msgid "Pre-Release available"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:95 #: allianceauth/templates/allianceauth/admin-status/overview.html:102
msgid "Task Queue" msgid "Task Queue"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:100 #: allianceauth/templates/allianceauth/admin-status/overview.html:107
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@@ -2501,11 +2464,11 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:116 #: allianceauth/templates/allianceauth/admin-status/overview.html:123
msgid "running" msgid "running"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:117 #: allianceauth/templates/allianceauth/admin-status/overview.html:124
msgid "queued" msgid "queued"
msgstr "" msgstr ""
@@ -2534,216 +2497,114 @@ msgstr ""
msgid "Select Theme" msgid "Select Theme"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:36 #: allianceauth/timerboard/form.py:53
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14 #: allianceauth/timerboard/templates/timerboard/timertable.html:172
msgid "Other"
msgstr ""
#: allianceauth/timerboard/form.py:54
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:38
#: allianceauth/timerboard/templates/timerboard/timertable.html:44
msgid "Friendly"
msgstr ""
#: allianceauth/timerboard/form.py:55
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:33
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
msgid "Hostile"
msgstr ""
#: allianceauth/timerboard/form.py:56
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:43
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
msgid "Neutral"
msgstr ""
#: allianceauth/timerboard/form.py:58
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:13
#: allianceauth/timerboard/templates/timerboard/timertable.html:7 #: allianceauth/timerboard/templates/timerboard/timertable.html:7
msgid "Details" msgid "Details"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:38 #: allianceauth/timerboard/form.py:60
msgid "Planet/Moon" msgid "Planet/Moon"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:39 #: allianceauth/timerboard/form.py:61
msgid "Structure Type" msgid "Structure Type"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:40 #: allianceauth/timerboard/form.py:62
msgid "Timer Type" msgid "Timer Type"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:41 #: allianceauth/timerboard/form.py:63
#: allianceauth/timerboard/templates/timerboard/timertable.html:8 #: allianceauth/timerboard/templates/timerboard/timertable.html:8
msgid "Objective" msgid "Objective"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:42 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer" msgid "Absolute Timer"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:43 #: allianceauth/timerboard/form.py:65
msgid "Date and Time" msgid "Date and Time"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:44 #: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:45 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:47 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:48 #: allianceauth/timerboard/form.py:71
msgid "Important" msgid "Important"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:49 #: allianceauth/timerboard/form.py:72
msgid "Corp-Restricted" msgid "Corp-Restricted"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:15 #: allianceauth/timerboard/models.py:14
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:39
#: allianceauth/timerboard/templates/timerboard/timertable.html:36
msgid "Friendly"
msgstr ""
#: allianceauth/timerboard/models.py:16
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:34
#: allianceauth/timerboard/templates/timerboard/timertable.html:34
msgid "Hostile"
msgstr ""
#: allianceauth/timerboard/models.py:17
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:44
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
msgid "Neutral"
msgstr ""
#: allianceauth/timerboard/models.py:24
#: allianceauth/timerboard/templates/timerboard/timertable.html:48
msgid "POCO"
msgstr ""
#: allianceauth/timerboard/models.py:25
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
msgid "Orbital Skyhook"
msgstr ""
#: allianceauth/timerboard/models.py:26
#: allianceauth/timerboard/templates/timerboard/timertable.html:52
msgid "I-HUB"
msgstr ""
#: allianceauth/timerboard/models.py:27
#: allianceauth/timerboard/templates/timerboard/timertable.html:55
msgid "TCU"
msgstr ""
#: allianceauth/timerboard/models.py:28
#: allianceauth/timerboard/templates/timerboard/timertable.html:57
msgid "POS [S]"
msgstr ""
#: allianceauth/timerboard/models.py:29
#: allianceauth/timerboard/templates/timerboard/timertable.html:59
msgid "POS [M]"
msgstr ""
#: allianceauth/timerboard/models.py:30
#: allianceauth/timerboard/templates/timerboard/timertable.html:61
msgid "POS [L]"
msgstr ""
#: allianceauth/timerboard/models.py:31
#: allianceauth/timerboard/templates/timerboard/timertable.html:63
msgid "Astrahus"
msgstr ""
#: allianceauth/timerboard/models.py:32
#: allianceauth/timerboard/templates/timerboard/timertable.html:65
msgid "Fortizar"
msgstr ""
#: allianceauth/timerboard/models.py:33
#: allianceauth/timerboard/templates/timerboard/timertable.html:67
msgid "Keepstar"
msgstr ""
#: allianceauth/timerboard/models.py:34
#: allianceauth/timerboard/templates/timerboard/timertable.html:69
msgid "Raitaru"
msgstr ""
#: allianceauth/timerboard/models.py:35
#: allianceauth/timerboard/templates/timerboard/timertable.html:71
msgid "Azbel"
msgstr ""
#: allianceauth/timerboard/models.py:36
#: allianceauth/timerboard/templates/timerboard/timertable.html:73
msgid "Sotiyo"
msgstr ""
#: allianceauth/timerboard/models.py:37
#: allianceauth/timerboard/templates/timerboard/timertable.html:75
msgid "Athanor"
msgstr ""
#: allianceauth/timerboard/models.py:38
#: allianceauth/timerboard/templates/timerboard/timertable.html:77
msgid "Tatara"
msgstr ""
#: allianceauth/timerboard/models.py:39
msgid "Pharolux Cyno Beacon"
msgstr ""
#: allianceauth/timerboard/models.py:40
msgid "Tenebrex Cyno Jammer"
msgstr ""
#: allianceauth/timerboard/models.py:41
#: allianceauth/timerboard/templates/timerboard/timertable.html:83
msgid "Ansiblex Jump Gate"
msgstr ""
#: allianceauth/timerboard/models.py:42
#: allianceauth/timerboard/templates/timerboard/timertable.html:85
msgid "Moon Mining Cycle"
msgstr ""
#: allianceauth/timerboard/models.py:43
#: allianceauth/timerboard/templates/timerboard/timertable.html:87
msgid "Metenox Moon Drill"
msgstr ""
#: allianceauth/timerboard/models.py:44
#: allianceauth/timerboard/templates/timerboard/timertable.html:89
msgid "Other"
msgstr ""
#: allianceauth/timerboard/models.py:51
msgid "Not Specified" msgid "Not Specified"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:52 #: allianceauth/timerboard/models.py:15
msgid "Shield" msgid "Shield"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:53 #: allianceauth/timerboard/models.py:16
msgid "Armor" msgid "Armor"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:54 #: allianceauth/timerboard/models.py:17
msgid "Hull" msgid "Hull"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:55 #: allianceauth/timerboard/models.py:18
msgid "Final" msgid "Final"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:56 #: allianceauth/timerboard/models.py:19
msgid "Anchoring" msgid "Anchoring"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:57 #: allianceauth/timerboard/models.py:20
msgid "Unanchoring" msgid "Unanchoring"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:58
msgid "Abandoned"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
#: allianceauth/timerboard/templates/timerboard/view.html:53 #: allianceauth/timerboard/templates/timerboard/view.html:53
msgid "Upcoming Timers" msgid "Upcoming Timers"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
msgid "Timer" msgid "Timer"
msgstr "" msgstr ""
@@ -2787,14 +2648,78 @@ msgstr ""
msgid "Structure" msgid "Structure"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:79 #: allianceauth/timerboard/templates/timerboard/timertable.html:64
msgid "POCO"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:70
msgid "I-HUB"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:76
msgid "TCU"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:82
msgid "POS [S]"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:88
msgid "POS [M]"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:94
msgid "POS [L]"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:100
msgid "Astrahus"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:106
msgid "Fortizar"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:112
msgid "Keepstar"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:118
msgid "Raitaru"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:124
msgid "Azbel"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:130
msgid "Sotiyo"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:136
msgid "Athanor"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:142
msgid "Tatara"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:148
msgid "Cyno Beacon" msgid "Cyno Beacon"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:81 #: allianceauth/timerboard/templates/timerboard/timertable.html:154
msgid "Cyno Jammer" msgid "Cyno Jammer"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:160
msgid "Ansiblex Jump Gate"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:166
msgid "Moon Mining Cycle"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/view.html:9 #: allianceauth/timerboard/templates/timerboard/view.html:9
msgid "Structure Timer Management" msgid "Structure Timer Management"
msgstr "" msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -12,14 +12,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-13 19:57+1000\n" "POT-Creation-Date: 2024-05-12 19:15+1000\n"
"PO-Revision-Date: 2023-11-08 13:50+0000\n" "PO-Revision-Date: 2023-11-08 13:50+0000\n"
"Last-Translator: Agent Fuse, 2024\n" "Last-Translator: Agent Fuse, 2024\n"
"Language-Team: Dutch (Netherlands) (https://app.transifex.com/alliance-auth/teams/107430/nl_NL/)\n" "Language-Team: Dutch (https://app.transifex.com/alliance-auth/teams/107430/nl/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: nl_NL\n" "Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: allianceauth/analytics/models.py:26 #: allianceauth/analytics/models.py:26
@@ -62,90 +62,66 @@ msgstr ""
"Je bent niet gemachtigd om de volgende beperkte groepen te verwijderen: %s" "Je bent niet gemachtigd om de volgende beperkte groepen te verwijderen: %s"
#: allianceauth/authentication/models.py:71 #: allianceauth/authentication/models.py:71
#: allianceauth/project_template/project_name/settings/base.py:99
msgid "English" msgid "English"
msgstr "Engels" msgstr "Engels"
#: allianceauth/authentication/models.py:72 #: allianceauth/authentication/models.py:72
msgid "Czech"
msgstr ""
#: allianceauth/authentication/models.py:73
#: allianceauth/project_template/project_name/settings/base.py:101
msgid "German" msgid "German"
msgstr "Duits" msgstr "Duits"
#: allianceauth/authentication/models.py:74 #: allianceauth/authentication/models.py:73
#: allianceauth/project_template/project_name/settings/base.py:102
msgid "Spanish" msgid "Spanish"
msgstr "Spaans" msgstr "Spaans"
#: allianceauth/authentication/models.py:74
msgid "Chinese Simplified"
msgstr "Vereenvoudigd Chinees"
#: allianceauth/authentication/models.py:75 #: allianceauth/authentication/models.py:75
#: allianceauth/project_template/project_name/settings/base.py:103
msgid "Italian"
msgstr "Italiaans"
#: allianceauth/authentication/models.py:76
#: allianceauth/project_template/project_name/settings/base.py:104
msgid "Japanese"
msgstr "Japans"
#: allianceauth/authentication/models.py:77
#: allianceauth/project_template/project_name/settings/base.py:105
msgid "Korean"
msgstr "Koreaans"
#: allianceauth/authentication/models.py:78
#: allianceauth/project_template/project_name/settings/base.py:106
msgid "French"
msgstr "Frans"
#: allianceauth/authentication/models.py:79
#: allianceauth/project_template/project_name/settings/base.py:109
msgid "Russian" msgid "Russian"
msgstr "Russisch" msgstr "Russisch"
#: allianceauth/authentication/models.py:76
msgid "Korean"
msgstr "Koreaans"
#: allianceauth/authentication/models.py:77
msgid "French"
msgstr "Frans"
#: allianceauth/authentication/models.py:78
msgid "Japanese"
msgstr "Japans"
#: allianceauth/authentication/models.py:79
msgid "Italian"
msgstr "Italiaans"
#: allianceauth/authentication/models.py:80 #: allianceauth/authentication/models.py:80
#: allianceauth/project_template/project_name/settings/base.py:107
msgid "Dutch"
msgstr ""
#: allianceauth/authentication/models.py:81
#: allianceauth/project_template/project_name/settings/base.py:108
msgid "Polish"
msgstr ""
#: allianceauth/authentication/models.py:82
#: allianceauth/project_template/project_name/settings/base.py:110
msgid "Ukrainian" msgid "Ukrainian"
msgstr "Oekraïens" msgstr "Oekraïens"
#: allianceauth/authentication/models.py:83 #: allianceauth/authentication/models.py:96
#: allianceauth/project_template/project_name/settings/base.py:111
msgid "Simplified Chinese"
msgstr ""
#: allianceauth/authentication/models.py:99
#: allianceauth/menu/templates/menu/menu-user.html:42 #: allianceauth/menu/templates/menu/menu-user.html:42
msgid "Language" msgid "Language"
msgstr "Taal" msgstr "Taal"
#: allianceauth/authentication/models.py:104 #: allianceauth/authentication/models.py:101
#: allianceauth/templates/allianceauth/night-toggle.html:6 #: allianceauth/templates/allianceauth/night-toggle.html:6
msgid "Night Mode" msgid "Night Mode"
msgstr "Nachtstand" msgstr "Nachtstand"
#: allianceauth/authentication/models.py:108 #: allianceauth/authentication/models.py:105
#: allianceauth/menu/templates/menu/menu-user.html:46 #: allianceauth/menu/templates/menu/menu-user.html:46
msgid "Theme" msgid "Theme"
msgstr "Thema" msgstr "Thema"
#: allianceauth/authentication/models.py:125 #: allianceauth/authentication/models.py:122
#, python-format #, python-format
msgid "State changed to: %s" msgid "State changed to: %s"
msgstr "State gewijzigd naar: %s" msgstr "State gewijzigd naar: %s"
#: allianceauth/authentication/models.py:126 #: allianceauth/authentication/models.py:123
#, python-format #, python-format
msgid "Your user's state is now: %(state)s" msgid "Your user's state is now: %(state)s"
msgstr "De gebruikers staat is nu: %(state)s" msgstr "De gebruikers staat is nu: %(state)s"
@@ -157,27 +133,27 @@ msgstr "De gebruikers staat is nu: %(state)s"
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashboard" msgstr "Dashboard"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:5 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:7
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33
#: allianceauth/hrapplications/templates/hrapplications/view.html:54 #: allianceauth/hrapplications/templates/hrapplications/view.html:54
msgid "Characters" msgid "Characters"
msgstr "Karakter" msgstr "Karakter"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:11 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:13
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:12 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:14
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:4 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:4
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:6 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:6
msgid "Add Character" msgid "Add Character"
msgstr "Personages toevoegen" msgstr "Personages toevoegen"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:14 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:16
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:15 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:17
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:10 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:10
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:12 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:12
msgid "Change Main" msgid "Change Main"
msgstr "Verander Main" msgstr "Verander Main"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:22 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31
@@ -186,12 +162,12 @@ msgstr "Verander Main"
msgid "Name" msgid "Name"
msgstr "Naam" msgstr "Naam"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:23 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:25
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33
msgid "Corp" msgid "Corp"
msgstr "Corp" msgstr "Corp"
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24 #: allianceauth/authentication/templates/authentication/dashboard_characters.html:26
#: allianceauth/corputils/templates/corputils/corpstats.html:125 #: allianceauth/corputils/templates/corputils/corpstats.html:125
#: allianceauth/hrapplications/templates/hrapplications/view.html:63 #: allianceauth/hrapplications/templates/hrapplications/view.html:63
msgid "Alliance" msgid "Alliance"
@@ -201,7 +177,7 @@ msgstr "Alliantie"
msgid "Membership" msgid "Membership"
msgstr "Lidmaatschap" msgstr "Lidmaatschap"
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:10 #: allianceauth/authentication/templates/authentication/dashboard_groups.html:8
msgid "State:" msgid "State:"
msgstr "Status:" msgstr "Status:"
@@ -446,19 +422,6 @@ msgstr ""
"Het is niet gelukt om bedrijfsstatistieken te verzamelen met het " "Het is niet gelukt om bedrijfsstatistieken te verzamelen met het "
"geselecteerde token." "geselecteerde token."
#: allianceauth/custom_css/apps.py:13 allianceauth/custom_css/models.py:36
#: allianceauth/custom_css/models.py:37 allianceauth/custom_css/models.py:47
msgid "Custom CSS"
msgstr ""
#: allianceauth/custom_css/models.py:25
msgid "Your custom CSS"
msgstr ""
#: allianceauth/custom_css/models.py:26
msgid "This CSS will be added to the site after the default CSS."
msgstr ""
#: allianceauth/fleetactivitytracking/auth_hooks.py:10 #: allianceauth/fleetactivitytracking/auth_hooks.py:10
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11
@@ -551,8 +514,8 @@ msgstr "Gebruiker"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:37 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:37
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:42 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:42
#: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:37 #: allianceauth/optimer/form.py:13 allianceauth/timerboard/form.py:59
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16
#: allianceauth/timerboard/templates/timerboard/timertable.html:9 #: allianceauth/timerboard/templates/timerboard/timertable.html:9
msgid "System" msgid "System"
msgstr "Systeem" msgstr "Systeem"
@@ -874,7 +837,7 @@ msgstr "Aanvrager"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:15 #: allianceauth/optimer/templates/optimer/dashboard.ops.html:15
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:16 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
@@ -966,7 +929,7 @@ msgid "Hidden"
msgstr "verborgen" msgstr "verborgen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:16 #: allianceauth/templates/allianceauth/admin-status/overview.html:19
msgid "Open" msgid "Open"
msgstr "Open" msgstr "Open"
@@ -1442,16 +1405,16 @@ msgstr "Notificatie"
msgid "Super User" msgid "Super User"
msgstr "" msgstr ""
#: allianceauth/menu/templates/menu/menu-user.html:70 #: allianceauth/menu/templates/menu/menu-user.html:68
#: allianceauth/templates/allianceauth/top-menu-admin.html:9 #: allianceauth/templates/allianceauth/top-menu-admin.html:9
msgid "Admin" msgid "Admin"
msgstr "Administrator" msgstr "Administrator"
#: allianceauth/menu/templates/menu/menu-user.html:82 #: allianceauth/menu/templates/menu/menu-user.html:80
msgid "Sign Out" msgid "Sign Out"
msgstr "" msgstr ""
#: allianceauth/menu/templates/menu/menu-user.html:86 #: allianceauth/menu/templates/menu/menu-user.html:84
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:17 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:17
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:18 #: allianceauth/templates/allianceauth/top-menu-rh-default.html:18
msgid "Sign In" msgid "Sign In"
@@ -1601,7 +1564,7 @@ msgid "Form Up System"
msgstr "" msgstr ""
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17 #: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:18 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
msgid "EVE Time" msgid "EVE Time"
msgstr "" msgstr ""
@@ -1796,17 +1759,17 @@ msgid ""
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:26 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:26
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:22 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:20
msgid "Activate" msgid "Activate"
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:34 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:32
msgid "Reset Password" msgid "Reset Password"
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:38 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:38
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:40 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:38
msgid "Deactivate" msgid "Deactivate"
msgstr "" msgstr ""
@@ -1887,12 +1850,12 @@ msgstr ""
msgid "Deactivated IPSuite4 account." msgid "Deactivated IPSuite4 account."
msgstr "" msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:28 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:26
#: allianceauth/services/templates/services/service_password.html:26 #: allianceauth/services/templates/services/service_password.html:26
msgid "Set Password" msgid "Set Password"
msgstr "" msgstr ""
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:46 #: allianceauth/services/modules/mumble/templates/services/mumble/mumble_service_ctrl.html:44
msgid "Connect" msgid "Connect"
msgstr "" msgstr ""
@@ -2455,56 +2418,56 @@ msgstr ""
msgid "Your Server received an ESI error response code of " msgid "Your Server received an ESI error response code of "
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:8 #: allianceauth/templates/allianceauth/admin-status/overview.html:11
msgid "Alliance Auth Notifications" msgid "Alliance Auth Notifications"
msgstr "Alliantie Authenticatie Notificaties" msgstr "Alliantie Authenticatie Notificaties"
#: allianceauth/templates/allianceauth/admin-status/overview.html:18 #: allianceauth/templates/allianceauth/admin-status/overview.html:21
msgid "Closed" msgid "Closed"
msgstr "Gesloten" msgstr "Gesloten"
#: allianceauth/templates/allianceauth/admin-status/overview.html:24 #: allianceauth/templates/allianceauth/admin-status/overview.html:27
msgid "No notifications at this time" msgid "No notifications at this time"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:33 #: allianceauth/templates/allianceauth/admin-status/overview.html:36
msgid "Powered by GitLab" msgid "Powered by GitLab"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:39 #: allianceauth/templates/allianceauth/admin-status/overview.html:42
msgid "Support Discord" msgid "Support Discord"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:53 #: allianceauth/templates/allianceauth/admin-status/overview.html:59
#: allianceauth/templates/allianceauth/admin-status/overview.html:57 #: allianceauth/templates/allianceauth/admin-status/overview.html:63
msgid "Software Version" msgid "Software Version"
msgstr "Software Versie" msgstr "Software Versie"
#: allianceauth/templates/allianceauth/admin-status/overview.html:60 #: allianceauth/templates/allianceauth/admin-status/overview.html:66
msgid "Current" msgid "Current"
msgstr "Huidige" msgstr "Huidige"
#: allianceauth/templates/allianceauth/admin-status/overview.html:67 #: allianceauth/templates/allianceauth/admin-status/overview.html:73
msgid "Latest Stable" msgid "Latest Stable"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:72 #: allianceauth/templates/allianceauth/admin-status/overview.html:78
msgid "Update available" msgid "Update available"
msgstr "Update Beschikbaar" msgstr "Update Beschikbaar"
#: allianceauth/templates/allianceauth/admin-status/overview.html:80 #: allianceauth/templates/allianceauth/admin-status/overview.html:86
msgid "Latest Pre-Release" msgid "Latest Pre-Release"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:85 #: allianceauth/templates/allianceauth/admin-status/overview.html:91
msgid "Pre-Release available" msgid "Pre-Release available"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:95 #: allianceauth/templates/allianceauth/admin-status/overview.html:102
msgid "Task Queue" msgid "Task Queue"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:100 #: allianceauth/templates/allianceauth/admin-status/overview.html:107
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@@ -2512,11 +2475,11 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:116 #: allianceauth/templates/allianceauth/admin-status/overview.html:123
msgid "running" msgid "running"
msgstr "" msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:117 #: allianceauth/templates/allianceauth/admin-status/overview.html:124
msgid "queued" msgid "queued"
msgstr "" msgstr ""
@@ -2545,216 +2508,114 @@ msgstr ""
msgid "Select Theme" msgid "Select Theme"
msgstr "Selecteer Thema" msgstr "Selecteer Thema"
#: allianceauth/timerboard/form.py:36 #: allianceauth/timerboard/form.py:53
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14 #: allianceauth/timerboard/templates/timerboard/timertable.html:172
msgid "Other"
msgstr "Andere"
#: allianceauth/timerboard/form.py:54
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:38
#: allianceauth/timerboard/templates/timerboard/timertable.html:44
msgid "Friendly"
msgstr "Vriendelijk"
#: allianceauth/timerboard/form.py:55
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:33
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
msgid "Hostile"
msgstr "Vijandig"
#: allianceauth/timerboard/form.py:56
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:43
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
msgid "Neutral"
msgstr "Neutraal"
#: allianceauth/timerboard/form.py:58
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:13
#: allianceauth/timerboard/templates/timerboard/timertable.html:7 #: allianceauth/timerboard/templates/timerboard/timertable.html:7
msgid "Details" msgid "Details"
msgstr "Details" msgstr "Details"
#: allianceauth/timerboard/form.py:38 #: allianceauth/timerboard/form.py:60
msgid "Planet/Moon" msgid "Planet/Moon"
msgstr "Planeet/Maan" msgstr "Planeet/Maan"
#: allianceauth/timerboard/form.py:39 #: allianceauth/timerboard/form.py:61
msgid "Structure Type" msgid "Structure Type"
msgstr "" msgstr ""
#: allianceauth/timerboard/form.py:40 #: allianceauth/timerboard/form.py:62
msgid "Timer Type" msgid "Timer Type"
msgstr "Timer Type" msgstr "Timer Type"
#: allianceauth/timerboard/form.py:41 #: allianceauth/timerboard/form.py:63
#: allianceauth/timerboard/templates/timerboard/timertable.html:8 #: allianceauth/timerboard/templates/timerboard/timertable.html:8
msgid "Objective" msgid "Objective"
msgstr "Doel" msgstr "Doel"
#: allianceauth/timerboard/form.py:42 #: allianceauth/timerboard/form.py:64
msgid "Absolute Timer" msgid "Absolute Timer"
msgstr "Absolute Timer" msgstr "Absolute Timer"
#: allianceauth/timerboard/form.py:43 #: allianceauth/timerboard/form.py:65
msgid "Date and Time" msgid "Date and Time"
msgstr "Datum en Tijd" msgstr "Datum en Tijd"
#: allianceauth/timerboard/form.py:44 #: allianceauth/timerboard/form.py:66
msgid "Days Remaining" msgid "Days Remaining"
msgstr "Resterende Dagen" msgstr "Resterende Dagen"
#: allianceauth/timerboard/form.py:45 #: allianceauth/timerboard/form.py:67
msgid "Hours Remaining" msgid "Hours Remaining"
msgstr "Resterende Uren" msgstr "Resterende Uren"
#: allianceauth/timerboard/form.py:47 #: allianceauth/timerboard/form.py:69
msgid "Minutes Remaining" msgid "Minutes Remaining"
msgstr "Resterende Minuten" msgstr "Resterende Minuten"
#: allianceauth/timerboard/form.py:48 #: allianceauth/timerboard/form.py:71
msgid "Important" msgid "Important"
msgstr "Belangrijk" msgstr "Belangrijk"
#: allianceauth/timerboard/form.py:49 #: allianceauth/timerboard/form.py:72
msgid "Corp-Restricted" msgid "Corp-Restricted"
msgstr "" msgstr ""
#: allianceauth/timerboard/models.py:15 #: allianceauth/timerboard/models.py:14
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:39
#: allianceauth/timerboard/templates/timerboard/timertable.html:36
msgid "Friendly"
msgstr "Vriendelijk"
#: allianceauth/timerboard/models.py:16
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:34
#: allianceauth/timerboard/templates/timerboard/timertable.html:34
msgid "Hostile"
msgstr "Vijandig"
#: allianceauth/timerboard/models.py:17
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:44
#: allianceauth/timerboard/templates/timerboard/timertable.html:38
msgid "Neutral"
msgstr "Neutraal"
#: allianceauth/timerboard/models.py:24
#: allianceauth/timerboard/templates/timerboard/timertable.html:48
msgid "POCO"
msgstr ""
#: allianceauth/timerboard/models.py:25
#: allianceauth/timerboard/templates/timerboard/timertable.html:50
msgid "Orbital Skyhook"
msgstr ""
#: allianceauth/timerboard/models.py:26
#: allianceauth/timerboard/templates/timerboard/timertable.html:52
msgid "I-HUB"
msgstr ""
#: allianceauth/timerboard/models.py:27
#: allianceauth/timerboard/templates/timerboard/timertable.html:55
msgid "TCU"
msgstr ""
#: allianceauth/timerboard/models.py:28
#: allianceauth/timerboard/templates/timerboard/timertable.html:57
msgid "POS [S]"
msgstr ""
#: allianceauth/timerboard/models.py:29
#: allianceauth/timerboard/templates/timerboard/timertable.html:59
msgid "POS [M]"
msgstr ""
#: allianceauth/timerboard/models.py:30
#: allianceauth/timerboard/templates/timerboard/timertable.html:61
msgid "POS [L]"
msgstr ""
#: allianceauth/timerboard/models.py:31
#: allianceauth/timerboard/templates/timerboard/timertable.html:63
msgid "Astrahus"
msgstr "Astrahus"
#: allianceauth/timerboard/models.py:32
#: allianceauth/timerboard/templates/timerboard/timertable.html:65
msgid "Fortizar"
msgstr "Fortizar"
#: allianceauth/timerboard/models.py:33
#: allianceauth/timerboard/templates/timerboard/timertable.html:67
msgid "Keepstar"
msgstr "Keepstar"
#: allianceauth/timerboard/models.py:34
#: allianceauth/timerboard/templates/timerboard/timertable.html:69
msgid "Raitaru"
msgstr "Raitaru"
#: allianceauth/timerboard/models.py:35
#: allianceauth/timerboard/templates/timerboard/timertable.html:71
msgid "Azbel"
msgstr "Sotiyo"
#: allianceauth/timerboard/models.py:36
#: allianceauth/timerboard/templates/timerboard/timertable.html:73
msgid "Sotiyo"
msgstr "Sotiyo"
#: allianceauth/timerboard/models.py:37
#: allianceauth/timerboard/templates/timerboard/timertable.html:75
msgid "Athanor"
msgstr "Athanor"
#: allianceauth/timerboard/models.py:38
#: allianceauth/timerboard/templates/timerboard/timertable.html:77
msgid "Tatara"
msgstr "Tatara"
#: allianceauth/timerboard/models.py:39
msgid "Pharolux Cyno Beacon"
msgstr ""
#: allianceauth/timerboard/models.py:40
msgid "Tenebrex Cyno Jammer"
msgstr ""
#: allianceauth/timerboard/models.py:41
#: allianceauth/timerboard/templates/timerboard/timertable.html:83
msgid "Ansiblex Jump Gate"
msgstr "Ansiblex Jump Gate"
#: allianceauth/timerboard/models.py:42
#: allianceauth/timerboard/templates/timerboard/timertable.html:85
msgid "Moon Mining Cycle"
msgstr "Maan mijn Cyclus"
#: allianceauth/timerboard/models.py:43
#: allianceauth/timerboard/templates/timerboard/timertable.html:87
msgid "Metenox Moon Drill"
msgstr ""
#: allianceauth/timerboard/models.py:44
#: allianceauth/timerboard/templates/timerboard/timertable.html:89
msgid "Other"
msgstr "Andere"
#: allianceauth/timerboard/models.py:51
msgid "Not Specified" msgid "Not Specified"
msgstr "Niet gespecifieerd" msgstr "Niet gespecifieerd"
#: allianceauth/timerboard/models.py:52 #: allianceauth/timerboard/models.py:15
msgid "Shield" msgid "Shield"
msgstr "Schild" msgstr "Schild"
#: allianceauth/timerboard/models.py:53 #: allianceauth/timerboard/models.py:16
msgid "Armor" msgid "Armor"
msgstr "Pantser" msgstr "Pantser"
#: allianceauth/timerboard/models.py:54 #: allianceauth/timerboard/models.py:17
msgid "Hull" msgid "Hull"
msgstr "Romp" msgstr "Romp"
#: allianceauth/timerboard/models.py:55 #: allianceauth/timerboard/models.py:18
msgid "Final" msgid "Final"
msgstr "Laatste" msgstr "Laatste"
#: allianceauth/timerboard/models.py:56 #: allianceauth/timerboard/models.py:19
msgid "Anchoring" msgid "Anchoring"
msgstr "Ankeren" msgstr "Ankeren"
#: allianceauth/timerboard/models.py:57 #: allianceauth/timerboard/models.py:20
msgid "Unanchoring" msgid "Unanchoring"
msgstr "ontankeren" msgstr "ontankeren"
#: allianceauth/timerboard/models.py:58
msgid "Abandoned"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
#: allianceauth/timerboard/templates/timerboard/view.html:53 #: allianceauth/timerboard/templates/timerboard/view.html:53
msgid "Upcoming Timers" msgid "Upcoming Timers"
msgstr "" msgstr ""
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:15 #: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
msgid "Timer" msgid "Timer"
msgstr "Timer" msgstr "Timer"
@@ -2798,14 +2659,78 @@ msgstr ""
msgid "Structure" msgid "Structure"
msgstr "Constructie" msgstr "Constructie"
#: allianceauth/timerboard/templates/timerboard/timertable.html:79 #: allianceauth/timerboard/templates/timerboard/timertable.html:64
msgid "POCO"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:70
msgid "I-HUB"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:76
msgid "TCU"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:82
msgid "POS [S]"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:88
msgid "POS [M]"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:94
msgid "POS [L]"
msgstr ""
#: allianceauth/timerboard/templates/timerboard/timertable.html:100
msgid "Astrahus"
msgstr "Astrahus"
#: allianceauth/timerboard/templates/timerboard/timertable.html:106
msgid "Fortizar"
msgstr "Fortizar"
#: allianceauth/timerboard/templates/timerboard/timertable.html:112
msgid "Keepstar"
msgstr "Keepstar"
#: allianceauth/timerboard/templates/timerboard/timertable.html:118
msgid "Raitaru"
msgstr "Raitaru"
#: allianceauth/timerboard/templates/timerboard/timertable.html:124
msgid "Azbel"
msgstr "Sotiyo"
#: allianceauth/timerboard/templates/timerboard/timertable.html:130
msgid "Sotiyo"
msgstr "Sotiyo"
#: allianceauth/timerboard/templates/timerboard/timertable.html:136
msgid "Athanor"
msgstr "Athanor"
#: allianceauth/timerboard/templates/timerboard/timertable.html:142
msgid "Tatara"
msgstr "Tatara"
#: allianceauth/timerboard/templates/timerboard/timertable.html:148
msgid "Cyno Beacon" msgid "Cyno Beacon"
msgstr "Cyno Beacon" msgstr "Cyno Beacon"
#: allianceauth/timerboard/templates/timerboard/timertable.html:81 #: allianceauth/timerboard/templates/timerboard/timertable.html:154
msgid "Cyno Jammer" msgid "Cyno Jammer"
msgstr "Cyno Jammer" msgstr "Cyno Jammer"
#: allianceauth/timerboard/templates/timerboard/timertable.html:160
msgid "Ansiblex Jump Gate"
msgstr "Ansiblex Jump Gate"
#: allianceauth/timerboard/templates/timerboard/timertable.html:166
msgid "Moon Mining Cycle"
msgstr "Maan mijn Cyclus"
#: allianceauth/timerboard/templates/timerboard/view.html:9 #: allianceauth/timerboard/templates/timerboard/view.html:9
msgid "Structure Timer Management" msgid "Structure Timer Management"
msgstr "" msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -35,12 +35,12 @@ class MenuItemAdmin(admin.ModelAdmin):
] ]
ordering = ["parent", "order", "text"] ordering = ["parent", "order", "text"]
def get_form(self, request: HttpRequest, obj: Optional[MenuItem] = None, **kwargs): def get_form(self, request: HttpRequest, obj: MenuItem | None = None, **kwargs):
kwargs["form"] = self._choose_form(request, obj) kwargs["form"] = self._choose_form(request, obj)
return super().get_form(request, obj, **kwargs) return super().get_form(request, obj, **kwargs)
@classmethod @classmethod
def _choose_form(cls, request: HttpRequest, obj: Optional[MenuItem]): def _choose_form(cls, request: HttpRequest, obj: MenuItem | None):
"""Return the form for the current menu item type.""" """Return the form for the current menu item type."""
if obj: # change if obj: # change
if obj.hook_hash: if obj.hook_hash:
@@ -104,7 +104,7 @@ class MenuItemAdmin(admin.ModelAdmin):
@staticmethod @staticmethod
def _type_from_request( def _type_from_request(
request: HttpRequest, default=None request: HttpRequest, default=None
) -> Optional[MenuItemType]: ) -> MenuItemType | None:
try: try:
return MenuItemType(request.GET.get("type")) return MenuItemType(request.GET.get("type"))
except ValueError: except ValueError:

View File

@@ -14,8 +14,8 @@ class MenuItemHookCustom(MenuItemHook):
text: str, text: str,
classes: str, classes: str,
url_name: str, url_name: str,
order: Optional[int] = None, order: int | None = None,
navactive: Optional[List[str]] = None, navactive: list[str] | None = None,
): ):
super().__init__(text, classes, url_name, order, navactive) super().__init__(text, classes, url_name, order, navactive)
self.url = "" self.url = ""

View File

@@ -33,8 +33,8 @@ class MenuItemHook:
text: str, text: str,
classes: str, classes: str,
url_name: str, url_name: str,
order: Optional[int] = None, order: int | None = None,
navactive: Optional[List[str]] = None, navactive: list[str] | None = None,
): ):
self.text = text self.text = text
self.classes = classes self.classes = classes

View File

@@ -11,8 +11,8 @@
data-bs-target="#{{ item.html_id }}" data-bs-target="#{{ item.html_id }}"
aria-expanded="false" aria-expanded="false"
aria-controls="" aria-controls=""
{% endif %} {% endif %}>
></i> </i>
<a <a
class="nav-link flex-fill align-self-center me-auto {% if item.navactive %}{% navactive request item.navactive|join:' ' %}{% endif %}" class="nav-link flex-fill align-self-center me-auto {% if item.navactive %}{% navactive request item.navactive|join:' ' %}{% endif %}"
{% if item.is_folder %} {% if item.is_folder %}
@@ -36,7 +36,7 @@
{% if item.is_folder %} {% if item.is_folder %}
<span <span
class="pill m-2 align-self-center" class="pill m-2 align-self-center collapsed"
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#{{ item.html_id }}" data-bs-target="#{{ item.html_id }}"

View File

@@ -56,8 +56,8 @@ class RenderedMenuItem:
menu_item: MenuItem menu_item: MenuItem
children: List["RenderedMenuItem"] = field(default_factory=list) children: list["RenderedMenuItem"] = field(default_factory=list)
count: Optional[int] = None count: int | None = None
html: str = "" html: str = ""
html_id: str = "" html_id: str = ""
@@ -78,7 +78,7 @@ class RenderedMenuItem:
self.html_id = hook_obj.html_id self.html_id = hook_obj.html_id
def render_menu(request: HttpRequest) -> List[RenderedMenuItem]: def render_menu(request: HttpRequest) -> list[RenderedMenuItem]:
"""Return the rendered side menu for including in a template. """Return the rendered side menu for including in a template.
This function is creating BS5 style menus. This function is creating BS5 style menus.
@@ -88,7 +88,7 @@ def render_menu(request: HttpRequest) -> List[RenderedMenuItem]:
# Menu items needs to be rendered with the new BS5 template # Menu items needs to be rendered with the new BS5 template
bs5_template = "menu/menu-item-bs5.html" bs5_template = "menu/menu-item-bs5.html"
rendered_items: Dict[int, RenderedMenuItem] = {} rendered_items: dict[int, RenderedMenuItem] = {}
menu_items: QuerySet[MenuItem] = MenuItem.objects.order_by( menu_items: QuerySet[MenuItem] = MenuItem.objects.order_by(
"parent", "order", "text" "parent", "order", "text"
) )
@@ -131,7 +131,7 @@ def render_menu(request: HttpRequest) -> List[RenderedMenuItem]:
return list(rendered_items.values()) return list(rendered_items.values())
def _gather_menu_items_from_hooks() -> Dict[str, MenuItemHook]: def _gather_menu_items_from_hooks() -> dict[str, MenuItemHook]:
hook_items = {} hook_items = {}
for hook in get_hooks("menu_item_hook"): for hook in get_hooks("menu_item_hook"):
f = hook() f = hook()
@@ -161,14 +161,14 @@ def _render_link_item(
def _render_folder_items( def _render_folder_items(
request: HttpRequest, rendered_items: Dict[int, RenderedMenuItem], new_template: str request: HttpRequest, rendered_items: dict[int, RenderedMenuItem], new_template: str
): ):
for item in rendered_items.values(): for item in rendered_items.values():
if item.menu_item.is_folder: if item.menu_item.is_folder:
item.update_html(request=request, template=new_template) item.update_html(request=request, template=new_template)
def _remove_empty_folders(rendered_items: Dict[int, RenderedMenuItem]): def _remove_empty_folders(rendered_items: dict[int, RenderedMenuItem]):
ids_to_remove = [] ids_to_remove = []
for item_id, item in rendered_items.items(): for item_id, item in rendered_items.items():
if item.is_folder and not item.children: if item.is_folder and not item.children:

View File

@@ -347,9 +347,9 @@ class TestRenderedMenuItem(TestCase):
class _ParsedMenuItem(NamedTuple): class _ParsedMenuItem(NamedTuple):
classes: List[str] classes: list[str]
text: str text: str
count: Optional[int] count: int | None
def parse_html(obj: RenderedMenuItem) -> _ParsedMenuItem: def parse_html(obj: RenderedMenuItem) -> _ParsedMenuItem:

View File

@@ -1,21 +1 @@
"""
Example
=======
.. code-block:: python
from allianceauth.notifications.models import Notification
def notify_user_view(request):
'''Simple view sending a notification to the user'''
Notification.objects.notify_user(
user=request.user,
title="Some title",
message="Some message",
level=Notification.Level.INFO,
)
"""
from .core import notify # noqa: F401 from .core import notify # noqa: F401

View File

@@ -15,7 +15,7 @@ class OpForm(forms.Form):
operation_name = forms.CharField(max_length=254, required=True, label=_("Operation Name")) operation_name = forms.CharField(max_length=254, required=True, label=_("Operation Name"))
type = forms.CharField(required=False, label=_("Operation Type")) type = forms.CharField(required=False, label=_("Operation Type"))
fc = forms.CharField(max_length=254, required=True, label=_("Fleet Commander")) fc = forms.CharField(max_length=254, required=True, label=_("Fleet Commander"))
duration = forms.CharField(max_length=25, required=True, label=_("Duration")) duration = forms.CharField(max_length=254, required=True, label=_("Duration"))
description = forms.CharField( description = forms.CharField(
widget=forms.Textarea(attrs={"rows": 10, "cols": 20, "input_type": "textarea"}), widget=forms.Textarea(attrs={"rows": 10, "cols": 20, "input_type": "textarea"}),
required=False, required=False,

View File

@@ -1,6 +1,5 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
@@ -58,14 +57,11 @@
{% include "bundles/datatables-js-bs5.html" %} {% include "bundles/datatables-js-bs5.html" %}
{% include "bundles/filterdropdown-js.html" %} {% include "bundles/filterdropdown-js.html" %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
const groupColumn = 0; const groupColumn = 0;
$('#tab_permissions_audit').DataTable({ $('#tab_permissions_audit').DataTable({
language: {url: '{{ DT_LANG_PATH }}'},
columnDefs: [{ columnDefs: [{
"visible": false, "visible": false,
"targets": groupColumn "targets": groupColumn

View File

@@ -1,6 +1,5 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
@@ -63,14 +62,11 @@
{% include "bundles/datatables-js-bs5.html" %} {% include "bundles/datatables-js-bs5.html" %}
{% include "bundles/filterdropdown-js.html" %} {% include "bundles/filterdropdown-js.html" %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script> <script>
$(document).ready(() => { $(document).ready(() => {
const groupColumn = 0; const groupColumn = 0;
$('#tab_permissions_overview').DataTable({ $('#tab_permissions_overview').DataTable({
language: {url: '{{ DT_LANG_PATH }}'},
columnDefs: [{ columnDefs: [{
"visible": false, "visible": false,
"targets": groupColumn "targets": groupColumn

View File

@@ -12,8 +12,6 @@ from celery.schedules import crontab
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _
INSTALLED_APPS = [ INSTALLED_APPS = [
'allianceauth', # needs to be on top of this list to support favicons in Django admin (see https://gitlab.com/allianceauth/allianceauth/-/issues/1301) 'allianceauth', # needs to be on top of this list to support favicons in Django admin (see https://gitlab.com/allianceauth/allianceauth/-/issues/1301)
'django.contrib.admin', 'django.contrib.admin',
@@ -24,7 +22,6 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize', 'django.contrib.humanize',
'django_celery_beat', 'django_celery_beat',
'solo',
'bootstrapform', 'bootstrapform',
'django_bootstrap5', # https://github.com/zostera/django-bootstrap5 'django_bootstrap5', # https://github.com/zostera/django-bootstrap5
'sortedm2m', 'sortedm2m',
@@ -42,17 +39,13 @@ INSTALLED_APPS = [
'allianceauth.theme.darkly', 'allianceauth.theme.darkly',
'allianceauth.theme.flatly', 'allianceauth.theme.flatly',
'allianceauth.theme.materia', 'allianceauth.theme.materia',
"allianceauth.custom_css",
'allianceauth.crontab',
'sri',
] ]
SRI_ALGORITHM = "sha512"
SECRET_KEY = "wow I'm a really bad default secret key" SECRET_KEY = "wow I'm a really bad default secret key"
# Celery configuration # Celery configuration
BROKER_URL = 'redis://localhost:6379/0' BROKER_URL = 'redis://localhost:6379/0'
CELERYBEAT_SCHEDULER = "allianceauth.crontab.schedulers.OffsetDatabaseScheduler" CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
CELERYBEAT_SCHEDULE = { CELERYBEAT_SCHEDULE = {
'esi_cleanup_callbackredirect': { 'esi_cleanup_callbackredirect': {
'task': 'esi.tasks.cleanup_callbackredirect', 'task': 'esi.tasks.cleanup_callbackredirect',
@@ -65,12 +58,10 @@ CELERYBEAT_SCHEDULE = {
'run_model_update': { 'run_model_update': {
'task': 'allianceauth.eveonline.tasks.run_model_update', 'task': 'allianceauth.eveonline.tasks.run_model_update',
'schedule': crontab(minute='0', hour="*/6"), 'schedule': crontab(minute='0', hour="*/6"),
'apply_offset': True
}, },
'check_all_character_ownership': { 'check_all_character_ownership': {
'task': 'allianceauth.authentication.tasks.check_all_character_ownership', 'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
'schedule': crontab(minute='0', hour='*/4'), 'schedule': crontab(minute='0', hour='*/4'),
'apply_offset': True
}, },
'analytics_daily_stats': { 'analytics_daily_stats': {
'task': 'allianceauth.analytics.tasks.analytics_daily_stats', 'task': 'allianceauth.analytics.tasks.analytics_daily_stats',
@@ -78,7 +69,6 @@ CELERYBEAT_SCHEDULE = {
} }
} }
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BASE_DIR = os.path.dirname(PROJECT_DIR) BASE_DIR = os.path.dirname(PROJECT_DIR)
@@ -101,62 +91,20 @@ LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale/'), os.path.join(BASE_DIR, 'locale/'),
) )
LANGUAGES = ( # Sorted by Language Code alphabetical order + English at top LANGUAGES = (
("en", _("English")), ("en", "English"),
# ("cs-cz", _("Czech")), #Not yet at 50% translated ("de", "German"),
("de", _("German")), ("es", "Spanish"),
("es", _("Spanish")), ("zh-hans", "Chinese Simplified"),
("it-it", _("Italian")), ("ru", "Russian"),
("ja", _("Japanese")), ("ko", "Korean"),
("ko-kr", _("Korean")), ("fr", "French"),
("fr-fr", _("French")), ("ja", "Japanese"),
("nl-nl", _("Dutch")), ("it", "Italian"),
("pl-pl", _("Polish")), ("uk", "Ukrainian"),
("ru", _("Russian")), ("pl", "Polish"),
("uk", _("Ukrainian")),
("zh-hans", _("Simplified Chinese")),
) )
# Django's language codes are different from some of the libraries we use,
# so we need to map them.
# When adding a new language, please remember to add it to the mapping
# and add the language files to their respective directories under `allianceauth/static/allianceauth/libs/`.
LANGUAGE_MAPPING = {
# See https://github.com/DataTables/Plugins/tree/master/i18n for available languages
# (We use the JSON files)
# `allianceauth/static/allianceauth/libs/DataTables/Plugins/{version}/i18n/` for the files
"DataTables": {
"cs-cz": "cs",
"de": "de-DE",
"es": "es-ES",
"fr-fr": "fr-FR",
"it-it": "it-IT",
"ja": "ja",
"ko-kr": "ko",
"nl-nl": "nl-NL",
"pl-pl": "pl",
"ru": "ru",
"uk": "uk",
"zh-hans": "zh-HANT",
},
# See https://github.com/moment/moment/tree/master/locale for available languages
# `allianceauth/static/allianceauth/libs/moment.js/{version}/locale/` for the files
"MomentJS": {
"cs-cz": "cs",
"de": "de",
"es": "es",
"fr-fr": "fr",
"it-it": "it",
"ja": "ja",
"ko-kr": "ko",
"nl-nl": "nl",
"pl-pl": "pl",
"ru": "ru",
"uk": "uk",
"zh-hans": "zh-cn",
},
}
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',

View File

@@ -1,6 +1,7 @@
from string import Formatter from string import Formatter
from django.urls import include, re_path from django.urls import include, re_path
from typing import Iterable, Optional from typing import Optional
from collections.abc import Iterable
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@@ -175,7 +176,7 @@ class UrlHook:
urls, urls,
namespace: str, namespace: str,
base_url: str, base_url: str,
excluded_views : Optional[Iterable[str]] = None excluded_views : Iterable[str] | None = None
): ):
self.include_pattern = re_path(base_url, include(urls, namespace=namespace)) self.include_pattern = re_path(base_url, include(urls, namespace=namespace))
self.excluded_views = set(excluded_views or []) self.excluded_views = set(excluded_views or [])

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