mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-13 10:36:25 +01:00
Compare commits
160 Commits
v4.0.0b2
...
63fb449060
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63fb449060 | ||
|
|
235675fa9b | ||
|
|
a065f043eb | ||
|
|
0839920032 | ||
|
|
29ad4acff7 | ||
|
|
3a5b84d1f9 | ||
|
|
bbcb94021e | ||
|
|
d50f13528b | ||
|
|
c88521af88 | ||
|
|
2bd5ff8723 | ||
|
|
84484cebcb | ||
|
|
5ee34fcb2d | ||
|
|
046473def1 | ||
|
|
6aaba2bf3d | ||
|
|
acff3695bc | ||
|
|
43ec8514aa | ||
|
|
4c629b193f | ||
|
|
c651da4011 | ||
|
|
da382cffd1 | ||
|
|
4ecfc3afd8 | ||
|
|
4eb7dbbe62 | ||
|
|
c96ba65296 | ||
|
|
ff2f60f7f3 | ||
|
|
3000545c98 | ||
|
|
f3ad092ef2 | ||
|
|
a012e7df2f | ||
|
|
1fa77412c0 | ||
|
|
e56caeb22b | ||
|
|
ceb07ebc67 | ||
|
|
237075d45c | ||
|
|
7099b1946d | ||
|
|
e416ab8ff2 | ||
|
|
2802ed03a5 | ||
|
|
4af73c76fe | ||
|
|
b6149979aa | ||
|
|
cb20288427 | ||
|
|
db6f4c91dc | ||
|
|
57ac7a5277 | ||
|
|
136438f9c2 | ||
|
|
e2be8b3440 | ||
|
|
04f3473ef3 | ||
|
|
255cb0da8d | ||
|
|
069352fb0f | ||
|
|
66e8ddb684 | ||
|
|
179c26975c | ||
|
|
e17f6e799b | ||
|
|
7cd8294104 | ||
|
|
ede5540335 | ||
|
|
747279b773 | ||
|
|
44f8b1c477 | ||
|
|
7c6ebd9bf6 | ||
|
|
430469b708 | ||
|
|
efbb3cee31 | ||
|
|
21094ed4dd | ||
|
|
5f326efc7e | ||
|
|
b6e34ace35 | ||
|
|
fe4a8965e3 | ||
|
|
23371c233d | ||
|
|
7a3bbf0d7f | ||
|
|
89a1bec9c1 | ||
|
|
1c1e70619a | ||
|
|
0ff4374efa | ||
|
|
18d0e58a48 | ||
|
|
84f44338dc | ||
|
|
2ba0412890 | ||
|
|
2326522b29 | ||
|
|
a7cb6ee434 | ||
|
|
2aeef63565 | ||
|
|
3c9e7335ef | ||
|
|
49067de325 | ||
|
|
471e7e29ae | ||
|
|
3ec5775406 | ||
|
|
e804d2b60d | ||
|
|
742438a95d | ||
|
|
5c60086baa | ||
|
|
e49041bb14 | ||
|
|
f3cbe91883 | ||
|
|
ea439a2176 | ||
|
|
56e1e76f11 | ||
|
|
634e7357be | ||
|
|
08dc88da1a | ||
|
|
3d206e445c | ||
|
|
64686cdad1 | ||
|
|
d7fe09bdf1 | ||
|
|
6da50da92f | ||
|
|
51e4dd986f | ||
|
|
bee6522182 | ||
|
|
1711a9dd33 | ||
|
|
3914626379 | ||
|
|
df276cb32d | ||
|
|
daad7d8b10 | ||
|
|
3bf5bc0fe3 | ||
|
|
96abae553a | ||
|
|
f9cbfb1562 | ||
|
|
8eaa94e179 | ||
|
|
4f876b648b | ||
|
|
cd738137c0 | ||
|
|
5605eb129d | ||
|
|
87ef0f21a3 | ||
|
|
a1c7ce827e | ||
|
|
97466bcdfb | ||
|
|
ff3096b106 | ||
|
|
98f0d77f3f | ||
|
|
92548ba402 | ||
|
|
c46741d311 | ||
|
|
7c7c1abf7c | ||
|
|
fc303b1b0a | ||
|
|
4e220a9679 | ||
|
|
b17b1f7504 | ||
|
|
7081fc0e76 | ||
|
|
68e4574f19 | ||
|
|
e6e0a70012 | ||
|
|
13e38da942 | ||
|
|
468c1de26b | ||
|
|
22ef5ac0e5 | ||
|
|
ef2dc08958 | ||
|
|
6b84ffa16c | ||
|
|
d7a1096413 | ||
|
|
93b94a8bc2 | ||
|
|
9a95716105 | ||
|
|
dbfcf5d87a | ||
|
|
105d7d53b3 | ||
|
|
01cefe1457 | ||
|
|
7fe3db8017 | ||
|
|
e0945fac80 | ||
|
|
40fa190820 | ||
|
|
670580f8f3 | ||
|
|
323a0bcf16 | ||
|
|
6e995edd80 | ||
|
|
8d86e45b7a | ||
|
|
2aa6df4461 | ||
|
|
cf6f989502 | ||
|
|
3e1d8ae334 | ||
|
|
bcfe9484b5 | ||
|
|
5e4d1b9cfd | ||
|
|
3b463e7305 | ||
|
|
eedf5082fa | ||
|
|
2ea5b15175 | ||
|
|
7a9808aad3 | ||
|
|
a1d712694c | ||
|
|
ca11c726a3 | ||
|
|
6e0f7a35bd | ||
|
|
7375b001ca | ||
|
|
0287086633 | ||
|
|
9eb2becbb5 | ||
|
|
12f1444fe7 | ||
|
|
d6372bd093 | ||
|
|
f4d8ead54e | ||
|
|
7427e13505 | ||
|
|
445683c3d5 | ||
|
|
677c46c48a | ||
|
|
87b9e3f87a | ||
|
|
da2a5aff2f | ||
|
|
65d77743dc | ||
|
|
9f4bf13cc9 | ||
|
|
51b86f88b9 | ||
|
|
8184461b48 | ||
|
|
ca0cdd6e15 | ||
|
|
036a17ad3b | ||
|
|
2418023ddd |
@@ -25,7 +25,7 @@ before_script:
|
||||
pre-commit-check:
|
||||
<<: *only-default
|
||||
stage: pre-commit
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
# variables:
|
||||
# PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||
# cache:
|
||||
@@ -51,33 +51,9 @@ secret_detection:
|
||||
stage: gitlab
|
||||
before_script: []
|
||||
|
||||
test-3.8-core:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
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-bullseye
|
||||
script:
|
||||
- tox -e py39-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
test-3.10-core:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.10-bookworm
|
||||
script:
|
||||
- tox -e py310-core
|
||||
artifacts:
|
||||
@@ -89,7 +65,7 @@ test-3.10-core:
|
||||
|
||||
test-3.11-core:
|
||||
<<: *only-default
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
script:
|
||||
- tox -e py311-core
|
||||
artifacts:
|
||||
@@ -101,7 +77,7 @@ test-3.11-core:
|
||||
|
||||
test-3.12-core:
|
||||
<<: *only-default
|
||||
image: python:3.12-rc-bullseye
|
||||
image: python:3.12-bookworm
|
||||
script:
|
||||
- tox -e py312-core
|
||||
artifacts:
|
||||
@@ -111,33 +87,22 @@ test-3.12-core:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
test-3.8-all:
|
||||
test-3.13-core:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
image: python:3.13-rc-bookworm
|
||||
script:
|
||||
- tox -e py38-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
test-3.9-all:
|
||||
<<: *only-default
|
||||
image: python:3.9-bullseye
|
||||
script:
|
||||
- tox -e py39-all
|
||||
- tox -e py313-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
test-3.10-all:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.10-bookworm
|
||||
script:
|
||||
- tox -e py310-all
|
||||
artifacts:
|
||||
@@ -149,7 +114,7 @@ test-3.10-all:
|
||||
|
||||
test-3.11-all:
|
||||
<<: *only-default
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.11-bookworm
|
||||
script:
|
||||
- tox -e py311-all
|
||||
artifacts:
|
||||
@@ -162,7 +127,7 @@ test-3.11-all:
|
||||
|
||||
test-3.12-all:
|
||||
<<: *only-default
|
||||
image: python:3.12-rc-bullseye
|
||||
image: python:3.12-bookworm
|
||||
script:
|
||||
- tox -e py312-all
|
||||
artifacts:
|
||||
@@ -172,9 +137,22 @@ test-3.12-all:
|
||||
coverage_format: cobertura
|
||||
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:
|
||||
stage: test
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.12-bookworm
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
@@ -193,13 +171,13 @@ build-test:
|
||||
|
||||
test-docs:
|
||||
<<: *only-default
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.12-bookworm
|
||||
script:
|
||||
- tox -e docs
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.11-bullseye
|
||||
image: python:3.12-bookworm
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
@@ -215,10 +193,10 @@ deploy_production:
|
||||
|
||||
build-image:
|
||||
before_script: []
|
||||
image: docker:24.0
|
||||
image: docker:27.0
|
||||
stage: docker
|
||||
services:
|
||||
- docker:24.0-dind
|
||||
- docker:27-dind
|
||||
script: |
|
||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
|
||||
@@ -239,10 +217,10 @@ build-image:
|
||||
|
||||
build-image-dev:
|
||||
before_script: []
|
||||
image: docker:24.0
|
||||
image: docker:27
|
||||
stage: docker
|
||||
services:
|
||||
- docker:24.0-dind
|
||||
- docker:27-dind
|
||||
script: |
|
||||
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
|
||||
@@ -260,10 +238,10 @@ build-image-dev:
|
||||
|
||||
build-image-mr:
|
||||
before_script: []
|
||||
image: docker:24.0
|
||||
image: docker:27
|
||||
stage: docker
|
||||
services:
|
||||
- docker:24.0-dind
|
||||
- docker:27-dind
|
||||
script: |
|
||||
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
|
||||
|
||||
@@ -4,8 +4,21 @@
|
||||
# pre-commit autoupdate
|
||||
|
||||
repos:
|
||||
# Code Upgrades
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py310-plus]
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.17.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version=4.2]
|
||||
|
||||
# Formatting
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
# Identify invalid files
|
||||
- id: check-ast
|
||||
@@ -13,27 +26,24 @@ repos:
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: check-xml
|
||||
|
||||
# git checks
|
||||
- id: check-merge-conflict
|
||||
- id: check-added-large-files
|
||||
args: [ --maxkb=1000 ]
|
||||
args: [--maxkb=1000]
|
||||
- id: detect-private-key
|
||||
- id: check-case-conflict
|
||||
|
||||
# Python checks
|
||||
# - id: check-docstring-first
|
||||
# - id: check-docstring-first
|
||||
- id: debug-statements
|
||||
# - id: requirements-txt-fixer
|
||||
# - id: requirements-txt-fixer
|
||||
- id: fix-encoding-pragma
|
||||
args: [ --remove ]
|
||||
args: [--remove]
|
||||
- id: fix-byte-order-marker
|
||||
|
||||
# General quality checks
|
||||
- id: mixed-line-ending
|
||||
args: [ --fix=lf ]
|
||||
args: [--fix=lf]
|
||||
- id: trailing-whitespace
|
||||
args: [ --markdown-linebreak-ext=md ]
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
@@ -52,9 +62,8 @@ repos:
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 2.7.2
|
||||
rev: 2.7.3
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
exclude: |
|
||||
@@ -65,21 +74,26 @@ repos:
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.10.1
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.41.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [ --py38-plus ]
|
||||
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.14.0
|
||||
- id: markdownlint
|
||||
args:
|
||||
- --disable=MD013
|
||||
# Infrastructure
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 2.1.3
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version=4.2]
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.3.0
|
||||
- id: pyproject-fmt
|
||||
name: pyproject.toml formatter
|
||||
description: "Format the pyproject.toml file."
|
||||
args:
|
||||
- --indent=4
|
||||
additional_dependencies:
|
||||
- tox==4.15.0 # https://github.com/tox-dev/tox/releases/latest
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.18
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: [ --include-version-classifiers ]
|
||||
- id: validate-pyproject
|
||||
name: Validate pyproject.toml
|
||||
description: "Validate the pyproject.toml file."
|
||||
|
||||
@@ -7,11 +7,11 @@ version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-24.04
|
||||
apt_packages:
|
||||
- redis
|
||||
tools:
|
||||
python: "3.11"
|
||||
python: "3.12"
|
||||
jobs:
|
||||
post_system_dependencies:
|
||||
- redis-server --daemonize yes
|
||||
|
||||
10
.tx/config
10
.tx/config
@@ -1,10 +0,0 @@
|
||||
[main]
|
||||
host = https://app.transifex.com
|
||||
lang_map = zh-Hans: zh_Hans
|
||||
|
||||
[o:alliance-auth:p:alliance-auth:r:django-po]
|
||||
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
minimum_perc = 0
|
||||
@@ -5,7 +5,7 @@ manage online service access.
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '4.0.0b2'
|
||||
__version__ = '4.2.2'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -9,6 +9,8 @@ from .utils import (
|
||||
install_stat_tokens,
|
||||
install_stat_users)
|
||||
|
||||
from allianceauth import __version__
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
BASE_URL = "https://www.google-analytics.com"
|
||||
@@ -99,11 +101,38 @@ def analytics_daily_stats():
|
||||
event_type='Stats')
|
||||
|
||||
for appconfig in apps.get_app_configs():
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_extension_stats',
|
||||
label=appconfig.label,
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
if appconfig.label in [
|
||||
"django_celery_beat",
|
||||
"bootstrapform",
|
||||
"messages",
|
||||
"sessions",
|
||||
"auth",
|
||||
"staticfiles",
|
||||
"users",
|
||||
"addons",
|
||||
"admin",
|
||||
"humanize",
|
||||
"contenttypes",
|
||||
"sortedm2m",
|
||||
"django_bootstrap5",
|
||||
"tokens",
|
||||
"authentication",
|
||||
"services",
|
||||
"framework",
|
||||
"notifications"
|
||||
"eveonline",
|
||||
"navhelper",
|
||||
"analytics",
|
||||
"menu",
|
||||
"theme"
|
||||
]:
|
||||
pass
|
||||
else:
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_extension_stats',
|
||||
label=appconfig.label,
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
|
||||
|
||||
@shared_task()
|
||||
@@ -139,7 +168,7 @@ def send_ga_tracking_celery_event(
|
||||
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
|
||||
"user_properties": {
|
||||
"allianceauth_version": {
|
||||
"value": "allianceauth_version"
|
||||
"value": __version__
|
||||
}
|
||||
},
|
||||
'non_personalized_ads': True,
|
||||
|
||||
@@ -5,26 +5,5 @@ from django.core.checks import Warning, Error, register
|
||||
class AllianceAuthConfig(AppConfig):
|
||||
name = 'allianceauth'
|
||||
|
||||
|
||||
@register()
|
||||
def check_settings(app_configs, **kwargs):
|
||||
from django.conf import settings
|
||||
|
||||
errors = []
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL[-1] == "/":
|
||||
errors.append(Warning(
|
||||
"'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth."))
|
||||
else:
|
||||
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."))
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS"):
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
errors.append(Warning(
|
||||
"'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified."))
|
||||
else:
|
||||
errors.append(Error(
|
||||
"No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified"))
|
||||
|
||||
return errors
|
||||
def ready(self) -> None:
|
||||
import allianceauth.checks # noqa
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.conf import settings
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def active_tasks_count() -> Optional[int]:
|
||||
def active_tasks_count() -> int | None:
|
||||
"""Return count of currently active tasks
|
||||
or None if celery workers are not online.
|
||||
"""
|
||||
@@ -20,7 +20,7 @@ def active_tasks_count() -> Optional[int]:
|
||||
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."""
|
||||
try:
|
||||
tasks = itertools.chain(*data.values())
|
||||
@@ -29,7 +29,7 @@ def _tasks_count(data: dict) -> Optional[int]:
|
||||
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."""
|
||||
try:
|
||||
with current_app.connection_or_acquire() as conn:
|
||||
|
||||
@@ -2,7 +2,8 @@ from django.urls import include
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
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.contrib import messages
|
||||
@@ -17,7 +18,7 @@ def user_has_main_character(user):
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.13 on 2024-05-12 09:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0022_userprofile_theme'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userprofile',
|
||||
name='language',
|
||||
field=models.CharField(blank=True, choices=[('en', 'English'), ('de', 'German'), ('es', 'Spanish'), ('zh-hans', 'Chinese Simplified'), ('ru', 'Russian'), ('ko', 'Korean'), ('fr', 'French'), ('ja', 'Japanese'), ('it', 'Italian'), ('uk', 'Ukrainian'), ('pl', 'Polish')], default='', max_length=10, verbose_name='Language'),
|
||||
),
|
||||
]
|
||||
@@ -78,6 +78,7 @@ class UserProfile(models.Model):
|
||||
JAPANESE = 'ja', _('Japanese')
|
||||
ITALIAN = 'it', _('Italian')
|
||||
UKRAINIAN = 'uk', _('Ukrainian')
|
||||
POLISH = 'pl', _("Polish")
|
||||
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
|
||||
@@ -16,7 +16,7 @@ class _TaskCounts(NamedTuple):
|
||||
retried: int
|
||||
failed: int
|
||||
total: int
|
||||
earliest_task: Optional[dt.datetime]
|
||||
earliest_task: dt.datetime | None
|
||||
hours: int
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class EventSeries:
|
||||
|
||||
_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._key_id = str(key_id)
|
||||
self.clear()
|
||||
@@ -46,7 +46,7 @@ class EventSeries:
|
||||
my_id = self._redis.incr(self._key_counter)
|
||||
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})
|
||||
|
||||
def all(self) -> List[dt.datetime]:
|
||||
def all(self) -> list[dt.datetime]:
|
||||
"""List of all known events."""
|
||||
return [
|
||||
event[1]
|
||||
@@ -75,7 +75,7 @@ class EventSeries:
|
||||
maximum = "+inf" if not latest else latest.timestamp()
|
||||
return self._redis.zcount(self._key_sorted_set, min=minimum, max=maximum)
|
||||
|
||||
def first_event(self, earliest: dt.datetime = None) -> Optional[dt.datetime]:
|
||||
def first_event(self, earliest: dt.datetime = None) -> dt.datetime | None:
|
||||
"""Date/Time of first event. Returns `None` if series has no events.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% translate "Dashboard" %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-around align-self-center flex-wrap">
|
||||
<div class="row">
|
||||
{% for dash in views %}
|
||||
{{ dash | safe }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div id="aa-dashboard-panel-characters" class="col-12 col-xl-8 align-self-stretch p-2 ps-0 pe-0 ps-xl-0 pe-xl-2">
|
||||
<div class="card">
|
||||
<div id="aa-dashboard-panel-characters" class="col-12 col-xl-8 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<h4 class="ms-auto me-auto">
|
||||
{% translate "Characters" %}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% translate "Characters" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div>
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{% load i18n %}
|
||||
<div id="aa-dashboard-panel-membership" class="col-12 col-xl-4 align-self-stretch py-2 ps-xl-2">
|
||||
<div id="aa-dashboard-panel-membership" class="col-12 col-xl-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Membership" %}</h4>
|
||||
<div class="card-body">
|
||||
{% translate "Membership" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div>
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<h5 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h5>
|
||||
<table class="table">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% load theme_tags %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -16,7 +17,7 @@
|
||||
|
||||
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
|
||||
|
||||
{% include 'bundles/bootstrap-css-bs5.html' %}
|
||||
{% theme_css %}
|
||||
{% include 'bundles/fontawesome.html' %}
|
||||
|
||||
{% block extra_include %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-6">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card card-login border-secondary p-3">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
|
||||
186
allianceauth/checks.py
Normal file
186
allianceauth/checks.py
Normal file
@@ -0,0 +1,186 @@
|
||||
from django import db
|
||||
from django.core.checks import CheckMessage, Error, register, Warning
|
||||
from allianceauth.utils.cache import get_redis_client
|
||||
from django.utils import timezone
|
||||
from packaging.version import InvalidVersion, Version as Pep440Version
|
||||
from celery import current_app
|
||||
from django.conf import settings
|
||||
from sqlite3.dbapi2 import sqlite_version_info
|
||||
import datetime
|
||||
"""
|
||||
A = System Packages
|
||||
B = Configuration
|
||||
"""
|
||||
|
||||
|
||||
@register()
|
||||
def django_settings(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL[-1] == "/":
|
||||
errors.append(Warning("'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth.", hint="", id="allianceauth.checks.B005"))
|
||||
else:
|
||||
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"))
|
||||
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
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"))
|
||||
else:
|
||||
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"))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_redis(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
try:
|
||||
redis_version = Pep440Version(get_redis_client().info()['redis_version'])
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm Redis Version"))
|
||||
return errors
|
||||
|
||||
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):
|
||||
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"))
|
||||
elif redis_version.major == 7 and redis_version.minor == 0:
|
||||
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"))
|
||||
elif redis_version.major == 6 and redis_version.minor == 2:
|
||||
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"))
|
||||
elif redis_version.major in [6, 5]:
|
||||
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"))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_mysql(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
mysql_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm MySQL Version"))
|
||||
return errors
|
||||
|
||||
# MySQL 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):
|
||||
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"))
|
||||
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"))
|
||||
elif mysql_version.major == 8 and mysql_version.minor == 2:
|
||||
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"))
|
||||
elif mysql_version.major == 8 and mysql_version.minor == 1:
|
||||
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"))
|
||||
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
|
||||
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"))
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_mariadb(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql": # Still to find a way to determine MySQL vs MariaDB
|
||||
try:
|
||||
mariadb_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm MariaDB Version"))
|
||||
return errors
|
||||
|
||||
# MariaDB 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):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A010"))
|
||||
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"))
|
||||
if timezone.now() > timezone.datetime(year=2024, month=11, 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.A011"))
|
||||
elif mariadb_version.major == 11 and mariadb_version.minor == 1:
|
||||
errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A019"))
|
||||
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
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config.", id="allianceauth.checks.A013"))
|
||||
|
||||
# MariaDB 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):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config.", id="allianceauth.checks.A014"))
|
||||
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"))
|
||||
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):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A016"))
|
||||
elif mariadb_version.major == 10 and mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]: # Demote versions down here once EOL
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A017"))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_sqlite(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "sqlite":
|
||||
try:
|
||||
sqlite_version = Pep440Version(".".join(str(i) for i in sqlite_version_info))
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm SQLite Version"))
|
||||
return errors
|
||||
if sqlite_version.major == 3 and sqlite_version.minor < 27:
|
||||
errors.append(Error(f"SQLite {sqlite_version.public} Unsupported by Django", hint="https://pkgs.org/download/sqlite3", id="allianceauth.checks.A020"))
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def sql_settings(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
|
||||
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"))
|
||||
except KeyError:
|
||||
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"))
|
||||
|
||||
# This hasn't actually been set on AA yet
|
||||
# try:
|
||||
# if connection.settings_dict["OPTIONS"]["collation"] != "utf8mb4_unicode_ci":
|
||||
# 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"))
|
||||
# except KeyError:
|
||||
# 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"))
|
||||
|
||||
# if connection.vendor == "sqlite":
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def celery_settings(app_configs, **kwargs) -> list[CheckMessage]:
|
||||
errors: list[CheckMessage] = []
|
||||
|
||||
try:
|
||||
if current_app.conf.broker_transport_options != {'priority_steps': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'queue_order_strategy': 'priority'}:
|
||||
errors.append(Error("Celery Priorities are not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
|
||||
except KeyError:
|
||||
errors.append(Error("Celery Priorities are not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
|
||||
|
||||
try:
|
||||
if current_app.conf.broker_connection_retry_on_startup != True:
|
||||
errors.append(Error("Celery broker_connection_retry_on_startup not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
|
||||
except KeyError:
|
||||
errors.append(Error("Celery broker_connection_retry_on_startup not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
# IDEAS
|
||||
|
||||
# Any other celery things weve manually changed over the years
|
||||
# I'd be happy to add Community App checks, old versions the owners dont want to support etc.
|
||||
|
||||
|
||||
# Check Default Collation on DB
|
||||
# Check Charset Collation on all tables
|
||||
@@ -235,7 +235,7 @@ class EveCharacter(models.Model):
|
||||
return self.corporation_id == DOOMHEIM_CORPORATION_ID
|
||||
|
||||
@property
|
||||
def alliance(self) -> Union[EveAllianceInfo, None]:
|
||||
def alliance(self) -> EveAllianceInfo | None:
|
||||
"""
|
||||
Pseudo foreign key from alliance_id to EveAllianceInfo
|
||||
:raises: EveAllianceInfo.DoesNotExist
|
||||
@@ -255,7 +255,7 @@ class EveCharacter(models.Model):
|
||||
return EveCorporationInfo.objects.get(corporation_id=self.corporation_id)
|
||||
|
||||
@property
|
||||
def faction(self) -> Union[EveFactionInfo, None]:
|
||||
def faction(self) -> EveFactionInfo | None:
|
||||
"""
|
||||
Pseudo foreign key from faction_id to EveFactionInfo
|
||||
:raises: EveFactionInfo.DoesNotExist
|
||||
|
||||
@@ -13,7 +13,7 @@ from allianceauth.framework.api.user import get_sentinel_user
|
||||
|
||||
def get_main_character_from_evecharacter(
|
||||
character: EveCharacter,
|
||||
) -> Optional[EveCharacter]:
|
||||
) -> EveCharacter | None:
|
||||
"""
|
||||
Get the main character for a given EveCharacter or None when no main character is set
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def get_all_characters_from_user(user: User) -> list:
|
||||
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
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{#Usage:#}
|
||||
{# {% include "framework/dashboard/widget-title.html" with title="Foobar" %}#}
|
||||
|
||||
<div class="text-center">
|
||||
<h4 class="ms-auto me-auto mb-3">
|
||||
{{ title }}
|
||||
</h4>
|
||||
</div>
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
{% if subtitle %}
|
||||
<br>
|
||||
<small>{{ subtitle }}</small>
|
||||
<small class="text-muted">{{ subtitle }}</small>
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% endif %}
|
||||
|
||||
@@ -181,7 +181,7 @@ class AuthGroup(models.Model):
|
||||
def __str__(self):
|
||||
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 set(
|
||||
self.group_leaders.all()
|
||||
|
||||
@@ -92,6 +92,9 @@
|
||||
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
||||
{% translate "Accept" %}
|
||||
</a>
|
||||
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
||||
{% translate "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Optional
|
||||
|
||||
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"""
|
||||
if user.is_superuser:
|
||||
return self.filter(approved__isnull=True).count()
|
||||
|
||||
BIN
allianceauth/locale/cs/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/cs/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2776
allianceauth/locale/cs/LC_MESSAGES/django.po
Normal file
2776
allianceauth/locale/cs/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-17 20:02+1000\n"
|
||||
"POT-Creation-Date: 2024-05-12 19:15+1000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -26,6 +26,15 @@ msgstr ""
|
||||
msgid "Google Analytics V4"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/constants.py:6
|
||||
msgid ""
|
||||
"This software has exceeded the error limit for ESI. If you are a user, "
|
||||
"please contact the maintainer of this software. If you are a developer/"
|
||||
"maintainer, please make a greater effort in the future to receive valid "
|
||||
"responses. For tips on how, come have a chat with us in ##3rd-party-dev-and-"
|
||||
"esi on the EVE Online Discord. https://www.eveonline.com/discord"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/decorators.py:52
|
||||
msgid "A main character is required to perform that action. Add one below."
|
||||
msgstr ""
|
||||
@@ -104,27 +113,34 @@ msgstr ""
|
||||
msgid "Your user's state is now: %(state)s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:7
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:7
|
||||
#: allianceauth/menu/templates/menu/sortable-side-menu.html:14
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:7
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:33
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:54
|
||||
msgid "Characters"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:13
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:14
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:13
|
||||
#: 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:6
|
||||
msgid "Add Character"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:16
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:17
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:16
|
||||
#: 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:12
|
||||
msgid "Change Main"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:24
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:24
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:89
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:31
|
||||
@@ -133,32 +149,25 @@ msgstr ""
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:25
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:25
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:33
|
||||
msgid "Corp"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.characters.html:26
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_characters.html:26
|
||||
#: allianceauth/corputils/templates/corputils/corpstats.html:125
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:63
|
||||
msgid "Alliance"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.groups.html:5
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:5
|
||||
msgid "Membership"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.groups.html:8
|
||||
#: allianceauth/authentication/templates/authentication/dashboard_groups.html:8
|
||||
msgid "State:"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:5
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:7
|
||||
#: allianceauth/menu/templates/menu/sortable-side-menu.html:14
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:10
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/tokens.html:6
|
||||
#: allianceauth/authentication/templates/authentication/tokens.html:10
|
||||
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:62
|
||||
@@ -197,7 +206,7 @@ msgstr ""
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:55
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:115
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:118
|
||||
msgid "Character"
|
||||
msgstr ""
|
||||
|
||||
@@ -234,49 +243,49 @@ msgstr ""
|
||||
msgid "Invalid or expired activation link."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:147
|
||||
#: allianceauth/authentication/views.py:158
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot change main character to %(char)s: character owned by a different "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:153
|
||||
#: allianceauth/authentication/views.py:165
|
||||
#, python-format
|
||||
msgid "Changed main character to %(char)s"
|
||||
msgid "Changed main character to %s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:162
|
||||
#: allianceauth/authentication/views.py:179
|
||||
#, python-format
|
||||
msgid "Added %(name)s to your account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:164
|
||||
#: allianceauth/authentication/views.py:181
|
||||
#, python-format
|
||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:207
|
||||
#: allianceauth/authentication/views.py:226
|
||||
msgid ""
|
||||
"Unable to authenticate as the selected character. Please log in with the "
|
||||
"main character associated with this account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:273
|
||||
#: allianceauth/authentication/views.py:293
|
||||
msgid "Registration token has expired."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:331
|
||||
#: allianceauth/authentication/views.py:354
|
||||
msgid ""
|
||||
"Sent confirmation email. Please follow the link to confirm your email "
|
||||
"address."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:336
|
||||
#: allianceauth/authentication/views.py:360
|
||||
msgid "Confirmed your email address. Please login to continue."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:341
|
||||
#: allianceauth/authentication/views.py:366
|
||||
msgid "Registration of new accounts is not allowed at this time."
|
||||
msgstr ""
|
||||
|
||||
@@ -495,7 +504,6 @@ msgstr ""
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:44
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:92
|
||||
#: allianceauth/templates/allianceauth/top-menu.html:23
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:11
|
||||
msgid "Eve Time"
|
||||
msgstr ""
|
||||
@@ -833,7 +841,7 @@ msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:56
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:116
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:119
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:32
|
||||
msgid "Organization"
|
||||
msgstr ""
|
||||
@@ -845,7 +853,7 @@ msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:60
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:145
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:148
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit_row.html:18
|
||||
msgid "(unknown)"
|
||||
msgstr ""
|
||||
@@ -926,7 +934,7 @@ msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:36
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:57
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:117
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:120
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:29
|
||||
#: allianceauth/services/modules/openfire/forms.py:6
|
||||
msgid "Group"
|
||||
@@ -980,20 +988,21 @@ msgid "Group Membership"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:93
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:153
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156
|
||||
msgid "Accept"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:103
|
||||
msgid "No group add requests."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:157
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:160
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:104
|
||||
msgid "Reject"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:166
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:106
|
||||
msgid "No group add requests."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:169
|
||||
msgid "No group leave requests."
|
||||
msgstr ""
|
||||
|
||||
@@ -1376,6 +1385,8 @@ msgid "Sign Out"
|
||||
msgstr ""
|
||||
|
||||
#: 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:18
|
||||
msgid "Sign In"
|
||||
msgstr ""
|
||||
|
||||
@@ -1458,7 +1469,6 @@ msgid "Doctrine"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/form.py:14
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
|
||||
#: allianceauth/optimer/templates/optimer/fleetoptable.html:13
|
||||
msgid "Start Time"
|
||||
msgstr ""
|
||||
@@ -1523,6 +1533,11 @@ msgstr ""
|
||||
msgid "Form Up System"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:17
|
||||
msgid "EVE Time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/fleetoptable.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:12
|
||||
msgid "Local Time"
|
||||
@@ -1568,17 +1583,17 @@ msgstr ""
|
||||
msgid "Update fleet operation"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/views.py:89
|
||||
#: allianceauth/optimer/views.py:91
|
||||
#, python-format
|
||||
msgid "Created operation timer for %(opname)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/views.py:118
|
||||
#: allianceauth/optimer/views.py:120
|
||||
#, python-format
|
||||
msgid "Removed operation timer for %(opname)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/views.py:169
|
||||
#: allianceauth/optimer/views.py:171
|
||||
#, python-format
|
||||
msgid "Saved changes to operation timer for %(opname)s."
|
||||
msgstr ""
|
||||
@@ -2367,6 +2382,10 @@ msgstr ""
|
||||
msgid "Saved changes to SRP fleet %(fleetname)s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/esi_check.html:4
|
||||
msgid "Your Server received an ESI error response code of "
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:11
|
||||
msgid "Alliance Auth Notifications"
|
||||
msgstr ""
|
||||
@@ -2693,12 +2712,12 @@ msgstr ""
|
||||
msgid "Past Timers"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/views.py:78
|
||||
#: allianceauth/timerboard/views.py:85
|
||||
#, python-format
|
||||
msgid "Added new timer in %(system)s at %(time)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/views.py:87
|
||||
#: allianceauth/timerboard/views.py:95
|
||||
msgid "Saved changes to the timer."
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/nl/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/nl/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2789
allianceauth/locale/nl/LC_MESSAGES/django.po
Normal file
2789
allianceauth/locale/nl/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/pl_PL/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/pl_PL/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2848
allianceauth/locale/pl_PL/LC_MESSAGES/django.po
Normal file
2848
allianceauth/locale/pl_PL/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -35,12 +35,12 @@ class MenuItemAdmin(admin.ModelAdmin):
|
||||
]
|
||||
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)
|
||||
return super().get_form(request, obj, **kwargs)
|
||||
|
||||
@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."""
|
||||
if obj: # change
|
||||
if obj.hook_hash:
|
||||
@@ -104,7 +104,7 @@ class MenuItemAdmin(admin.ModelAdmin):
|
||||
@staticmethod
|
||||
def _type_from_request(
|
||||
request: HttpRequest, default=None
|
||||
) -> Optional[MenuItemType]:
|
||||
) -> MenuItemType | None:
|
||||
try:
|
||||
return MenuItemType(request.GET.get("type"))
|
||||
except ValueError:
|
||||
|
||||
@@ -14,8 +14,8 @@ class MenuItemHookCustom(MenuItemHook):
|
||||
text: str,
|
||||
classes: str,
|
||||
url_name: str,
|
||||
order: Optional[int] = None,
|
||||
navactive: Optional[List[str]] = None,
|
||||
order: int | None = None,
|
||||
navactive: list[str] | None = None,
|
||||
):
|
||||
super().__init__(text, classes, url_name, order, navactive)
|
||||
self.url = ""
|
||||
|
||||
@@ -33,8 +33,8 @@ class MenuItemHook:
|
||||
text: str,
|
||||
classes: str,
|
||||
url_name: str,
|
||||
order: Optional[int] = None,
|
||||
navactive: Optional[List[str]] = None,
|
||||
order: int | None = None,
|
||||
navactive: list[str] | None = None,
|
||||
):
|
||||
self.text = text
|
||||
self.classes = classes
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{% endif %}>
|
||||
</i>
|
||||
<a
|
||||
class="nav-link flex-fill align-self-center me-auto"
|
||||
class="nav-link flex-fill align-self-center me-auto {% if item.navactive %}{% navactive request item.navactive|join:' ' %}{% endif %}"
|
||||
{% if item.is_folder %}
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
|
||||
@@ -60,15 +60,17 @@
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://discord.gg/fjnHAmk" title="Alliance Auth Discord"><i class="fa-brands fa-discord fa-fw"></i> Alliance Auth Discord</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i> Alliance Auth Git</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
||||
<i class="fa-solid fa-gear fa-fw"></i> {% translate "Admin" %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i> Alliance Auth Git</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
||||
<i class="fa-solid fa-gear fa-fw"></i> {% translate "Admin" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% if user.is_authenticated %}
|
||||
<li>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ul id="sidebar-menu" class="navbar-nav flex-column mb-auto overflow-auto pt-2">
|
||||
<li class="d-flex flex-wrap m-2 p-2 pt-0 pb-0 mt-0 mb-0 me-0 pe-0">
|
||||
<i class="nav-link fas fa-tachometer-alt fa-fw align-self-center me-3 {% navactive request 'authentication:dashboard' %}"></i>
|
||||
<a class="nav-link flex-fill align-self-center" href="{% url 'authentication:dashboard' %}">
|
||||
<a class="nav-link flex-fill align-self-center {% navactive request 'authentication:dashboard' %}" href="{% url 'authentication:dashboard' %}">
|
||||
{% translate "Dashboard" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -56,8 +56,8 @@ class RenderedMenuItem:
|
||||
|
||||
menu_item: MenuItem
|
||||
|
||||
children: List["RenderedMenuItem"] = field(default_factory=list)
|
||||
count: Optional[int] = None
|
||||
children: list["RenderedMenuItem"] = field(default_factory=list)
|
||||
count: int | None = None
|
||||
html: str = ""
|
||||
html_id: str = ""
|
||||
|
||||
@@ -78,7 +78,7 @@ class RenderedMenuItem:
|
||||
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.
|
||||
|
||||
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
|
||||
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(
|
||||
"parent", "order", "text"
|
||||
)
|
||||
@@ -98,6 +98,10 @@ def render_menu(request: HttpRequest) -> List[RenderedMenuItem]:
|
||||
|
||||
if item.is_app_item:
|
||||
rendered_item = _render_app_item(request, hook_items, item, bs5_template)
|
||||
if rendered_item.html == "":
|
||||
# If there is no content dont render it.
|
||||
# This item has probably been hidden by permissions
|
||||
continue
|
||||
elif item.is_link_item:
|
||||
rendered_item = _render_link_item(request, item, bs5_template)
|
||||
elif item.is_folder:
|
||||
@@ -127,7 +131,7 @@ def render_menu(request: HttpRequest) -> List[RenderedMenuItem]:
|
||||
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 = {}
|
||||
for hook in get_hooks("menu_item_hook"):
|
||||
f = hook()
|
||||
@@ -157,14 +161,14 @@ def _render_link_item(
|
||||
|
||||
|
||||
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():
|
||||
if item.menu_item.is_folder:
|
||||
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 = []
|
||||
for item_id, item in rendered_items.items():
|
||||
if item.is_folder and not item.children:
|
||||
|
||||
@@ -19,6 +19,11 @@ def create_user(permissions=None, **kwargs) -> User:
|
||||
return user
|
||||
|
||||
|
||||
def create_menu_item_hook_class(**kwargs) -> MenuItemHook:
|
||||
num = next(counter_menu_item_hook)
|
||||
return type(f"GeneratedMenuItem{num}", (MenuItemHook,), {})
|
||||
|
||||
|
||||
def create_menu_item_hook(**kwargs) -> MenuItemHook:
|
||||
num = next(counter_menu_item_hook)
|
||||
new_class = type(f"GeneratedMenuItem{num}", (MenuItemHook,), {})
|
||||
|
||||
@@ -14,6 +14,7 @@ from allianceauth.menu.tests.factories import (
|
||||
create_folder_menu_item,
|
||||
create_link_menu_item,
|
||||
create_menu_item_from_hook,
|
||||
create_menu_item_hook_class,
|
||||
create_menu_item_hook_function,
|
||||
create_rendered_menu_item,
|
||||
)
|
||||
@@ -177,6 +178,44 @@ class TestRenderDefaultMenu(TestCase):
|
||||
self.assertEqual(menu[0].menu_item.text, "Alpha")
|
||||
self.assertEqual(menu[1].menu_item.text, "Bravo")
|
||||
|
||||
def test_should_remove_empty_folders_with_items_hidden(self, mock_get_hooks):
|
||||
# given
|
||||
|
||||
class TestHook(create_menu_item_hook_class()):
|
||||
text = "Dummy App No Data"
|
||||
classes = "fa-solid fa-users-gear"
|
||||
url_name = "groupmanagement:management"
|
||||
|
||||
def render(Self, request):
|
||||
# simulate no perms
|
||||
return ""
|
||||
|
||||
params = {
|
||||
"text": "Alpha",
|
||||
"classes": "fa-solid fa-users-gear",
|
||||
"url_name": "groupmanagement:management",
|
||||
}
|
||||
|
||||
alpha = TestHook(**params)
|
||||
|
||||
hooks = [lambda: alpha]
|
||||
|
||||
mock_get_hooks.return_value = hooks
|
||||
|
||||
folder = create_folder_menu_item(text="Folder", order=2)
|
||||
create_menu_item_from_hook(hooks[0], parent=folder)
|
||||
create_link_menu_item(text="Bravo", order=3) # this is all that should show
|
||||
|
||||
request = self.factory.get("/")
|
||||
|
||||
# when
|
||||
result = render_menu(request)
|
||||
|
||||
# then
|
||||
menu = list(result)
|
||||
self.assertEqual(len(menu), 1)
|
||||
self.assertEqual(menu[0].menu_item.text, "Bravo")
|
||||
|
||||
def test_should_not_include_hidden_items(self, mock_get_hooks):
|
||||
# given
|
||||
mock_get_hooks.return_value = []
|
||||
@@ -196,7 +235,6 @@ class TestRenderDefaultMenu(TestCase):
|
||||
self.assertEqual(menu[1].menu_item.text, "Charlie")
|
||||
|
||||
def test_should_not_render_hidden_folders(self, mock_get_hooks):
|
||||
# given
|
||||
# given
|
||||
menu = [
|
||||
create_menu_item_hook_function(text="Charlie", count=42),
|
||||
@@ -309,9 +347,9 @@ class TestRenderedMenuItem(TestCase):
|
||||
|
||||
|
||||
class _ParsedMenuItem(NamedTuple):
|
||||
classes: List[str]
|
||||
classes: list[str]
|
||||
text: str
|
||||
count: Optional[int]
|
||||
count: int | None
|
||||
|
||||
|
||||
def parse_html(obj: RenderedMenuItem) -> _ParsedMenuItem:
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
<div class="col-12 align-self-stretch py-2">
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Upcoming Fleets" %}</h4>
|
||||
{% translate "Upcoming Fleets" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div class="card-body">
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Operation" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "Form Up System" %}</th>
|
||||
<th class="text-center">{% translate "EVE Time" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for ops in timers %}
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Operation" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "Form Up System" %}</th>
|
||||
<th class="text-center">{% translate "Start Time" %}</th>
|
||||
<td class="text-center">
|
||||
{{ ops.operation_name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ ops.type }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ ops.system|dotlan_solar_system_url }}">{{ ops.system }}</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for ops in timers %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
{{ ops.operation_name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ ops.type }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ ops.system|dotlan_solar_system_url }}">{{ ops.system }}</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,9 +14,11 @@ from .models import OpTimer, OpTimerType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OPS_VIEW_PERMISSION = 'auth.optimer_view'
|
||||
OPS_MANAGE_PERMISSION = 'auth.optimer_management'
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.optimer_view')
|
||||
@permission_required(OPS_VIEW_PERMISSION)
|
||||
def optimer_view(request):
|
||||
"""
|
||||
View for the optimer management page
|
||||
@@ -39,7 +41,7 @@ def optimer_view(request):
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.optimer_management')
|
||||
@permission_required(OPS_MANAGE_PERMISSION)
|
||||
def add_optimer_view(request):
|
||||
"""
|
||||
View for the add optimer page
|
||||
@@ -98,7 +100,7 @@ def add_optimer_view(request):
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.optimer_management')
|
||||
@permission_required(OPS_MANAGE_PERMISSION)
|
||||
def remove_optimer(request, optimer_id):
|
||||
"""
|
||||
Remove optimer
|
||||
@@ -121,7 +123,7 @@ def remove_optimer(request, optimer_id):
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.optimer_management')
|
||||
@permission_required(OPS_MANAGE_PERMISSION)
|
||||
def edit_optimer(request, optimer_id):
|
||||
"""
|
||||
Edit optimer
|
||||
@@ -192,14 +194,22 @@ def dashboard_ops(request):
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
if request.user.has_perm(OPS_VIEW_PERMISSION):
|
||||
base_query = OpTimer.objects.select_related('eve_character', 'type')
|
||||
timers = base_query.filter(
|
||||
start__gte=timezone.now()
|
||||
)[:5]
|
||||
|
||||
base_query = OpTimer.objects.select_related('eve_character', 'type')
|
||||
timers = base_query.filter(start__gte=timezone.now())[:5]
|
||||
|
||||
if timers.count():
|
||||
context = {
|
||||
'timers': timers,
|
||||
}
|
||||
return render_to_string('optimer/dashboard.ops.html', context=context, request=request)
|
||||
if timers.count():
|
||||
context = {
|
||||
'timers': timers,
|
||||
}
|
||||
return render_to_string(
|
||||
'optimer/dashboard.ops.html',
|
||||
context=context,
|
||||
request=request
|
||||
)
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -102,6 +102,7 @@ LANGUAGES = (
|
||||
("ja", "Japanese"),
|
||||
("it", "Italian"),
|
||||
("uk", "Ukrainian"),
|
||||
("pl", "Polish"),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from string import Formatter
|
||||
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.core.exceptions import ObjectDoesNotExist
|
||||
@@ -175,7 +176,7 @@ class UrlHook:
|
||||
urls,
|
||||
namespace: 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.excluded_views = set(excluded_views or [])
|
||||
|
||||
@@ -28,7 +28,7 @@ from .models import DiscordUser # noqa
|
||||
__all__ = ["create_bot_client", "group_to_role", "server_name", "DiscordUser", "Role"]
|
||||
|
||||
|
||||
def discord_guild_id() -> Optional[int]:
|
||||
def discord_guild_id() -> int | None:
|
||||
"""Guild ID of configured Discord server.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -36,7 +36,7 @@ def calculate_roles_for_user(
|
||||
client: DiscordClient,
|
||||
discord_uid: int,
|
||||
state_name: str = None,
|
||||
) -> Tuple[RolesSet, Optional[bool]]:
|
||||
) -> tuple[RolesSet, bool | None]:
|
||||
"""Calculate current Discord roles for an Auth user.
|
||||
|
||||
Takes into account reserved groups and existing managed roles (e.g. nitro).
|
||||
@@ -68,7 +68,7 @@ def calculate_roles_for_user(
|
||||
return roles_calculated.union(roles_persistent), True
|
||||
|
||||
|
||||
def _user_group_names(user: User, state_name: str = None) -> List[str]:
|
||||
def _user_group_names(user: User, state_name: str = None) -> list[str]:
|
||||
"""Names of groups and state the given user is a member of."""
|
||||
if not state_name:
|
||||
state_name = user.profile.state.name
|
||||
@@ -77,7 +77,7 @@ def _user_group_names(user: User, state_name: str = None) -> List[str]:
|
||||
return group_names
|
||||
|
||||
|
||||
def user_formatted_nick(user: User) -> Optional[str]:
|
||||
def user_formatted_nick(user: User) -> str | None:
|
||||
"""Name of the given user's main character with name formatting applied.
|
||||
|
||||
Returns:
|
||||
@@ -90,7 +90,7 @@ def user_formatted_nick(user: User) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def group_to_role(group: Group) -> Optional[Role]:
|
||||
def group_to_role(group: Group) -> Role | None:
|
||||
"""Fetch the Discord role matching the given Django group by name.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -6,7 +6,8 @@ from enum import IntEnum
|
||||
from hashlib import md5
|
||||
from http import HTTPStatus
|
||||
from time import sleep
|
||||
from typing import Iterable, List, Optional, Set, Tuple
|
||||
from typing import List, Optional, Set, Tuple
|
||||
from collections.abc import Iterable
|
||||
from urllib.parse import urljoin
|
||||
from uuid import uuid1
|
||||
|
||||
@@ -233,7 +234,7 @@ class DiscordClient:
|
||||
|
||||
# guild roles
|
||||
|
||||
def guild_roles(self, guild_id: int, use_cache: bool = True) -> Set[Role]:
|
||||
def guild_roles(self, guild_id: int, use_cache: bool = True) -> set[Role]:
|
||||
"""Fetch all roles for this guild.
|
||||
|
||||
Args:
|
||||
@@ -268,7 +269,7 @@ class DiscordClient:
|
||||
|
||||
def create_guild_role(
|
||||
self, guild_id: int, role_name: str, **kwargs
|
||||
) -> Optional[Role]:
|
||||
) -> Role | None:
|
||||
"""Create a new guild role with the given name.
|
||||
|
||||
See official documentation for additional optional parameters.
|
||||
@@ -318,7 +319,7 @@ class DiscordClient:
|
||||
gen_key = cls._generate_hash(f'{guild_id}')
|
||||
return f'{cls._KEYPREFIX_GUILD_ROLES}__{gen_key}'
|
||||
|
||||
def match_role_from_name(self, guild_id: int, role_name: str) -> Optional[Role]:
|
||||
def match_role_from_name(self, guild_id: int, role_name: str) -> Role | None:
|
||||
"""Fetch Discord role matching the given name (cached).
|
||||
|
||||
Args:
|
||||
@@ -333,7 +334,7 @@ class DiscordClient:
|
||||
|
||||
def match_or_create_roles_from_names(
|
||||
self, guild_id: int, role_names: Iterable[str]
|
||||
) -> List[Tuple[Role, bool]]:
|
||||
) -> list[tuple[Role, bool]]:
|
||||
"""Fetch or create Discord roles matching the given names (cached).
|
||||
|
||||
Will try to match with existing roles names
|
||||
@@ -361,7 +362,7 @@ class DiscordClient:
|
||||
|
||||
def match_or_create_role_from_name(
|
||||
self, guild_id: int, role_name: str, guild_roles: RolesSet = None
|
||||
) -> Tuple[Role, bool]:
|
||||
) -> tuple[Role, bool]:
|
||||
"""Fetch or create Discord role matching the given name.
|
||||
|
||||
Will try to match with existing roles names
|
||||
@@ -418,7 +419,7 @@ class DiscordClient:
|
||||
access_token: str,
|
||||
role_ids: list = None,
|
||||
nick: str = None
|
||||
) -> Optional[bool]:
|
||||
) -> bool | None:
|
||||
"""Adds a user to the guild.
|
||||
|
||||
Returns:
|
||||
@@ -442,7 +443,7 @@ class DiscordClient:
|
||||
return None
|
||||
return False
|
||||
|
||||
def guild_member(self, guild_id: int, user_id: int) -> Optional[GuildMember]:
|
||||
def guild_member(self, guild_id: int, user_id: int) -> GuildMember | None:
|
||||
"""Fetch info for a guild member.
|
||||
|
||||
Args:
|
||||
@@ -461,8 +462,8 @@ class DiscordClient:
|
||||
return GuildMember.from_dict(r.json())
|
||||
|
||||
def modify_guild_member(
|
||||
self, guild_id: int, user_id: int, role_ids: List[int] = None, nick: str = None
|
||||
) -> Optional[bool]:
|
||||
self, guild_id: int, user_id: int, role_ids: list[int] = None, nick: str = None
|
||||
) -> bool | None:
|
||||
"""Set properties of a guild member.
|
||||
|
||||
Args:
|
||||
@@ -501,7 +502,7 @@ class DiscordClient:
|
||||
return True
|
||||
return False
|
||||
|
||||
def remove_guild_member(self, guild_id: int, user_id: int) -> Optional[bool]:
|
||||
def remove_guild_member(self, guild_id: int, user_id: int) -> bool | None:
|
||||
"""Remove a member from a guild.
|
||||
|
||||
Args:
|
||||
@@ -529,7 +530,7 @@ class DiscordClient:
|
||||
|
||||
def add_guild_member_role(
|
||||
self, guild_id: int, user_id: int, role_id: int
|
||||
) -> Optional[bool]:
|
||||
) -> bool | None:
|
||||
"""Adds a role to a guild member
|
||||
|
||||
Returns:
|
||||
@@ -549,7 +550,7 @@ class DiscordClient:
|
||||
|
||||
def remove_guild_member_role(
|
||||
self, guild_id: int, user_id: int, role_id: int
|
||||
) -> Optional[bool]:
|
||||
) -> bool | None:
|
||||
"""Remove a role to a guild member
|
||||
|
||||
Args:
|
||||
@@ -572,7 +573,7 @@ class DiscordClient:
|
||||
return True
|
||||
return False
|
||||
|
||||
def guild_member_roles(self, guild_id: int, user_id: int) -> Optional[RolesSet]:
|
||||
def guild_member_roles(self, guild_id: int, user_id: int) -> RolesSet | None:
|
||||
"""Fetch the current guild roles of a guild member.
|
||||
|
||||
Args:
|
||||
@@ -821,6 +822,6 @@ class DiscordClient:
|
||||
return md5(key.encode('utf-8')).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_role_ids(role_ids: Iterable[int]) -> List[int]:
|
||||
def _sanitize_role_ids(role_ids: Iterable[int]) -> list[int]:
|
||||
"""Sanitize a list of role IDs, i.e. make sure its a list of unique integers."""
|
||||
return [int(role_id) for role_id in set(role_ids)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from copy import copy
|
||||
from typing import Iterable, List, Optional, Set, Tuple
|
||||
from typing import List, Optional, Set, Tuple
|
||||
from collections.abc import Iterable
|
||||
|
||||
from .models import Role
|
||||
|
||||
@@ -50,7 +51,7 @@ class RolesSet:
|
||||
def __len__(self):
|
||||
return len(self._roles.keys())
|
||||
|
||||
def has_roles(self, role_ids: Set[int]) -> bool:
|
||||
def has_roles(self, role_ids: set[int]) -> bool:
|
||||
"""True if this objects contains all roles defined by given role_ids
|
||||
incl. managed roles.
|
||||
"""
|
||||
@@ -58,7 +59,7 @@ class RolesSet:
|
||||
all_role_ids = self._roles.keys()
|
||||
return role_ids.issubset(all_role_ids)
|
||||
|
||||
def ids(self) -> Set[int]:
|
||||
def ids(self) -> set[int]:
|
||||
"""Set of all role IDs."""
|
||||
return set(self._roles.keys())
|
||||
|
||||
@@ -114,7 +115,7 @@ class RolesSet:
|
||||
new_ids = self.ids().difference(other.ids())
|
||||
return self.subset(role_ids=new_ids)
|
||||
|
||||
def role_by_name(self, role_name: str) -> Optional[Role]:
|
||||
def role_by_name(self, role_name: str) -> Role | None:
|
||||
"""Role if one with matching name is found else None."""
|
||||
role_name = Role.sanitize_name(role_name)
|
||||
if role_name in self._roles_by_name:
|
||||
@@ -123,7 +124,7 @@ class RolesSet:
|
||||
|
||||
@classmethod
|
||||
def create_from_matched_roles(
|
||||
cls, matched_roles: List[Tuple[Role, bool]]
|
||||
cls, matched_roles: list[tuple[Role, bool]]
|
||||
) -> "RolesSet":
|
||||
"""Create new instance from the given list of matches roles.
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class Guild:
|
||||
|
||||
id: int
|
||||
name: str
|
||||
roles: FrozenSet[Role]
|
||||
roles: frozenset[Role]
|
||||
|
||||
def __post_init__(self):
|
||||
object.__setattr__(self, "id", int(self.id))
|
||||
@@ -95,7 +95,7 @@ class GuildMember:
|
||||
|
||||
_NICK_MAX_CHARS = 32
|
||||
|
||||
roles: FrozenSet[int]
|
||||
roles: frozenset[int]
|
||||
nick: str = None
|
||||
user: User = None
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class DiscordUser(models.Model):
|
||||
logger.warning('Failed to update nickname for %s', self.user)
|
||||
return success
|
||||
|
||||
def update_groups(self, state_name: str = None) -> Optional[bool]:
|
||||
def update_groups(self, state_name: str = None) -> bool | None:
|
||||
"""update groups for a user based on his current group memberships.
|
||||
Will add or remove roles of a user as needed.
|
||||
|
||||
@@ -134,7 +134,7 @@ class DiscordUser(models.Model):
|
||||
logger.info('No need to update roles for user %s', self.user)
|
||||
return True
|
||||
|
||||
def update_username(self) -> Optional[bool]:
|
||||
def update_username(self) -> bool | None:
|
||||
"""Updates the username incl. the discriminator
|
||||
from the Discord server and saves it
|
||||
|
||||
@@ -159,7 +159,7 @@ class DiscordUser(models.Model):
|
||||
notify_user: bool = False,
|
||||
is_rate_limited: bool = True,
|
||||
handle_api_exceptions: bool = False
|
||||
) -> Optional[bool]:
|
||||
) -> bool | None:
|
||||
"""Deletes the Discount user both on the server and locally
|
||||
|
||||
Params:
|
||||
|
||||
@@ -438,7 +438,7 @@ class TestUserHasAccount(NoSocketsTestCase):
|
||||
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
|
||||
|
||||
def test_return_false_if_user_does_not_exist(self):
|
||||
my_user = User(username='Dummy')
|
||||
my_user = AuthUtils.create_user("test_return_false_if_user_does_not_exist")
|
||||
self.assertFalse(DiscordUser.objects.user_has_account(my_user))
|
||||
|
||||
def test_return_false_if_not_called_with_user_object(self):
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block url %}
|
||||
<a href="{{ service_url }}">{{ service_url }}</a>
|
||||
{% if username != '' %}
|
||||
<a href="mumble://{{ connect_url }}">{{ service_url }}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block user %}
|
||||
|
||||
@@ -181,7 +181,7 @@ class SmfManager:
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, username, email_address, groups, main_character: EveCharacter) -> Tuple:
|
||||
def add_user(cls, username, email_address, groups, main_character: EveCharacter) -> tuple:
|
||||
"""
|
||||
Add a user to SMF
|
||||
:param username:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
<div id="esi-alert" class="col-12 align-self-stretch py-2 collapse">
|
||||
<div id="esi-alert" class="col-12 collapse">
|
||||
<div class="alert alert-warning">
|
||||
<p class="text-center ">{% translate 'Your Server received an ESI error response code of ' %}<b id="esi-code">?</b></p>
|
||||
<hr>
|
||||
@@ -23,7 +23,7 @@
|
||||
console.log("ESI Check: ", JSON.stringify(responseJson, null, 2));
|
||||
|
||||
const status = responseJson.status;
|
||||
if (status != 200) {
|
||||
if (status !== 200) {
|
||||
elemCode.textContent = status
|
||||
elemMessage.textContent = responseJson.data.error;
|
||||
new bootstrap.Collapse(elemCard, {
|
||||
|
||||
@@ -2,48 +2,43 @@
|
||||
{% load humanize %}
|
||||
|
||||
{% if notifications %}
|
||||
<div id="aa-dashboard-panel-admin-notifications" class="col-12 align-self-stretch pb-2">
|
||||
<div id="aa-dashboard-panel-admin-notifications" class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="w-100 align-self-stretch">
|
||||
<h4 class="ms-auto me-auto text-center">
|
||||
{% translate "Alliance Auth Notifications" %}
|
||||
</h4>
|
||||
{% translate "Alliance Auth Notifications" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div class="card-body">
|
||||
<ul class="list-group">
|
||||
{% for notif in notifications %}
|
||||
<li class="list-group-item">
|
||||
{% if notif.state == 'opened' %}
|
||||
<span class="badge bg-success">{% translate "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">{% translate "Closed" %}</span>
|
||||
{% endif %}
|
||||
<a href="{{ notif.web_url }}" target="_blank">#{{ notif.iid }} {{ notif.title }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class="alert alert-primary" role="alert">
|
||||
{% translate "No notifications at this time" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="text-end">
|
||||
<a href="https://gitlab.com/allianceauth/allianceauth/issues" target="_blank" class="me-1">
|
||||
<span class="badge" style="background-color: rgb(230 83 40);">
|
||||
<i class="fab fa-gitlab" aria-hidden="true"></i>
|
||||
{% translate 'Powered by GitLab' %}
|
||||
</span>
|
||||
</a>
|
||||
<a href="https://discord.com/invite/fjnHAmk" target="_blank">
|
||||
<span class="badge" style="background-color: rgb(110 133 211);">
|
||||
<i class="fab fa-discord" aria-hidden="true"></i>
|
||||
{% translate 'Support Discord' %}
|
||||
</span>
|
||||
</a>
|
||||
<div>
|
||||
<ul class="list-group">
|
||||
{% for notif in notifications %}
|
||||
<li class="list-group-item">
|
||||
{% if notif.state == 'opened' %}
|
||||
<span class="badge bg-success me-2">{% translate "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger me-2">{% translate "Closed" %}</span>
|
||||
{% endif %}
|
||||
<a href="{{ notif.web_url }}" target="_blank">#{{ notif.iid }} {{ notif.title }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class="alert alert-primary" role="alert">
|
||||
{% translate "No notifications at this time" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="text-end pt-3">
|
||||
<a href="https://gitlab.com/allianceauth/allianceauth/issues" target="_blank" class="me-1 text-decoration-none">
|
||||
<span class="badge" style="background-color: rgb(230 83 40);">
|
||||
<i class="fab fa-gitlab" aria-hidden="true"></i>
|
||||
{% translate 'Powered by GitLab' %}
|
||||
</span>
|
||||
</a>
|
||||
<a href="https://discord.com/invite/fjnHAmk" target="_blank" class="text-decoration-none">
|
||||
<span class="badge" style="background-color: rgb(110 133 211);">
|
||||
<i class="fab fa-discord" aria-hidden="true"></i>
|
||||
{% translate 'Support Discord' %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,25 +46,24 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-12 align-self-stretch pb-2">
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body d-flex flex-row flex-wrap">
|
||||
<div class="card-body row">
|
||||
<div id="aa-dashboard-panel-software-version" class="col-xl-6 col-lg-12 col-md-12 col-sm-12">
|
||||
<h4 class="ms-auto me-auto text-center">
|
||||
{% translate "Software Version" %}
|
||||
</h4>
|
||||
{% translate "Software Version" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<ul class="list-group list-group-horizontal w-100" role="group" aria-label="{% translate 'Software Version' %}">
|
||||
<li class="list-group-item w-100">
|
||||
<div class="btn w-100 cursor-default">
|
||||
<div class="btn h-100 w-100 cursor-default">
|
||||
<h5 class="list-group-item-heading">{% translate "Current" %}</h5>
|
||||
<p class="list-group-item-text">{{ current_version }}</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="list-group-item bg-{% if latest_patch %}success{% elif latest_minor %}warning{% else %}danger{% endif %} w-100">
|
||||
<a class="btn w-100" href="https://gitlab.com/allianceauth/allianceauth/-/releases/v{{ latest_patch_version }}">
|
||||
<a class="btn h-100 w-100" href="https://gitlab.com/allianceauth/allianceauth/-/releases/v{{ latest_patch_version }}">
|
||||
<h5 class="list-group-item-heading">{% translate "Latest Stable" %}</h5>
|
||||
|
||||
<p class="list-group-item-text">
|
||||
@@ -82,7 +76,7 @@
|
||||
|
||||
{% if latest_beta %}
|
||||
<li class="list-group-item bg-info w-100">
|
||||
<a class="btn w-100" href="https://gitlab.com/allianceauth/allianceauth/-/releases/v{{ latest_beta_version }}">
|
||||
<a class="btn h-100 w-100" href="https://gitlab.com/allianceauth/allianceauth/-/releases/v{{ latest_beta_version }}">
|
||||
<h5 class="list-group-item-heading">{% translate "Latest Pre-Release" %}</h5>
|
||||
|
||||
<p class="list-group-item-text">
|
||||
@@ -98,11 +92,10 @@
|
||||
</div>
|
||||
|
||||
<div id="aa-dashboard-panel-task-queue" class="col-xl-6 col-lg-12 col-md-12 col-sm-12">
|
||||
<h4 class="ms-auto me-auto text-center">
|
||||
{% translate "Task Queue" %}
|
||||
</h4>
|
||||
{% translate "Task Queue" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<p>
|
||||
{% blocktranslate with total=tasks_total|intcomma latest=earliest_task|timesince|default:"?" %}
|
||||
Status of {{ total }} processed tasks • last {{ latest }}
|
||||
|
||||
@@ -29,56 +29,53 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
.nav-padding {
|
||||
padding-top: {% header_padding_size %} !important;
|
||||
}
|
||||
{% endif %}
|
||||
.nav-padding {
|
||||
padding-top: {% header_padding_size %} !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% if user.is_authenticated %}
|
||||
<!-- Top Menu, Blocks don't work in "include" tagged views -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark fixed-top bg-primary">
|
||||
<div class="container-fluid justify-content-start">
|
||||
{% if user.is_authenticated %}
|
||||
<a class="navbar-brand" data-bs-toggle="collapse" data-bs-target="#sidebar" role="button">
|
||||
<i class="fa-solid fa-bars ms-2 me-2"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<!-- Top Menu, Blocks don't work in "include" tagged views -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark fixed-top bg-primary">
|
||||
<div class="container-fluid justify-content-start">
|
||||
<a class="navbar-brand" data-bs-toggle="collapse" data-bs-target="#sidebar" role="button">
|
||||
<i class="fa-solid fa-bars ms-2 me-2"></i>
|
||||
</a>
|
||||
|
||||
<div class="navbar-brand">
|
||||
{% block header_nav_brand %}{{ SITE_NAME }}{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse ms-2 px-2" id="navbarexpand">
|
||||
<ul id="nav-left" class="nav navbar-nav me-auto">
|
||||
{% block header_nav_collapse_left %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
|
||||
<ul id="nav-right" class="nav navbar-nav">
|
||||
{% block header_nav_collapse_right %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
|
||||
<ul id="nav-right-character-control" class="nav navbar-nav">
|
||||
{% block header_nav_user_character_control %} <!-- Default to add char and swap main -->
|
||||
{% include 'allianceauth/top-menu-rh-default.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% include 'menu/menu-notification-block.html' %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a class="navbar-toggler navbar-brand border-0 collapsed" data-bs-toggle="collapse" data-bs-target="#navbarexpand" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation" style="margin-left: auto;">
|
||||
<i class="fa-solid fa-chevron-up"></i>
|
||||
</a>
|
||||
<div class="navbar-brand">
|
||||
{% block header_nav_brand %}{{ SITE_NAME }}{% endblock %}
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<div class="collapse navbar-collapse ms-2 px-2" id="navbarexpand">
|
||||
<ul id="nav-left" class="nav navbar-nav me-auto">
|
||||
{% block header_nav_collapse_left %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
|
||||
<ul id="nav-right" class="nav navbar-nav">
|
||||
{% block header_nav_collapse_right %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
|
||||
<ul id="nav-right-character-control" class="nav navbar-nav">
|
||||
{% block header_nav_user_character_control %} <!-- Default to add char and swap main -->
|
||||
{% include 'allianceauth/top-menu-rh-default.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
{% include 'menu/menu-notification-block.html' %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a class="navbar-toggler navbar-brand border-0 collapsed" data-bs-toggle="collapse" data-bs-target="#navbarexpand" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation" style="margin-left: auto;">
|
||||
<i class="fa-solid fa-chevron-up"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- End Top Menu -->
|
||||
|
||||
<!-- Body -->
|
||||
@@ -98,23 +95,27 @@
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// TODO Extend this to the groups in the sidebar too.
|
||||
// TODO Move to own JS file
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
sidebar.addEventListener("shown.bs.collapse", () => {
|
||||
localStorage.removeItem("sidebar_" + sidebar.id);
|
||||
sidebar.addEventListener('shown.bs.collapse', () => {
|
||||
localStorage.removeItem('sidebar_' + sidebar.id);
|
||||
});
|
||||
|
||||
sidebar.addEventListener("hidden.bs.collapse", () => {
|
||||
localStorage.setItem("sidebar_" + sidebar.id, true);
|
||||
sidebar.addEventListener('hidden.bs.collapse', () => {
|
||||
localStorage.setItem('sidebar_' + sidebar.id, 'closed');
|
||||
});
|
||||
|
||||
if (localStorage.getItem("sidebar_" + sidebar.id) === "true") {
|
||||
sidebar.classList.remove("show")
|
||||
if (localStorage.getItem('sidebar_' + sidebar.id) === 'closed') {
|
||||
sidebar.classList.remove('show')
|
||||
} else {
|
||||
sidebar.classList.add("show")
|
||||
}
|
||||
|
||||
const activeChildMenuItem = document.querySelector('#sidebar-menu li ul li a.active');
|
||||
if (activeChildMenuItem) {
|
||||
activeChildMenuItem.parentElement.parentElement.classList.add('show');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
|
||||
{% load theme_tags %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ error_title }}
|
||||
{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
{% include "framework/header/page-header.html" with title=error_title %}
|
||||
<div class="d-flex flex-column" style="height: calc(100vh - {% header_padding_size %}); margin-top: -1rem; margin-bottom: -1rem;">
|
||||
<div class="d-flex flex-grow-1 justify-content-center align-items-center">
|
||||
<div>
|
||||
{% include "framework/header/page-header.html" with title=error_title %}
|
||||
|
||||
<div class="text-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
fill="currentColor"
|
||||
class="bi bi-exclamation-triangle"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.146.146 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.163.163 0 0 1-.054.06.116.116 0 0 1-.066.017H1.146a.115.115 0 0 1-.066-.017.163.163 0 0 1-.054-.06.176.176 0 0 1 .002-.183L7.884 2.073a.147.147 0 0 1 .054-.057zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z"
|
||||
/>
|
||||
<path
|
||||
d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
fill="currentColor"
|
||||
class="bi bi-exclamation-triangle"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.146.146 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.163.163 0 0 1-.054.06.116.116 0 0 1-.066.017H1.146a.115.115 0 0 1-.066-.017.163.163 0 0 1-.054-.06.176.176 0 0 1 .002-.183L7.884 2.073a.147.147 0 0 1 .054-.057zm1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566z"
|
||||
/>
|
||||
<path
|
||||
d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p class="text-center">{{ error_message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center">{{ error_message }}</p>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'authentication:add_character' %}" class="nav-link" title="{% translate 'Add Character' %}">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
@@ -12,3 +12,10 @@
|
||||
<span class="d-lg-none d-md-inline m-2">{% translate "Change Main" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'authentication:login' %}" class="nav-link" title="{% translate 'Sign In' %}">
|
||||
<i class="fa-solid fa-right-to-bracket fa-fw "></i> {% translate "Sign In" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@@ -262,7 +262,7 @@ class AuthUtils:
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_user_by_name(
|
||||
cls, perms: List[str], user: User, disconnect_signals: bool = True
|
||||
cls, perms: list[str], user: User, disconnect_signals: bool = True
|
||||
) -> User:
|
||||
"""Add permissions given by name to a user
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.apps import AppConfig
|
||||
class BootstrapThemeConfig(AppConfig):
|
||||
name = "allianceauth.theme.bootstrap"
|
||||
label = "bootstrap"
|
||||
version = "5.3.0"
|
||||
version = "5.3.3"
|
||||
verbose_name = f"Bootstrap v{version}"
|
||||
|
||||
def ready(self):
|
||||
|
||||
@@ -3,16 +3,16 @@ from allianceauth.theme.hooks import ThemeHook
|
||||
|
||||
|
||||
CSS_STATICS = [{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css",
|
||||
"integrity": "sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css",
|
||||
"integrity": "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg=="
|
||||
}]
|
||||
|
||||
JS_STATICS = [{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js",
|
||||
"integrity": "sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ=="
|
||||
}, {
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.min.js",
|
||||
"integrity": "sha512-WW8/jxkELe2CAiE4LvQfwm1rajOS8PHasCCx+knHG0gBHt8EXxS6T6tJRTGuDQVnluuAvMxWF4j8SNFDKceLFg=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js",
|
||||
"integrity": "sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="
|
||||
}]
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.apps import AppConfig
|
||||
class DarklyThemeConfig(AppConfig):
|
||||
name = "allianceauth.theme.darkly"
|
||||
label = "darkly"
|
||||
version = "5.3.0"
|
||||
version = "5.3.3"
|
||||
verbose_name = f"Bootswatch Darkly v{version}"
|
||||
|
||||
def ready(self):
|
||||
|
||||
@@ -14,15 +14,15 @@ class DarklyThemeHook(ThemeHook):
|
||||
"Darkly",
|
||||
"Flatly in night mode!",
|
||||
css=[{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.3/darkly/bootstrap.min.css",
|
||||
"integrity": "sha512-YRcmztDXzJQCCBk2YUiEAY+r74gu/c9UULMPTeLsAp/Tw5eXiGkYMPC4tc4Kp1jx/V9xjEOCVpBe4r6Lx6n5dA=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/darkly/bootstrap.min.css",
|
||||
"integrity": "sha512-HDszXqSUU0om4Yj5dZOUNmtwXGWDa5ppESlX98yzbBS+z+3HQ8a/7kcdI1dv+jKq+1V5b01eYurE7+yFjw6Rdg=="
|
||||
}],
|
||||
js=[{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js",
|
||||
"integrity": "sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ=="
|
||||
}, {
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.min.js",
|
||||
"integrity": "sha512-1/RvZTcCDEUjY/CypiMz+iqqtaoQfAITmNSJY17Myp4Ms5mdxPS5UV7iOfdZoxcGhzFbOm6sntTKJppjvuhg4g=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js",
|
||||
"integrity": "sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="
|
||||
}],
|
||||
header_padding="4.5em"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.apps import AppConfig
|
||||
class FlatlyThemeConfig(AppConfig):
|
||||
name = "allianceauth.theme.flatly"
|
||||
label = "flatly"
|
||||
version = "5.3.0"
|
||||
version = "5.3.3"
|
||||
verbose_name = f"Bootswatch Flatly v{version}"
|
||||
|
||||
def ready(self):
|
||||
|
||||
@@ -14,15 +14,15 @@ class FlatlyThemeHook(ThemeHook):
|
||||
"Flatly",
|
||||
"Flat and modern!",
|
||||
css=[{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/flatly/bootstrap.min.css",
|
||||
"integrity": "sha512-rx+BMEjKes84XHg1erhvtq7Mqxm/lm6w4WMoCtDAaTMtUzT5iK5hNTu8mc2+yPNSldAX5hheN/ZhtNQjjYy5nA=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/flatly/bootstrap.min.css",
|
||||
"integrity": "sha512-qoT4KwnRpAQ9uczPsw7GunsNmhRnYwSlE2KRCUPRQHSkDuLulCtDXuC2P/P6oqr3M5hoGagUG9pgHDPkD2zCDA=="
|
||||
}],
|
||||
js=[{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js",
|
||||
"integrity": "sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ=="
|
||||
}, {
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.min.js",
|
||||
"integrity": "sha512-WW8/jxkELe2CAiE4LvQfwm1rajOS8PHasCCx+knHG0gBHt8EXxS6T6tJRTGuDQVnluuAvMxWF4j8SNFDKceLFg=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js",
|
||||
"integrity": "sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="
|
||||
}],
|
||||
header_padding="4.5em"
|
||||
)
|
||||
|
||||
@@ -10,12 +10,12 @@ class ThemeHook:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
description: str,
|
||||
css: List[dict],
|
||||
js: List[dict],
|
||||
css_template: Optional[str] = None,
|
||||
js_template: Optional[str] = None,
|
||||
html_tags: Optional[str] = "",
|
||||
header_padding: Optional[str] = "4em"):
|
||||
css: list[dict],
|
||||
js: list[dict],
|
||||
css_template: str | None = None,
|
||||
js_template: str | None = None,
|
||||
html_tags: str | None = "",
|
||||
header_padding: str | None = "4em"):
|
||||
"""
|
||||
:param name: Theme python name
|
||||
:type name: str
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.apps import AppConfig
|
||||
class MateriaThemeConfig(AppConfig):
|
||||
name = "allianceauth.theme.materia"
|
||||
label = "materia"
|
||||
version = "5.3.0"
|
||||
version = "5.3.3"
|
||||
verbose_name = f"Bootswatch Materia v{version}"
|
||||
|
||||
def ready(self):
|
||||
|
||||
@@ -14,15 +14,15 @@ class MateriaThemeHook(ThemeHook):
|
||||
"Materia",
|
||||
"Material is the metaphor",
|
||||
css=[{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/materia/bootstrap.min.css",
|
||||
"integrity": "sha512-4+PCWoNUxEeasuW2ipP8Avsr7X/oS61Kz2CLdwS6ZfHt7jLuzAAcIfPlWLg4aGaDNo0GSmTOmM9biaqnmo6P7g=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/materia/bootstrap.min.css",
|
||||
"integrity": "sha512-2S9Do+uTmZmmJpdmAcOKdUrK/YslcvAuRfIF2ws8+BW9AvZXMRZM+o8Wq+PZrfISD6ZlIaeCWWZAdeprXIoYuQ=="
|
||||
}],
|
||||
js=[{
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js",
|
||||
"integrity": "sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ=="
|
||||
}, {
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.min.js",
|
||||
"integrity": "sha512-WW8/jxkELe2CAiE4LvQfwm1rajOS8PHasCCx+knHG0gBHt8EXxS6T6tJRTGuDQVnluuAvMxWF4j8SNFDKceLFg=="
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js",
|
||||
"integrity": "sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="
|
||||
}],
|
||||
header_padding="5.25em"
|
||||
)
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
<div class="col-12 align-self-stretch py-2">
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Upcoming Timers" %}</h4>
|
||||
<div class="card-body">
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Details" %}</th>
|
||||
<th class="text-center">{% translate "Timer" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "System" %}</th>
|
||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% translate "Upcoming Timers" as widget_title %}
|
||||
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||
|
||||
<tbody>
|
||||
{% for timer in timers %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
{{ timer.details }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ timer.get_timer_type_display }}
|
||||
</td>
|
||||
<td class="text-center" nowrap>
|
||||
{% if timer.objective == "Hostile" %}
|
||||
<div class="badge bg-danger">
|
||||
{% translate "Hostile" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if timer.objective == "Friendly" %}
|
||||
<div class="badge bg-primary">
|
||||
{% translate "Friendly" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if timer.objective == "Neutral" %}
|
||||
<div class="badge bg-default">
|
||||
{% translate "Neutral" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center"><a href="{{ timer.system|dotlan_solar_system_url }}">
|
||||
{{ timer.system }} {{ timer.planet_moon }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ timer.eve_time | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Details" %}</th>
|
||||
<th class="text-center">{% translate "Timer" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "System" %}</th>
|
||||
<th class="text-center">{% translate "EVE Time" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for timer in timers %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
{{ timer.details }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ timer.get_timer_type_display }}
|
||||
</td>
|
||||
<td class="text-center" nowrap>
|
||||
{% if timer.objective == "Hostile" %}
|
||||
<div class="badge bg-danger">
|
||||
{% translate "Hostile" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if timer.objective == "Friendly" %}
|
||||
<div class="badge bg-primary">
|
||||
{% translate "Friendly" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if timer.objective == "Neutral" %}
|
||||
<div class="badge bg-default">
|
||||
{% translate "Neutral" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center"><a href="{{ timer.system|dotlan_solar_system_url }}">
|
||||
{{ timer.system }} {{ timer.planet_moon }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ timer.eve_time | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
{% for timer in timers %}
|
||||
{% if timer.important == True %}
|
||||
<tr class="danger">
|
||||
<tr class="bg-danger bg-opacity-25">
|
||||
{% else %}
|
||||
<tr class="info">
|
||||
<tr class="bg-info bg-opacity-25">
|
||||
{% endif %}
|
||||
|
||||
<td style="width: 150px;" class="text-center">
|
||||
|
||||
@@ -20,6 +20,9 @@ from allianceauth.timerboard.models import Timer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TIMER_VIEW_PERMISSION = 'auth.timer_view'
|
||||
TIMER_MANAGE_PERMISSION = 'auth.timer_management'
|
||||
|
||||
|
||||
class BaseTimerView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
pass
|
||||
@@ -27,7 +30,7 @@ class BaseTimerView(LoginRequiredMixin, PermissionRequiredMixin, View):
|
||||
|
||||
class TimerView(BaseTimerView):
|
||||
template_name = 'timerboard/view.html'
|
||||
permission_required = 'auth.timer_view'
|
||||
permission_required = TIMER_VIEW_PERMISSION
|
||||
|
||||
def get(self, request):
|
||||
logger.debug(f"timer_view called by user {request.user}")
|
||||
@@ -48,7 +51,7 @@ class TimerView(BaseTimerView):
|
||||
|
||||
|
||||
class TimerManagementView(BaseTimerView):
|
||||
permission_required = 'auth.timer_management'
|
||||
permission_required = TIMER_MANAGE_PERMISSION
|
||||
index_redirect = 'timerboard:view'
|
||||
success_url = reverse_lazy(index_redirect)
|
||||
model = Timer
|
||||
@@ -74,8 +77,13 @@ class AddTimerView(TimerManagementView, AddUpdateMixin, CreateView):
|
||||
def form_valid(self, form):
|
||||
result = super().form_valid(form)
|
||||
timer = self.object
|
||||
logger.info(f"Created new timer in {timer.system} at {timer.eve_time} by user {self.request.user}")
|
||||
messages.success(self.request, _('Added new timer in %(system)s at %(time)s.') % {"system": timer.system, "time": timer.eve_time})
|
||||
logger.info(
|
||||
f"Created new timer in {timer.system} at {timer.eve_time} by user {self.request.user}"
|
||||
)
|
||||
messages.success(
|
||||
self.request,
|
||||
_('Added new timer in %(system)s at %(time)s.') % {"system": timer.system, "time": timer.eve_time}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -89,22 +97,33 @@ class EditTimerView(TimerManagementView, AddUpdateMixin, UpdateView):
|
||||
|
||||
|
||||
class RemoveTimerView(TimerManagementView, DeleteView):
|
||||
form_class = TimerForm
|
||||
pass
|
||||
|
||||
|
||||
def dashboard_timers(request):
|
||||
try:
|
||||
corp = request.user.profile.main_character.corporation
|
||||
except (EveCorporationInfo.DoesNotExist, AttributeError):
|
||||
return ""
|
||||
if request.user.has_perm(TIMER_VIEW_PERMISSION):
|
||||
try:
|
||||
corp = request.user.profile.main_character.corporation
|
||||
except (EveCorporationInfo.DoesNotExist, AttributeError):
|
||||
return ""
|
||||
|
||||
timers = Timer.objects.select_related('eve_character').filter((Q(eve_corp__isnull=True) | Q(eve_corp=corp)) ,eve_time__gte=timezone.now())[:5]
|
||||
timers = Timer.objects.select_related(
|
||||
'eve_character'
|
||||
).filter(
|
||||
(Q(corp_timer=True) & Q(eve_corp=corp)) | Q(corp_timer=False),
|
||||
eve_time__gte=timezone.now()
|
||||
)[:5]
|
||||
|
||||
if timers.count():
|
||||
context = {
|
||||
'timers': timers,
|
||||
}
|
||||
if timers.count():
|
||||
context = {
|
||||
'timers': timers,
|
||||
}
|
||||
|
||||
return render_to_string(template_name='timerboard/dashboard.timers.html', context=context, request=request)
|
||||
return render_to_string(
|
||||
template_name='timerboard/dashboard.timers.html',
|
||||
context=context, request=request
|
||||
)
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import List, Iterable, Callable
|
||||
from typing import List
|
||||
from collections.abc import Iterable, Callable
|
||||
|
||||
from django.urls import include
|
||||
import esi.urls
|
||||
@@ -24,8 +25,8 @@ admin.site.site_header = NAME
|
||||
|
||||
|
||||
def urls_from_apps(
|
||||
apps_hook_functions: Iterable[Callable], public_views_allowed: List[str]
|
||||
) -> List[URLPattern]:
|
||||
apps_hook_functions: Iterable[Callable], public_views_allowed: list[str]
|
||||
) -> list[URLPattern]:
|
||||
"""Return urls from apps and add default decorators."""
|
||||
|
||||
url_patterns = []
|
||||
|
||||
@@ -22,7 +22,7 @@ class ItemCounter:
|
||||
DEFAULT_CACHE_TIMEOUT = 24 * 3600
|
||||
|
||||
def __init__(
|
||||
self, name: str, minimum: Optional[int] = None, redis: Optional[Redis] = None
|
||||
self, name: str, minimum: int | None = None, redis: Redis | None = None
|
||||
) -> None:
|
||||
if not name:
|
||||
raise ValueError("Must define a name")
|
||||
@@ -60,6 +60,6 @@ class ItemCounter:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def value(self) -> Optional[int]:
|
||||
def value(self) -> int | None:
|
||||
"""Return current value or None if not yet initialized."""
|
||||
return cache.get(self._cache_key)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PROTOCOL=https://
|
||||
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
||||
DOMAIN=%DOMAIN%
|
||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v4.0.0b2
|
||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v4.2.2
|
||||
|
||||
# Nginx Proxy Manager
|
||||
PROXY_HTTP_PORT=80
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM python:3.11-slim
|
||||
ARG AUTH_VERSION=v4.0.0b2
|
||||
ARG AUTH_VERSION=v4.2.2
|
||||
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
||||
ENV AUTH_USER=allianceauth
|
||||
ENV AUTH_GROUP=allianceauth
|
||||
@@ -9,21 +9,21 @@ ENV AUTH_HOME=/home/allianceauth
|
||||
|
||||
# Setup user and directory permissions
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
RUN groupadd -g 61000 ${AUTH_GROUP}
|
||||
RUN useradd -g 61000 -l -M -s /bin/false -u 61000 ${AUTH_USER}
|
||||
RUN mkdir -p ${STATIC_BASE} \
|
||||
&& chown ${AUTH_USERGROUP} ${STATIC_BASE} \
|
||||
&& mkdir -p ${AUTH_HOME} \
|
||||
&& chown ${AUTH_USERGROUP} ${AUTH_HOME}
|
||||
RUN groupadd -g 61000 ${AUTH_GROUP} && \
|
||||
useradd -g 61000 -l -m -s /bin/false -u 61000 ${AUTH_USER}
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
|
||||
libmariadb-dev gcc git pkg-config
|
||||
RUN mkdir -p ${STATIC_BASE}/myauth/static \
|
||||
&& chown ${AUTH_USERGROUP} ${STATIC_BASE}/myauth/static
|
||||
|
||||
# Install python dependencies
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install wheel gunicorn
|
||||
RUN pip install ${AUTH_PACKAGE}
|
||||
# Install Build Dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get install -y --no-install-recommends libmariadb-dev gcc git pkg-config \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install AA and Dependencies
|
||||
RUN pip install --no-cache-dir ${AUTH_PACKAGE} gunicorn
|
||||
|
||||
# Switch to non-root user
|
||||
USER ${AUTH_USER}
|
||||
@@ -33,7 +33,6 @@ WORKDIR ${AUTH_HOME}
|
||||
RUN allianceauth start myauth
|
||||
COPY /allianceauth/project_template/project_name/settings/local.py ${AUTH_HOME}/myauth/myauth/settings/local.py
|
||||
RUN allianceauth update myauth
|
||||
RUN mkdir -p ${STATIC_BASE}/myauth/static
|
||||
|
||||
RUN echo 'alias auth="python $AUTH_HOME/myauth/manage.py"' >> ~/.bashrc && \
|
||||
source ~/.bashrc
|
||||
|
||||
@@ -9,6 +9,10 @@ from django.conf import settings # noqa
|
||||
|
||||
app = Celery('myauth')
|
||||
|
||||
# Automatically try to establish the connection to the AMQP broker on
|
||||
# Celery startup if it is unavailable.
|
||||
app.conf.broker_connection_retry_on_startup = True
|
||||
|
||||
# Using a string here means the worker don't have to serialize
|
||||
# the configuration object to child processes.
|
||||
app.config_from_object('django.conf:settings')
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
server {
|
||||
listen 80;
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location /static {
|
||||
alias /var/www/myauth/static;
|
||||
autoindex off;
|
||||
}
|
||||
events {}
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
location /robots.txt {
|
||||
alias /var/www/myauth/static/robots.txt;
|
||||
}
|
||||
sendfile on;
|
||||
|
||||
location / {
|
||||
proxy_pass http://allianceauth_gunicorn:8000;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_redirect off;
|
||||
server {
|
||||
listen 80;
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location /static {
|
||||
alias /var/www/myauth/static;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
location /robots.txt {
|
||||
alias /var/www/myauth/static/robots.txt;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://allianceauth_gunicorn:8000;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
ARG AA_DOCKER_TAG
|
||||
FROM $AA_DOCKER_TAG
|
||||
|
||||
RUN cd /home/allianceauth
|
||||
WORKDIR ${AUTH_HOME}
|
||||
|
||||
COPY /conf/requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
RUN --mount=type=cache,target=~/.cache \
|
||||
pip install -r requirements.txt
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user