Compare commits

..

102 Commits

Author SHA1 Message Date
Ariel Rin
5de19c43df Version Bump 4.0.0a4 2023-11-09 00:13:45 +10:00
Ariel Rin
6a0ddc9a83 Merge branch 'docker-superlance' into 'v4.x'
Add superlance/memmon path to the project bootstrap

See merge request allianceauth/allianceauth!1560
2023-11-08 13:39:11 +00:00
Ariel Rin
03be66d11f multilines dont work in our yaml 2023-11-08 23:26:12 +10:00
Ariel Rin
7e312bb95f four args now 2023-11-08 20:25:24 +10:00
Ariel Rin
c92fee78e2 add superlance/memmon path to the bootstrap 2023-11-08 20:01:07 +10:00
Ariel Rin
658a8cd6ce Merge branch 'task-queue-progressbar-background' into 'v4.x'
[FIX] Celery task status bar background

See merge request allianceauth/allianceauth!1559
2023-11-08 03:31:48 +00:00
Peter Pfeufer
c1dc130766 [FIX] Celery task status bar background 2023-11-07 15:21:44 +01:00
Ariel Rin
35f5573b63 Merge branch 'js-fixes' into 'v4.x'
JS Fixes

See merge request allianceauth/allianceauth!1556
2023-11-06 13:49:31 +00:00
Peter Pfeufer
21f0a96422 [CHANGE] Modernize and convert to ES6+ 2023-10-31 22:00:43 +01:00
Peter Pfeufer
9e47d19337 [ADD] Missing semicolons 2023-10-31 21:48:18 +01:00
Peter Pfeufer
2c5972d0ab [ADD] Refresh notification icon script
Similar to what we have in AAv3 for the notification count
2023-10-31 21:47:40 +01:00
Ariel Rin
ee41d62c13 Merge branch 'quickfix-services-control-template' into 'v4.x'
[REMOVE] Deprecated overrides …

See merge request allianceauth/allianceauth!1555
2023-10-31 13:29:48 +00:00
Peter Pfeufer
346b4014a9 [ADD] Missing semicolon
Just for the sake of it …
2023-10-31 14:13:01 +01:00
Peter Pfeufer
9b56a441ed [REMOVE] Deprecated overrides … 2023-10-31 14:10:26 +01:00
Ariel Rin
068bf1ae7a Merge branch 'services-template-improvements' into 'v4.x'
Services template improvements

See merge request allianceauth/allianceauth!1553
2023-10-31 12:42:08 +00:00
Peter Pfeufer
5be686e3ca [FIX] Username check 2023-10-31 13:28:38 +01:00
Ariel Rin
a215b4411c Merge branch 'v4theme' into 'v4.x'
Missing Import on views

See merge request allianceauth/allianceauth!1554
2023-10-31 12:14:15 +00:00
Ariel Rin
e15cfa0fb1 Missing Import on views 2023-10-31 12:14:14 +00:00
Peter Pfeufer
46d51699f4 [CHANGE] Service delete confirm template converted to BS5 2023-10-31 11:59:12 +01:00
Peter Pfeufer
ff30a136d5 [CHANGE] Service credentials template converted to BS5 2023-10-31 11:56:18 +01:00
Peter Pfeufer
6dcf3304d5 Merge remote-tracking branch 'origin/services-template-improvements' into services-template-improvements 2023-10-31 11:49:49 +01:00
Peter Pfeufer
beddeea338 [CHANGE] Discourse service status badge text 2023-10-31 11:49:18 +01:00
Ariel Rin
69723937f7 Merge branch 'update-project-classifier' into 'v4.x'
[CHANGE] Update project classifier for Django

See merge request allianceauth/allianceauth!1551
2023-10-31 10:01:14 +00:00
Ariel Rin
c541f56ee2 Merge branch 'fix-dashboard-timers' into 'v4.x'
[FIX] EveCorporationInfo matching query does not exist

See merge request allianceauth/allianceauth!1552
2023-10-31 10:01:03 +00:00
Peter Pfeufer
7e887e5e34 [ADD] General template include for username line 2023-10-31 10:47:06 +01:00
Peter Pfeufer
072327c79f [CHANGE] Comment active section for nor for Discourse service
The Discourse service doesn't seem to have anything to determine weather it's active or not.
2023-10-31 10:38:01 +01:00
Peter Pfeufer
28af3ff11e [CHANGE] Sort service card information to be a bit more uniform in apperance 2023-10-31 10:27:49 +01:00
Peter Pfeufer
e3b151f2fb [CHANGE] Use BS5 forms 2023-10-31 10:25:04 +01:00
Peter Pfeufer
f87d7dbdf8 [FIX] Normalization of TeamSpeak3 service name 2023-10-31 10:12:02 +01:00
Peter Pfeufer
a04e6ae3d0 [FIX] Normalization of IPSuite4 service name 2023-10-31 10:09:43 +01:00
Peter Pfeufer
15042f5e77 [FIX] Capitalization of Discord service name 2023-10-31 10:07:47 +01:00
Peter Pfeufer
6e25361d5e [FIX] Capitalization of XenForo service name 2023-10-31 10:06:36 +01:00
Peter Pfeufer
9e639a0eeb [FIX] Capitalization of SMF service name 2023-10-31 10:04:55 +01:00
Peter Pfeufer
257fbdef36 [FIX] Capitalization of Jabber service name 2023-10-31 10:03:06 +01:00
Peter Pfeufer
df003c8ec5 [CHANGE] TS³ service template 2023-10-31 09:57:54 +01:00
Peter Pfeufer
ba22685eb8 [CHANGE] Discourse service template 2023-10-31 09:53:09 +01:00
Peter Pfeufer
773288072a [CHANGE] Discord service 2023-10-31 09:48:29 +01:00
Peter Pfeufer
63afb13d25 [CHANGE] Mumble service template 2023-10-31 09:48:07 +01:00
Peter Pfeufer
5dd286bbe7 [CHANGE] Set a default via status template 2023-10-31 09:47:22 +01:00
Peter Pfeufer
8aaa8172ca [CHANGE] Only show username when there is a username 2023-10-31 08:58:30 +01:00
Peter Pfeufer
b68b401146 [FIX] EveCorporationInfo matching query does not exist 2023-10-29 11:55:42 +01:00
Peter Pfeufer
a6526d6f78 [CHANGE] Update project classifier for Django 2023-10-28 00:12:40 +02:00
Ariel Rin
7898594909 Version Bump 4.0.0a3 2023-10-27 23:23:48 +10:00
Ariel Rin
cfd12ee3cc Merge branch 'v4theme' into 'v4.x'
remove rogue span tag

See merge request allianceauth/allianceauth!1546
2023-10-27 13:22:38 +00:00
Ariel Rin
2c9177b19f remove rogue span tag 2023-10-27 13:22:38 +00:00
Ariel Rin
abff26fb6e Version Bump 4.0.0a2 2023-10-27 22:48:06 +10:00
Ariel Rin
e8c3b5225c Merge branch 'v4.x' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-27 22:42:53 +10:00
Ariel Rin
98fd1dcc4c Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-27 22:42:30 +10:00
Ariel Rin
cfe46e4ca5 Merge branch 'fix-same-name-template-tag-modules' into 'v4.x'
[FIX] Give template tag modules unique names

See merge request allianceauth/allianceauth!1548
2023-10-27 12:15:27 +00:00
Peter Pfeufer
4675193416 [REEMOVE] services.templatetags as its no longer needed 2023-10-27 14:08:44 +02:00
Ariel Rin
a84fa1ca69 Merge branch 'aa-css-framework' into 'v4.x'
Alliance Auth CSS Framework

See merge request allianceauth/allianceauth!1544
2023-10-27 11:58:16 +00:00
Ariel Rin
8f6cb0b9bb Merge branch 'fix-notification-colors' into 'v4.x'
[FIX]  Restore notification level colours

See merge request allianceauth/allianceauth!1547
2023-10-27 11:54:40 +00:00
Ariel Rin
1c8634f1c8 Merge branch 'theme-work' into 'v4.x'
Theme test fixes

See merge request allianceauth/allianceauth!1549
2023-10-27 11:33:08 +00:00
Aaron Kable
2a21599d45 BS5 Theme test fixes 2023-10-27 11:33:08 +00:00
Peter Pfeufer
e379c01655 [FIX] Typo in variable name while we're at it 2023-10-27 08:31:55 +02:00
Peter Pfeufer
afa3d2e7cc [FIX] Give template tags modules unique names
This fixes the check error:

?: (templates.E003) 'menu_items' is used for multiple template tag modules: 'allianceauth.menu.templatetags.menu_items', 'allianceauth.services.templatetags.menu_items'
2023-10-27 08:28:09 +02:00
Peter Pfeufer
e5ed33aeec [FIX] [Bootstrap] Uniform default table background
Also removed empty style and class arguments while I was at it
2023-10-26 18:21:23 +02:00
Peter Pfeufer
b12471e775 [CHANGE] Bring back the level colors to the notification list 2023-10-26 15:30:54 +02:00
Peter Pfeufer
5e70dab11f [FIX] Ensure the modifier is only applied to elements with the base class 2023-10-24 12:54:20 +02:00
Peter Pfeufer
f728c786b3 [FIX] Grammar and indentation (it's important!) 2023-10-24 12:27:08 +02:00
Peter Pfeufer
7056912d54 [FIX] Path to image in .md file 2023-10-24 11:58:34 +02:00
Peter Pfeufer
7efed950ca [CHANGE] Seems it needs to be a PNG image … 2023-10-24 11:53:00 +02:00
Peter Pfeufer
886acf2005 [ADD] CSS framework docs to custom documentation index 2023-10-24 11:36:54 +02:00
Peter Pfeufer
b2dec3bff2 [FIX] Typo … 2023-10-24 11:31:15 +02:00
Peter Pfeufer
f0a402e141 [ADD] Documentation 2023-10-24 11:25:52 +02:00
Peter Pfeufer
2e2afd7923 [ADD] Callout boxes
Not quite alerts, but custom and helpful notes for folks. Requires a base and modifier class.
2023-10-24 11:05:15 +02:00
Peter Pfeufer
e9ea09bc56 [CHANGE] Rename auth-base-bs5.css to auth-framework.css 2023-10-24 10:58:45 +02:00
Ariel Rin
186fa1be03 Correct Django version back to !1541 after porting !1513 2023-10-24 12:37:37 +10:00
Ariel Rin
37d1d84fc3 Merge branch 'aa4-bs5-template-fixes' into 'v4.x'
v4 Template fixes

See merge request allianceauth/allianceauth!1541
2023-10-24 02:04:50 +00:00
Peter Pfeufer
ee24706e43 v4 Template fixes 2023-10-24 02:04:49 +00:00
Ariel Rin
07e85727ea Merge branch 'v4theme' into 'v4.x'
Theme handling improvements

See merge request allianceauth/allianceauth!1542
2023-10-21 09:08:28 +00:00
Ariel Rin
4912f0f8f0 Theme handling improvements 2023-10-21 09:08:28 +00:00
Ariel Rin
24376262f0 minor doc structure changes 2023-10-07 21:27:08 +10:00
Ariel Rin
efe0c6963b move doc dependencies to pyproject 2023-10-07 20:45:39 +10:00
Ariel Rin
a4644028ae file path typos 2023-10-07 20:44:16 +10:00
Ariel Rin
3a77b4a429 Add missing docker tags, make docker buildsteps more readable 2023-10-07 19:57:55 +10:00
Ariel Rin
fa375a551c Merge branch 'celry' into 'v4.x'
Rework default celery configuration and documentation

See merge request allianceauth/allianceauth!1482
2023-10-07 09:28:04 +00:00
Aaron Kable
00a93e6fe9 Rework default celery configuration and documentation 2023-10-07 09:28:04 +00:00
Ariel Rin
656e69d4b2 4.4.0a1 - use pep44, refer to !1323 2023-10-07 18:54:02 +10:00
Ariel Rin
3b55d370d0 Merge branch 'v4.x' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-07 18:40:45 +10:00
Ariel Rin
5c126ffe82 Version Bump 4.0.0-alpha.1 2023-10-07 18:40:00 +10:00
Ariel Rin
99be753836 Merge branch 'v4.x-theme' into 'v4.x'
BS5 Theme

See merge request allianceauth/allianceauth!1464
2023-10-07 08:20:22 +00:00
Aaron Kable
2e78aa5f26 BS5 Theme 2023-10-07 08:20:22 +00:00
Ariel Rin
567d97f38a Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-07 17:38:30 +10:00
Ariel Rin
d6821b3fd6 Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-08-14 15:13:54 +10:00
Ariel Rin
90375246fd Merge branch 'analytics' into 'v4.x'
Analytics UA to V4 Conversion

See merge request allianceauth/allianceauth!1500
2023-08-14 03:31:33 +00:00
Ariel Rin
a2f217ace5 Analytics UA to V4 Conversion 2023-08-14 03:31:33 +00:00
Ariel Rin
25cf2fdcd5 Merge branch 'docs' into 'v4.x'
V4.x Docker Refactoring and Docs

See merge request allianceauth/allianceauth!1507
2023-08-14 03:05:44 +00:00
Ariel Rin
4305ae7995 V4.x Docker Refactoring and Docs 2023-08-14 03:05:44 +00:00
Ariel Rin
4aff4006e3 Merge branch 'v4-bumps' into 'v4.x'
V4.x Major versions

See merge request allianceauth/allianceauth!1502
2023-06-09 06:07:39 +00:00
Ariel Rin
55c188f2d0 more docker image bumps 2023-06-03 17:32:30 +10:00
Ariel Rin
f36f824a4b run pre-commit 2023-06-03 17:13:51 +10:00
Ariel Rin
6fbf33bcdd update pre-commit 2023-06-03 17:13:17 +10:00
Ariel Rin
ed3c2c8529 dj4.1 #31395 changed testdata upstream, breaks setting up classes or something 2023-06-03 17:08:06 +10:00
Ariel Rin
05d7fb1f63 repr workaround no longer needed 2023-05-03 14:23:10 +10:00
Ariel Rin
3b19db2564 let pre-commit do some work 2023-05-03 13:18:05 +10:00
Ariel Rin
98aa44c070 dj 4.2 2023-05-03 13:17:55 +10:00
Ariel Rin
8d46ee65af target dj4.2 2023-05-03 12:51:56 +10:00
Ariel Rin
49780b871d python bumps 2023-05-03 12:46:49 +10:00
Ariel Rin
2b7d24fc28 Merge branch 'v4.x' of https://gitlab.com/allianceauth/allianceauth into v4-bumps 2023-05-03 12:24:46 +10:00
Ariel Rin
b8f86a618f py312 rc tests 2023-05-03 12:20:17 +10:00
Ariel Rin
9921011742 docker bumps 2023-05-03 12:19:52 +10:00
255 changed files with 5084 additions and 3602 deletions

View File

@@ -25,7 +25,7 @@ before_script:
pre-commit-check: pre-commit-check:
<<: *only-default <<: *only-default
stage: pre-commit stage: pre-commit
image: python:3.10-bullseye image: python:3.11-bullseye
variables: variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache: cache:
@@ -112,6 +112,19 @@ test-pvpy-core:
path: coverage.xml path: coverage.xml
allow_failure: true allow_failure: true
test-3.12-core:
<<: *only-default
image: python:3.12-rc-bullseye
script:
- tox -e py312-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.8-all: test-3.8-all:
<<: *only-default <<: *only-default
image: python:3.8-bullseye image: python:3.8-bullseye
@@ -174,9 +187,22 @@ test-pvpy-all:
path: coverage.xml path: coverage.xml
allow_failure: true allow_failure: true
test-3.12-all:
<<: *only-default
image: python:3.12-rc-bullseye
script:
- tox -e py312-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
build-test: build-test:
stage: test stage: test
image: python:3.10-bullseye image: python:3.11-bullseye
before_script: before_script:
- python -m pip install --upgrade pip - python -m pip install --upgrade pip
@@ -195,13 +221,13 @@ build-test:
test-docs: test-docs:
<<: *only-default <<: *only-default
image: python:3.10-bullseye image: python:3.11-bullseye
script: script:
- tox -e docs - tox -e docs
deploy_production: deploy_production:
stage: deploy stage: deploy
image: python:3.10-bullseye image: python:3.11-bullseye
before_script: before_script:
- python -m pip install --upgrade pip - python -m pip install --upgrade pip
@@ -217,10 +243,10 @@ deploy_production:
build-image: build-image:
before_script: [] before_script: []
image: docker:20.10.10 image: docker:24.0
stage: docker stage: docker
services: services:
- docker:20.10.10-dind - docker:24.0-dind
script: | script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -) CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
@@ -230,12 +256,10 @@ build-image:
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-) docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker tag $IMAGE_TAG $CURRENT_TAG docker run --privileged --rm tonistiigi/binfmt --install all
docker tag $IMAGE_TAG $MINOR_TAG docker buildx create --use --name new-builder
docker tag $IMAGE_TAG $MAJOR_TAG docker buildx build . --tag $IMAGE_TAG --tag $CURRENT_TAG --tag $MINOR_TAG --tag $MAJOR_TAG --tag $LATEST_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
docker tag $IMAGE_TAG $LATEST_TAG
docker image push --all-tags $CI_REGISTRY_IMAGE/auth
rules: rules:
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
when: delayed when: delayed
@@ -243,17 +267,19 @@ build-image:
build-image-dev: build-image-dev:
before_script: [] before_script: []
image: docker:20.10.10 image: docker:24.0
stage: docker stage: docker
services: services:
- docker:20.10.10-dind - docker:24.0-dind
script: | script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -) CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker push $IMAGE_TAG docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder
docker buildx build . --tag $IMAGE_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
rules: rules:
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""' - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
when: manual when: manual
@@ -262,17 +288,19 @@ build-image-dev:
build-image-mr: build-image-mr:
before_script: [] before_script: []
image: docker:20.10.10 image: docker:24.0
stage: docker stage: docker
services: services:
- docker:20.10.10-dind - docker:24.0-dind
script: | script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -) CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker push $IMAGE_TAG docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder
docker buildx build . --tag $IMAGE_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual when: manual

View File

@@ -66,14 +66,20 @@ repos:
swagger\.json swagger\.json
) )
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [ --target-version=4.0 ]
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.10.1 rev: v3.10.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [ --py38-plus ] args: [ --py38-plus ]
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [--target-version=4.2]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.3.0
hooks:
- id: setup-cfg-fmt
args: [ --include-version-classifiers ]

View File

@@ -23,4 +23,7 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs # Optionally set the version of Python and requirements required to build your docs
python: python:
install: install:
- requirements: docs/requirements.txt - method: pip
path: .
extra_requirements:
- docs

View File

@@ -1,5 +1,5 @@
[main] [main]
host = https://app.transifex.com host = https://www.transifex.com
lang_map = zh-Hans: zh_Hans lang_map = zh-Hans: zh_Hans
[o:alliance-auth:p:alliance-auth:r:django-po] [o:alliance-auth:p:alliance-auth:r:django-po]

View File

@@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
lang_map = zh-Hans:zh_Hans
[alliance-auth.django-po]
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
minimum_perc = 0
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO

View File

@@ -1,10 +0,0 @@
filters:
- filter_type: file
file_format: PO
source_file: allianceauth/locale/en/LC_MESSAGES/django.po
source_language: en
translation_files_expression: allianceauth/locale/<lang>/LC_MESSAGES/django.po
settings:
language_mapping:
zh-Hans: zh_Hans

View File

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

View File

@@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens from .models import AnalyticsIdentifier, AnalyticsTokens
@admin.register(AnalyticsIdentifier) @admin.register(AnalyticsIdentifier)
@@ -13,9 +13,3 @@ class AnalyticsIdentifierAdmin(admin.ModelAdmin):
class AnalyticsTokensAdmin(admin.ModelAdmin): class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ] search_fields = ['name', ]
list_display = ('name', 'type',) list_display = ('name', 'type',)
@admin.register(AnalyticsPath)
class AnalyticsPathAdmin(admin.ModelAdmin):
search_fields = ['ignore_path', ]
list_display = ('ignore_path',)

View File

@@ -4,6 +4,3 @@ from django.apps import AppConfig
class AnalyticsConfig(AppConfig): class AnalyticsConfig(AppConfig):
name = 'allianceauth.analytics' name = 'allianceauth.analytics'
label = 'analytics' label = 'analytics'
def ready(self):
import allianceauth.analytics.signals

View File

@@ -3,11 +3,10 @@
"model": "analytics.AnalyticsTokens", "model": "analytics.AnalyticsTokens",
"pk": 1, "pk": 1,
"fields": { "fields": {
"name": "AA Team Public Google Analytics (Universal)", "name": "AA Team Public Google Analytics (V4)",
"type": "GA-V4", "type": "GA-V4",
"token": "UA-186249766-2", "token": "G-6LYSMYK8DE",
"send_page_views": "False", "secret": "KLlpjLZ-SRGozS5f5wb_kw",
"send_celery_tasks": "False",
"send_stats": "False" "send_stats": "False"
} }
}, },

View File

@@ -1,52 +0,0 @@
from bs4 import BeautifulSoup
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
from .models import AnalyticsTokens, AnalyticsIdentifier
from .tasks import send_ga_tracking_web_view
import re
class AnalyticsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
"""Django Middleware: Process Page Views and creates Analytics Celery Tasks"""
if getattr(settings, "ANALYTICS_DISABLED", False):
return response
analyticstokens = AnalyticsTokens.objects.all()
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
try:
title = BeautifulSoup(
response.content, "html.parser").html.head.title.text
except AttributeError:
title = ''
for token in analyticstokens:
# Check if Page View Sending is Disabled
if token.send_page_views is False:
continue
# Check Exclusions
ignore = False
for ignore_path in token.ignore_paths.values():
ignore_path_regex = re.compile(ignore_path["ignore_path"])
if re.search(ignore_path_regex, request.path) is not None:
ignore = True
if ignore is True:
continue
tracking_id = token.token
locale = request.LANGUAGE_CODE
path = request.path
try:
useragent = request.headers["User-Agent"]
except KeyError:
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
send_ga_tracking_web_view.s(tracking_id=tracking_id,
client_id=client_id,
page=path,
title=title,
locale=locale,
useragent=useragent).\
apply_async(priority=9)
return response

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.6 on 2022-08-30 05:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('analytics', '0006_more_ignore_paths'),
]
operations = [
migrations.AddField(
model_name='analyticstokens',
name='secret',
field=models.CharField(blank=True, max_length=254),
),
]

View File

@@ -0,0 +1,64 @@
# Generated by Django 3.1.4 on 2020-12-30 08:53
from django.db import migrations
from django.core.exceptions import ObjectDoesNotExist
def add_aa_team_token(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
token = Tokens()
try:
ua_token = Tokens.objects.get(token="UA-186249766-2")
original_send_page_views = ua_token.send_page_views
original_send_celery_tasks = ua_token.send_celery_tasks
original_send_stats = ua_token.send_stats
except ObjectDoesNotExist:
original_send_page_views = True
original_send_celery_tasks = True
original_send_stats = True
try:
user_notifications_count = AnalyticsPath.objects.get(ignore_path=r"^\/user_notifications_count\/.*",)
except ObjectDoesNotExist:
user_notifications_count = AnalyticsPath.objects.create(ignore_path=r"^\/user_notifications_count\/.*")
try:
admin = AnalyticsPath.objects.get(ignore_path=r"^\/admin\/.*")
except ObjectDoesNotExist:
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
try:
account_activate = AnalyticsPath.objects.get(ignore_path=r"^\/account\/activate\/.*")
except ObjectDoesNotExist:
account_activate = AnalyticsPath.objects.create(ignore_path=r"^\/account\/activate\/.*")
token.type = 'GA-V4'
token.token = 'G-6LYSMYK8DE'
token.secret = 'KLlpjLZ-SRGozS5f5wb_kw'
token.send_page_views = original_send_page_views
token.send_celery_tasks = original_send_celery_tasks
token.send_stats = original_send_stats
token.name = 'AA Team Public Google Analytics (V4)'
token.save()
token.ignore_paths.add(admin, user_notifications_count, account_activate)
token.save()
def remove_aa_team_token(apps, schema_editor):
# Have to define some code to remove this identifier
# In case of migration rollback?
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.filter(token="G-6LYSMYK8DE").delete()
class Migration(migrations.Migration):
dependencies = [
('analytics', '0007_analyticstokens_secret'),
]
operations = [migrations.RunPython(
add_aa_team_token, remove_aa_team_token)]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.10 on 2023-05-08 05:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('analytics', '0008_add_AA_GA-4_Team_Token '),
]
operations = [
migrations.RemoveField(
model_name='analyticstokens',
name='ignore_paths',
),
migrations.RemoveField(
model_name='analyticstokens',
name='send_celery_tasks',
),
migrations.RemoveField(
model_name='analyticstokens',
name='send_page_views',
),
migrations.DeleteModel(
name='AnalyticsPath',
),
]

View File

@@ -7,22 +7,19 @@ from uuid import uuid4
class AnalyticsIdentifier(models.Model): class AnalyticsIdentifier(models.Model):
identifier = models.UUIDField(default=uuid4, identifier = models.UUIDField(
editable=False) default=uuid4,
editable=False)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.pk and AnalyticsIdentifier.objects.exists(): if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object # Force a single object
raise ValidationError('There is can be only one \ raise ValidationError('There is can be only one \
AnalyticsIdentifier instance') AnalyticsIdentifier instance')
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1 self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
class AnalyticsPath(models.Model):
ignore_path = models.CharField(max_length=254, default="/example/", help_text="Regex Expression, If matched no Analytics Page View is sent")
class AnalyticsTokens(models.Model): class AnalyticsTokens(models.Model):
class Analytics_Type(models.TextChoices): class Analytics_Type(models.TextChoices):
@@ -32,7 +29,5 @@ class AnalyticsTokens(models.Model):
name = models.CharField(max_length=254) name = models.CharField(max_length=254)
type = models.CharField(max_length=254, choices=Analytics_Type.choices) type = models.CharField(max_length=254, choices=Analytics_Type.choices)
token = models.CharField(max_length=254, blank=False) token = models.CharField(max_length=254, blank=False)
send_page_views = models.BooleanField(default=False) secret = models.CharField(max_length=254, blank=True)
send_celery_tasks = models.BooleanField(default=False)
send_stats = models.BooleanField(default=False) send_stats = models.BooleanField(default=False)
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)

View File

@@ -1,55 +0,0 @@
import logging
from celery.signals import task_failure, task_success
from django.conf import settings
from allianceauth.analytics.tasks import analytics_event
logger = logging.getLogger(__name__)
@task_failure.connect
def process_failure_signal(
exception, traceback,
sender, task_id, signal,
args, kwargs, einfo, **kw):
logger.debug("Celery task_failure signal %s" % sender.__class__.__name__)
if getattr(settings, "ANALYTICS_DISABLED", False):
return
category = sender.__module__
if 'allianceauth.analytics' not in category:
if category.endswith(".tasks"):
category = category[:-6]
action = sender.__name__
label = f"{exception.__class__.__name__}"
analytics_event(category=category,
action=action,
label=label)
@task_success.connect
def celery_success_signal(sender, result=None, **kw):
logger.debug("Celery task_success signal %s" % sender.__class__.__name__)
if getattr(settings, "ANALYTICS_DISABLED", False):
return
category = sender.__module__
if 'allianceauth.analytics' not in category:
if category.endswith(".tasks"):
category = category[:-6]
action = sender.__name__
label = "Success"
value = 0
if isinstance(result, int):
value = result
analytics_event(category=category,
action=action,
label=label,
value=value)

View File

@@ -3,7 +3,6 @@ import logging
from django.conf import settings from django.conf import settings
from django.apps import apps from django.apps import apps
from celery import shared_task from celery import shared_task
from allianceauth import __version__
from .models import AnalyticsTokens, AnalyticsIdentifier from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import ( from .utils import (
install_stat_addons, install_stat_addons,
@@ -12,14 +11,14 @@ from .utils import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
BASE_URL = "https://www.google-analytics.com/" BASE_URL = "https://www.google-analytics.com"
DEBUG_URL = f"{BASE_URL}debug/collect" DEBUG_URL = f"{BASE_URL}/debug/mp/collect"
COLLECTION_URL = f"{BASE_URL}collect" COLLECTION_URL = f"{BASE_URL}/mp/collect"
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG: if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
# Force sending of analytics data during in a debug/test environemt # Force sending of analytics data during in a debug/test environment
# Usefull for developers working on this feature. # Useful for developers working on this feature.
logger.warning( logger.warning(
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! " "You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
"This debug instance will send analytics data!") "This debug instance will send analytics data!")
@@ -31,40 +30,38 @@ if settings.DEBUG is True:
ANALYTICS_URL = DEBUG_URL ANALYTICS_URL = DEBUG_URL
def analytics_event(category: str, def analytics_event(namespace: str,
action: str, task: str,
label: str, label: str = "",
value: int = 0, result: str = "",
value: int = 1,
event_type: str = 'Celery'): event_type: str = 'Celery'):
""" """
Send a Google Analytics Event for each token stored Send a Google Analytics Event for each token stored
Includes check for if its enabled/disabled Includes check for if its enabled/disabled
Args: Args:
`category` (str): Celery Namespace `namespace` (str): Celery Namespace
`action` (str): Task Name `task` (str): Task Name
`label` (str): Optional, Task Success/Exception `label` (str): Optional, additional task label
`value` (int): Optional, If bulk, Query size, can be a binary True/False `result` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a Boolean
`event_type` (str): Optional, Celery or Stats only, Default to Celery `event_type` (str): Optional, Celery or Stats only, Default to Celery
""" """
analyticstokens = AnalyticsTokens.objects.all() for token in AnalyticsTokens.objects.filter(type='GA-V4'):
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex if event_type == 'Stats':
for token in analyticstokens:
if event_type == 'Celery':
allowed = token.send_celery_tasks
elif event_type == 'Stats':
allowed = token.send_stats allowed = token.send_stats
else: else:
allowed = False allowed = False
if allowed is True: if allowed is True:
tracking_id = token.token
send_ga_tracking_celery_event.s( send_ga_tracking_celery_event.s(
tracking_id=tracking_id, measurement_id=token.token,
client_id=client_id, secret=token.secret,
category=category, namespace=namespace,
action=action, task=task,
label=label, label=label,
result=result,
value=value).apply_async(priority=9) value=value).apply_async(priority=9)
@@ -72,136 +69,104 @@ def analytics_event(category: str,
def analytics_daily_stats(): def analytics_daily_stats():
"""Celery Task: Do not call directly """Celery Task: Do not call directly
Gathers a series of daily statistics and sends analytics events containing them Gathers a series of daily statistics
Sends analytics events containing them
""" """
users = install_stat_users() users = install_stat_users()
tokens = install_stat_tokens() tokens = install_stat_tokens()
addons = install_stat_addons() addons = install_stat_addons()
logger.debug("Running Daily Analytics Upload") logger.debug("Running Daily Analytics Upload")
analytics_event(category='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
action='send_install_stats', task='send_install_stats',
label='existence', label='existence',
value=1, value=1,
event_type='Stats') event_type='Stats')
analytics_event(category='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
action='send_install_stats', task='send_install_stats',
label='users', label='users',
value=users, value=users,
event_type='Stats') event_type='Stats')
analytics_event(category='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
action='send_install_stats', task='send_install_stats',
label='tokens', label='tokens',
value=tokens, value=tokens,
event_type='Stats') event_type='Stats')
analytics_event(category='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
action='send_install_stats', task='send_install_stats',
label='addons', label='addons',
value=addons, value=addons,
event_type='Stats') event_type='Stats')
for appconfig in apps.get_app_configs(): for appconfig in apps.get_app_configs():
analytics_event(category='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
action='send_extension_stats', task='send_extension_stats',
label=appconfig.label, label=appconfig.label,
value=1, value=1,
event_type='Stats') event_type='Stats')
@shared_task()
def send_ga_tracking_web_view(
tracking_id: str,
client_id: str,
page: str,
title: str,
locale: str,
useragent: str) -> requests.Response:
"""Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware
Parameters
----------
`tracking_id` (str): Unique Server Identifier
`client_id` (str): GA Token
`page` (str): Page Path
`title` (str): Page Title
`locale` (str): Browser Language
`useragent` (str): Browser UserAgent
Returns
-------
requests.Reponse Object
"""
headers = {"User-Agent": useragent}
payload = {
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'pageview',
'dp': page,
'dt': title,
'ul': locale,
'ua': useragent,
'aip': 1,
'an': "allianceauth",
'av': __version__
}
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Page View HTTP{response.status_code}")
return response
@shared_task() @shared_task()
def send_ga_tracking_celery_event( def send_ga_tracking_celery_event(
tracking_id: str, measurement_id: str,
client_id: str, secret: str,
category: str, namespace: str,
action: str, task: str,
label: str, label: str = "",
value: int) -> requests.Response: result: str = "",
value: int = 1):
"""Celery Task: Do not call directly """Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware Sends an events to GA
Parameters Parameters
---------- ----------
`tracking_id` (str): Unique Server Identifier `measurement_id` (str): GA Token
`client_id` (str): GA Token `secret` (str): GA Authentication Secret
`category` (str): Celery Namespace `namespace` (str): Celery Namespace
`action` (str): Task Name `task` (str): Task Name
`label` (str): Optional, Task Success/Exception `label` (str): Optional, additional task label
`result` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a binary True/False `value` (int): Optional, If bulk, Query size, can be a binary True/False
Returns
-------
requests.Reponse Object
""" """
headers = { parameters = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"} 'measurement_id': measurement_id,
'api_secret': secret
}
payload = { payload = {
'v': '1', 'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
'tid': tracking_id, "user_properties": {
'cid': client_id, "allianceauth_version": {
't': 'event', "value": "allianceauth_version"
'ec': category,
'ea': action,
'el': label,
'ev': value,
'aip': 1,
'an': "allianceauth",
'av': __version__
} }
},
response = requests.post( 'non_personalized_ads': True,
ANALYTICS_URL, data=payload, "events": [{
timeout=5, headers=headers) "name": "celery_event",
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}") "params": {
return response "namespace": namespace,
"task": task,
'result': result,
'label': label,
"value": value
}
}]
}
try:
response = requests.post(
ANALYTICS_URL,
params=parameters,
json=payload,
timeout=10)
response.raise_for_status()
logger.debug(
f"Analytics Celery/Stats Event HTTP{response.status_code}")
return response.status_code
except requests.exceptions.HTTPError as e:
logger.debug(e)
return response.status_code
except requests.exceptions.ConnectionError as e:
logger.debug(e)
return "Failed"

View File

@@ -1,109 +0,0 @@
from unittest.mock import patch
from urllib.parse import parse_qs
import requests_mock
from django.test import override_settings
from allianceauth.analytics.tasks import ANALYTICS_URL
from allianceauth.eveonline.tasks import update_character
from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.utils.testing import NoSocketsTestCase
@override_settings(CELERY_ALWAYS_EAGER=True)
@requests_mock.mock()
class TestAnalyticsForViews(NoSocketsTestCase):
@override_settings(ANALYTICS_DISABLED=False)
def test_should_run_analytics(self, requests_mocker):
# given
requests_mocker.post(ANALYTICS_URL)
user = AuthUtils.create_user("Bruce Wayne")
self.client.force_login(user)
# when
response = self.client.get("/dashboard/")
# then
self.assertEqual(response.status_code, 200)
self.assertTrue(requests_mocker.called)
@override_settings(ANALYTICS_DISABLED=True)
def test_should_not_run_analytics(self, requests_mocker):
# given
requests_mocker.post(ANALYTICS_URL)
user = AuthUtils.create_user("Bruce Wayne")
self.client.force_login(user)
# when
response = self.client.get("/dashboard/")
# then
self.assertEqual(response.status_code, 200)
self.assertFalse(requests_mocker.called)
@override_settings(CELERY_ALWAYS_EAGER=True)
@requests_mock.mock()
class TestAnalyticsForTasks(NoSocketsTestCase):
@override_settings(ANALYTICS_DISABLED=False)
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
def test_should_run_analytics_for_successful_task(
self, requests_mocker, mock_update_character
):
# given
requests_mocker.post(ANALYTICS_URL)
user = AuthUtils.create_user("Bruce Wayne")
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
# when
update_character.delay(character.character_id)
# then
self.assertTrue(mock_update_character.called)
self.assertTrue(requests_mocker.called)
payload = parse_qs(requests_mocker.last_request.text)
self.assertListEqual(payload["el"], ["Success"])
@override_settings(ANALYTICS_DISABLED=True)
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
def test_should_not_run_analytics_for_successful_task(
self, requests_mocker, mock_update_character
):
# given
requests_mocker.post(ANALYTICS_URL)
user = AuthUtils.create_user("Bruce Wayne")
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
# when
update_character.delay(character.character_id)
# then
self.assertTrue(mock_update_character.called)
self.assertFalse(requests_mocker.called)
@override_settings(ANALYTICS_DISABLED=False)
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
def test_should_run_analytics_for_failed_task(
self, requests_mocker, mock_update_character
):
# given
requests_mocker.post(ANALYTICS_URL)
mock_update_character.side_effect = RuntimeError
user = AuthUtils.create_user("Bruce Wayne")
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
# when
update_character.delay(character.character_id)
# then
self.assertTrue(mock_update_character.called)
self.assertTrue(requests_mocker.called)
payload = parse_qs(requests_mocker.last_request.text)
self.assertNotEqual(payload["el"], ["Success"])
@override_settings(ANALYTICS_DISABLED=True)
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
def test_should_not_run_analytics_for_failed_task(
self, requests_mocker, mock_update_character
):
# given
requests_mocker.post(ANALYTICS_URL)
mock_update_character.side_effect = RuntimeError
user = AuthUtils.create_user("Bruce Wayne")
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
# when
update_character.delay(character.character_id)
# then
self.assertTrue(mock_update_character.called)
self.assertFalse(requests_mocker.called)

View File

@@ -1,24 +0,0 @@
from allianceauth.analytics.middleware import AnalyticsMiddleware
from unittest.mock import Mock
from django.http import HttpResponse
from django.test.testcases import TestCase
class TestAnalyticsMiddleware(TestCase):
def setUp(self):
self.middleware = AnalyticsMiddleware(HttpResponse)
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/testURL/'
self.request.session = {}
self.request.LANGUAGE_CODE = 'en'
self.response = Mock()
self.response.content = 'hello world'
def test_middleware(self):
response = self.middleware.process_response(self.request, self.response)
self.assertEqual(self.response, response)

View File

@@ -23,4 +23,5 @@ class TestAnalyticsIdentifier(TestCase):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
AnalyticsIdentifier.objects.create(identifier=uuid_2) AnalyticsIdentifier.objects.create(identifier=uuid_2)
self.assertEqual(AnalyticsIdentifier.objects.count(), 1) self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
self.assertEqual(AnalyticsIdentifier.objects.get(pk=1).identifier, UUID(uuid_1)) self.assertEqual(AnalyticsIdentifier.objects.get(
pk=1).identifier, UUID(uuid_1))

View File

@@ -4,12 +4,11 @@ from django.test.utils import override_settings
from allianceauth.analytics.tasks import ( from allianceauth.analytics.tasks import (
analytics_event, analytics_event,
send_ga_tracking_celery_event, send_ga_tracking_celery_event)
send_ga_tracking_web_view)
from allianceauth.utils.testing import NoSocketsTestCase from allianceauth.utils.testing import NoSocketsTestCase
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/collect' GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/mp/collect'
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True) @override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
@@ -18,195 +17,53 @@ class TestAnalyticsTasks(NoSocketsTestCase):
def test_analytics_event(self, requests_mocker): def test_analytics_event(self, requests_mocker):
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL) requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
analytics_event( analytics_event(
category='allianceauth.analytics', namespace='allianceauth.analytics',
action='send_tests', task='send_tests',
label='test', label='test',
value=1, value=1,
event_type='Stats') result="Success",
event_type='Stats')
def test_send_ga_tracking_web_view_sent(self, requests_mocker):
"""This test sends if the event SENDS to google.
Not if it was successful.
"""
# given
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
# when
response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent)
# then
self.assertEqual(response.status_code, 200)
def test_send_ga_tracking_web_view_success(self, requests_mocker):
# given
requests_mocker.register_uri(
'POST',
GOOGLE_ANALYTICS_DEBUG_URL,
json={"hitParsingResult":[{'valid': True}]}
)
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
# when
json_response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent).json()
# then
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
def test_send_ga_tracking_web_view_invalid_token(self, requests_mocker):
# given
requests_mocker.register_uri(
'POST',
GOOGLE_ANALYTICS_DEBUG_URL,
json={
"hitParsingResult":[
{
'valid': False,
'parserMessage': [
{
'messageType': 'INFO',
'description': 'IP Address from this hit was anonymized to 1.132.110.0.',
'messageCode': 'VALUE_MODIFIED'
},
{
'messageType': 'ERROR',
'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.",
'messageCode': 'VALUE_INVALID', 'parameter': 'tid'
}
],
'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'
}
]
}
)
tracking_id = 'UA-IntentionallyBadTrackingID-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
# when
json_response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent).json()
# then
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
self.assertEqual(
json_response["hitParsingResult"][0]["parserMessage"][1]["description"],
"The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details."
)
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
def test_send_ga_tracking_celery_event_sent(self, requests_mocker): def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
# given # given
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL) requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
tracking_id = 'UA-186249766-2' token = 'G-6LYSMYK8DE'
client_id = 'ab33e241fbf042b6aa77c7655a768af7' secret = 'KLlpjLZ-SRGozS5f5wb_kw',
category = 'test' category = 'test'
action = 'test' action = 'test'
label = 'test' label = 'test'
value = '1' value = '1'
# when # when
response = send_ga_tracking_celery_event( task = send_ga_tracking_celery_event(
tracking_id, token,
client_id, secret,
category, category,
action, action,
label, label,
value) value)
# then # then
self.assertEqual(response.status_code, 200) self.assertEqual(task, 200)
def test_send_ga_tracking_celery_event_success(self, requests_mocker): def test_send_ga_tracking_celery_event_success(self, requests_mocker):
# given # given
requests_mocker.register_uri( requests_mocker.register_uri(
'POST', 'POST',
GOOGLE_ANALYTICS_DEBUG_URL, GOOGLE_ANALYTICS_DEBUG_URL,
json={"hitParsingResult":[{'valid': True}]} json={"validationMessages": []}
) )
tracking_id = 'UA-186249766-2' token = 'G-6LYSMYK8DE'
client_id = 'ab33e241fbf042b6aa77c7655a768af7' secret = 'KLlpjLZ-SRGozS5f5wb_kw',
category = 'test' category = 'test'
action = 'test' action = 'test'
label = 'test' label = 'test'
value = '1' value = '1'
# when # when
json_response = send_ga_tracking_celery_event( task = send_ga_tracking_celery_event(
tracking_id, token,
client_id, secret,
category, category,
action, action,
label, label,
value).json() value)
# then # then
self.assertTrue(json_response["hitParsingResult"][0]["valid"]) self.assertTrue(task, 200)
def test_send_ga_tracking_celery_event_invalid_token(self, requests_mocker):
# given
requests_mocker.register_uri(
'POST',
GOOGLE_ANALYTICS_DEBUG_URL,
json={
"hitParsingResult":[
{
'valid': False,
'parserMessage': [
{
'messageType': 'INFO',
'description': 'IP Address from this hit was anonymized to 1.132.110.0.',
'messageCode': 'VALUE_MODIFIED'
},
{
'messageType': 'ERROR',
'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.",
'messageCode': 'VALUE_INVALID', 'parameter': 'tid'
}
],
'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'
}
]
}
)
tracking_id = 'UA-IntentionallyBadTrackingID-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
# when
json_response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value).json()
# then
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
self.assertEqual(
json_response["hitParsingResult"][0]["parserMessage"][1]["description"],
"The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details."
)

View File

@@ -18,17 +18,17 @@ def create_testdata():
'abc@example.com', 'abc@example.com',
'password' 'password'
) )
#Token.objects.all().delete() # Token.objects.all().delete()
#Token.objects.create( # Token.objects.create(
# character_id=101, # character_id=101,
# character_name='character1', # character_name='character1',
# access_token='my_access_token' # access_token='my_access_token'
#) # )
#Token.objects.create( # Token.objects.create(
# character_id=102, # character_id=102,
# character_name='character2', # character_name='character2',
# access_token='my_access_token' # access_token='my_access_token'
#) # )
class TestAnalyticsUtils(TestCase): class TestAnalyticsUtils(TestCase):
@@ -40,7 +40,7 @@ class TestAnalyticsUtils(TestCase):
users = install_stat_users() users = install_stat_users()
self.assertEqual(users, expected) self.assertEqual(users, expected)
#def test_install_stat_tokens(self): # def test_install_stat_tokens(self):
# create_testdata() # create_testdata()
# expected = 2 # expected = 2
# #

View File

@@ -1,5 +1,30 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.core.checks import Warning, Error, register
class AllianceAuthConfig(AppConfig): class AllianceAuthConfig(AppConfig):
name = 'allianceauth' 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

View File

@@ -288,7 +288,7 @@ class UserAdmin(BaseUserAdmin):
Behavior of groups and characters columns can be configured via settings Behavior of groups and characters columns can be configured via settings
""" """
inlines = BaseUserAdmin.inlines + [UserProfileInline] inlines = [UserProfileInline]
ordering = ('username', ) ordering = ('username', )
list_select_related = ('profile__state', 'profile__main_character') list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True show_full_result_count = True

View File

@@ -0,0 +1,45 @@
from allianceauth.hooks import DashboardItemHook
from allianceauth import hooks
from .views import dashboard_characters, dashboard_groups, dashboard_admin
class UserCharactersHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_characters,
5
)
class UserGroupsHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_groups,
5
)
class AdminHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_admin,
0
)
@hooks.register('dashboard_hook')
def register_character_hook():
return UserCharactersHook()
@hooks.register('dashboard_hook')
def register_groups_hook():
return UserGroupsHook()
@hooks.register('dashboard_hook')
def register_admin_hook():
return AdminHook()

View File

@@ -1,3 +1,6 @@
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 functools import wraps
from typing import Callable, Iterable, Optional from typing import Callable, Iterable, Optional

View File

@@ -31,6 +31,7 @@ class UserSettingsMiddleware(MiddlewareMixin):
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
# AA v3 NIGHT_MODE
# Set our Night mode flag from the DB # Set our Night mode flag from the DB
# Null = hasnt been set by the user ever, dont act. # Null = hasnt been set by the user ever, dont act.
# #
@@ -42,4 +43,13 @@ class UserSettingsMiddleware(MiddlewareMixin):
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
# AA v4 Themes
# Null = has not been set by the user ever, dont act
# DEFAULT_THEME or DEFAULT_THEME_DARK will be used in get_theme()
try:
if request.user.profile.theme is not None:
request.session["THEME"] = request.user.profile.theme
except Exception as e:
logger.exception(e)
return response return response

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.10 on 2023-10-07 07:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0021_alter_userprofile_language'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='theme',
field=models.CharField(blank=True, help_text='Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps', max_length=200, null=True, verbose_name='Theme'),
),
]

View File

@@ -101,6 +101,13 @@ class UserProfile(models.Model):
_("Night Mode"), _("Night Mode"),
blank=True, blank=True,
null=True) null=True)
theme = models.CharField(
_("Theme"),
max_length=200,
blank=True,
null=True,
help_text="Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps"
)
def assign_state(self, state=None, commit=True): def assign_state(self, state=None, commit=True):
if not state: if not state:

View File

@@ -0,0 +1,46 @@
{% load i18n %}
<div class="col-12 col-xl-8 align-self-stretch p-2 ps-0">
<div class="card">
<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">
<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' %}">
<span class="d-md-inline m-2">{% translate 'Add Character' %}</span>
</a>
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Change Main' %}">
<span class="d-md-inline m-2">{% translate 'Change Main' %}</span>
</a>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% translate "Name" %}</th>
<th class="text-center">{% translate "Corp" %}</th>
<th class="text-center">{% translate "Alliance" %}</th>
</tr>
</thead>
<tbody>
{% for char in characters %}
<tr>
<td class="text-center">
<img class="ra-avatar rounded-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name|default_if_none:"" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
{% load i18n %}
<div class="col-12 col-xl-4 align-self-stretch py-2 ps-2">
<div class="card h-100">
<div class="card-body">
<h4 class="card-title text-center">{% translate "Membership" %}</h4>
<div class="card-body">
<div style="height: 300px; overflow-y:auto;">
<h6 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h6>
<table class="table">
{% for group in groups %}
<tr>
<td class="text-center">{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,190 +1,15 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %} {% block page_title %}{% translate "Dashboard" %}{% endblock %}
{% block header_nav_brand %}
{% translate "Dashboard" %}
{% endblock %}
{% block content %} {% block content %}
<h1 class="page-header text-center">{% translate "Dashboard" %}</h1> <div class="d-flex justify-content-around align-self-center flex-wrap">
{% if user.is_staff %} {% for dash in views %}
{% include 'allianceauth/admin-status/include.html' %} {{ dash | safe }}
{% endif %} {% endfor %}
<div class="col-sm-12">
<div class="row vertical-flexbox-row2">
<div class="col-sm-6 text-center">
<div class="panel panel-primary" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">
{% blocktranslate with state=request.user.profile.state %}
Main Character (State: {{ state }})
{% endblocktranslate %}
</h3>
</div>
<div class="panel-body">
{% if request.user.profile.main_character %}
{% with request.user.profile.main_character as main %}
<div class="hidden-xs">
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar" src="{{ main.portrait_url_128 }}" alt="{{ main.character_name }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.character_name }}</td>
</tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar" src="{{ main.corporation_logo_url_128 }}" alt="{{ main.corporation_name }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.corporation_name }}</td>
</tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
{% if main.alliance_id %}
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar" src="{{ main.alliance_logo_url_128 }}" alt="{{ main.alliance_name }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.alliance_name }}</td>
<tr>
</table>
{% elif main.faction_id %}
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar" src="{{ main.faction_logo_url_128 }}" alt="{{ main.faction_name }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.faction_name }}</td>
<tr>
</table>
{% endif %}
</div>
</div>
<div class="table visible-xs-block">
<p>
<img class="ra-avatar" src="{{ main.portrait_url_64 }}" alt="{{ main.corporation_name }}">
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}" alt="{{ main.corporation_name }}">
{% if main.alliance_id %}
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}" alt="{{ main.alliance_name }}">
{% endif %}
{% if main.faction_id %}
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}" alt="{{ main.faction_name }}">
{% endif %}
</p>
<p>
<strong>{{ main.character_name }}</strong><br>
{{ main.corporation_name }}<br>
{% if main.alliance_id %}
{{ main.alliance_name }}<br>
{% endif %}
{% if main.faction_id %}
{{ main.faction_name }}
{% endif %}
</p>
</div>
{% endwith %}
{% else %}
<div class="alert alert-danger" role="alert">
{% translate "No main character set." %}
</div>
{% endif %}
<div class="clearfix"></div>
<div class="row">
<div class="col-sm-6">
<p>
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
title="Add Character">{% translate 'Add Character' %}</a>
</p>
</div>
<div class="col-sm-6">
<p>
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
title="Change Main Character">{% translate "Change Main" %}</a>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 text-center">
<div class="panel panel-success" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">{% translate "Group Memberships" %}</h3>
</div>
<div class="panel-body">
<div style="height: 240px;overflow-y:auto;">
<table class="table table-aa">
{% for group in groups %}
<tr>
<td>{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title text-center" style="text-align: center">
{% translate 'Characters' %}
</h3>
</div>
<div class="panel-body">
<table class="table table-aa hidden-xs">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% translate 'Name' %}</th>
<th class="text-center">{% translate 'Corp' %}</th>
<th class="text-center">{% translate 'Alliance' %}</th>
</tr>
</thead>
<tbody>
{% for char in characters %}
<tr>
<td class="text-center">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name|default:"" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table class="table table-aa visible-xs-block" style="width: 100%">
<tbody>
{% for char in characters %}
<tr>
<td class="text-center" style="vertical-align: middle">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
</td>
<td class="text-center" style="vertical-align: middle; width: 100%">
<strong>{{ char.character_name }}</strong><br>
{{ char.corporation_name }}<br>
{{ char.alliance_name|default:"" }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,16 +1,16 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %} {% block page_title %}{% translate "Dashboard" %}{% endblock page_title %}
{% block content %} {% block content %}
<h1 class="page-header text-center">{% translate "Token Management" %}</h1> <h1 class="page-header text-center">{% translate "Token Management" %}</h1>
<div class="col-sm-12"> <div>
<table class="table table-aa" id="table_tokens" style="width:100%"> <table class="table table-aa" id="table_tokens" style="width: 100%;">
<thead> <thead>
<tr> <tr>
<th>{% translate "Scopes" %}</th> <th>{% translate "Scopes" %}</th>
<th class="text-right">{% translate "Actions" %}</th> <th class="text-end">{% translate "Actions" %}</th>
<th>{% translate "Character" %}</th> <th>{% translate "Character" %}</th>
</tr> </tr>
@@ -18,24 +18,27 @@
<tbody> <tbody>
{% for t in tokens %} {% for t in tokens %}
<tr> <tr>
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td> <td style="white-space:initial;">{% for s in t.scopes.all %}<span class="badge bg-secondary">{{ s.name }}</span>{% endfor %}</td>
<td nowrap class="text-right"><a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a> <a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a></td> <td nowrap class="text-end">
<td>{{t.character_name}}</td> <a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a>
<a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a>
</td>
<td>{{ t.character_name }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %} {% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %}
</div> </div>
{% endblock %} {% endblock content %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include "bundles/datatables-js-bs5.html" %}
{% endblock %} {% endblock extra_javascript %}
{% block extra_css %} {% block extra_css %}
{% include 'bundles/datatables-css.html' %} {% include "bundles/datatables-css-bs5.html" %}
{% endblock %} {% endblock extra_css %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function(){ $(document).ready(function(){
@@ -59,4 +62,4 @@
"stateSave": true, "stateSave": true,
}); });
}); });
{% endblock %} {% endblock extra_script %}

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
<!-- TODO Bundle all the site specific stuff up into its own template for easy overide -->
<meta property="og:title" content="{{ SITE_NAME }}"> <meta property="og:title" content="{{ SITE_NAME }}">
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}"> <meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access."> <meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
@@ -30,7 +31,7 @@
} }
.panel-transparent { .panel-transparent {
background: rgba(48, 48, 48, 0.7); background: rgba(48 48 48 / 0.7);
color: #ffffff; color: #ffffff;
padding-bottom: 21px; padding-bottom: 21px;
} }

View File

@@ -1,4 +1,5 @@
import logging import logging
from allianceauth.hooks import get_hooks
from django_registration.backends.activation.views import ( from django_registration.backends.activation.views import (
REGISTRATION_SALT, ActivationView as BaseActivationView, REGISTRATION_SALT, ActivationView as BaseActivationView,
@@ -42,23 +43,51 @@ def index(request):
return redirect('authentication:dashboard') return redirect('authentication:dashboard')
@login_required def dashboard_groups(request):
def dashboard(request):
groups = request.user.groups.all() groups = request.user.groups.all()
if _has_auto_groups: if _has_auto_groups:
groups = groups\ groups = groups\
.filter(managedalliancegroup__isnull=True)\ .filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True) .filter(managedcorpgroup__isnull=True)
groups = groups.order_by('name') groups = groups.order_by('name')
context = {
'groups': groups,
}
return render_to_string('authentication/dashboard.groups.html', context=context, request=request)
def dashboard_characters(request):
characters = EveCharacter.objects\ characters = EveCharacter.objects\
.filter(character_ownership__user=request.user)\ .filter(character_ownership__user=request.user)\
.select_related()\ .select_related()\
.order_by('character_name') .order_by('character_name')
context = { context = {
'groups': groups,
'characters': characters 'characters': characters
} }
return render_to_string('authentication/dashboard.characters.html', context=context, request=request)
def dashboard_admin(request):
if request.user.is_superuser:
return render_to_string('allianceauth/admin-status/include.html', request=request)
else:
return ""
@login_required
def dashboard(request):
_dash_items = list()
hooks = get_hooks('dashboard_hook')
items = [fn() for fn in hooks]
items.sort(key=lambda i: i.order)
for item in items:
_dash_items.append(item.render(request))
context = {
'views': _dash_items,
}
return render(request, 'authentication/dashboard.html', context) return render(request, 'authentication/dashboard.html', context)

View File

@@ -12,13 +12,14 @@ class StartProject(BaseStartProject):
parser.add_argument('--python', help='The path to the python executable.') parser.add_argument('--python', help='The path to the python executable.')
parser.add_argument('--celery', help='The path to the celery executable.') parser.add_argument('--celery', help='The path to the celery executable.')
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.') parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
parser.add_argument('--memmon', help='The path to the memmon executable.')
def create_project(parser, options, args): def create_project(parser, options, args):
# Validate args # Validate args
if len(args) < 2: if len(args) < 2:
parser.error("Please specify a name for your Alliance Auth installation.") parser.error("Please specify a name for your Alliance Auth installation.")
elif len(args) > 3: elif len(args) > 4:
parser.error("Too many arguments.") parser.error("Too many arguments.")
# First find the path to Alliance Auth # First find the path to Alliance Auth
@@ -32,6 +33,7 @@ def create_project(parser, options, args):
'python': shutil.which('python'), 'python': shutil.which('python'),
'gunicorn': shutil.which('gunicorn'), 'gunicorn': shutil.which('gunicorn'),
'celery': shutil.which('celery'), 'celery': shutil.which('celery'),
'memmon': shutil.which('memmon'),
'extensions': ['py', 'conf', 'json'], 'extensions': ['py', 'conf', 'json'],
} }

View File

@@ -1,4 +1,5 @@
from allianceauth.services.hooks import MenuItemHook, UrlHook from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import UrlHook
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth import hooks from allianceauth import hooks
from allianceauth.corputils import urls from allianceauth.corputils import urls

View File

@@ -1,37 +1,40 @@
{% extends 'allianceauth/base.html' %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Corporation Member Data" %}{% endblock %} {% block page_title %}
{% translate "Corporation Member Data" %}
{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center">{% translate "Corporation Member Data" %}</h1> <h1 class="page-header text-center">{% translate "Corporation Member Data" %}</h1>
<div class="col-lg-10 col-lg-offset-1 container"> <nav class="navbar navbar-default">
<nav class="navbar navbar-default"> <div class="container-fluid">
<div class="container-fluid"> <ul class="nav navbar-nav">
<ul class="nav navbar-nav"> <li class="dropdown">
<li class="dropdown"> <a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a>
<a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a> <ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"> {% for corpstat in available %}
{% for corpstat in available %} <li>
<li> <a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a> </li>
</li> {% endfor %}
{% endfor %} </ul>
</ul> </li>
{% if perms.corputils.add_corpstats %}
<li>
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
</li> </li>
{% if perms.corputils.add_corpstats %} {% endif %}
<li> </ul>
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
</li> <form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
{% endif %} <div class="form-group">
</ul> <input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate 'Search all corporations...' %}{% endif %}">
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET"> </div>
<div class="form-group"> </form>
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate "Search all corporations..." %}{% endif %}"> </div>
</div> </nav>
</form>
</div> {% block member_data %}
</nav> {% endblock member_data %}
{% block member_data %}{% endblock %}
</div>
</div> </div>
{% endblock %} {% endblock content %}

View File

@@ -3,177 +3,175 @@
{% load humanize %} {% load humanize %}
{% block member_data %} {% block member_data %}
{% if corpstats %} {% if corpstats %}
<div class="row"> <div>
<div class="col-lg-12 text-center"> <table class="table">
<table class="table"> <tr>
<tr> <td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"> <img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}"> </td>
</td> {% if corpstats.corp.alliance %}
{% if corpstats.corp.alliance %} <td class="text-center col-lg-6">
<td class="text-center col-lg-6"> <img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}"> </td>
</td> {% endif %}
{% endif %} </tr>
</tr> <tr>
<tr> <td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td> {% if corpstats.corp.alliance %}
{% if corpstats.corp.alliance %} <td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td> {% endif %}
{% endif %} </tr>
</tr> </table>
</table>
</div>
</div> </div>
<div class="row">
<div class="col-lg-12"> <div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<ul class="nav nav-pills pull-left"> <ul class="nav nav-pills pull-left">
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li> <li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li>
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li> <li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li>
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li> <li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
</ul> </ul>
<div class="pull-right hidden-xs"> <div class="pull-right hidden-xs">
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}&nbsp; {% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}&nbsp;
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now"> <a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
<span class="glyphicon glyphicon-refresh"></span> <span class="glyphicon glyphicon-refresh"></span>
</a> </a>
</div>
<div class="clearfix"></div>
</div> </div>
<div class="panel-body"> <div class="clearfix"></div>
<div class="tab-content"> </div>
<div class="tab-pane fade in active" id="mains">
{% if mains %} <div class="panel-body">
<div class="table-responsive"> <div class="tab-content">
<table class="table table-hover" id="table-mains"> <div class="tab-pane fade in active" id="mains">
<thead> {% if mains %}
<div class="table-responsive">
<table class="table table-hover" id="table-mains">
<thead>
<tr>
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
<th></th>
</tr>
</thead>
<tbody>
{% for id, main in mains.items %}
<tr> <tr>
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th> <td class="text-center" style="vertical-align:middle">
<th></th> <div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
</tr> <img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
</thead> <div class="caption text-center">
<tbody> {{ main.main }}
{% for id, main in mains.items %}
<tr>
<td class="text-center" style="vertical-align:middle">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
<div class="caption text-center">
{{ main.main }}
</div>
</div> </div>
</td> </div>
<td> </td>
<table class="table table-hover"> <td>
{% for alt in main.alts %} <table class="table table-hover">
{% if forloop.first %} {% for alt in main.alts %}
<tr> {% if forloop.first %}
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "Corporation" %}</th>
<th class="text-center">{% translate "Alliance" %}</th>
<th class="text-center"></th>
</tr>
{% endif %}
<tr> <tr>
<td class="text-center" style="width:5%"> <th></th>
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;"> <th class="text-center">{% translate "Character" %}</th>
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}"> <th class="text-center">{% translate "Corporation" %}</th>
</div> <th class="text-center">{% translate "Alliance" %}</th>
</td> <th class="text-center"></th>
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr> </tr>
{% endfor %} {% endif %}
</table> <tr>
</td> <td class="text-center" style="width:5%">
</tr> <div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
{% endfor %} <img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
</tbody> </div>
</table> </td>
</div> <td class="text-center" style="width:30%">{{ alt.character_name }}</td>
{% endif %} <td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
</div> <td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<div class="tab-pane fade" id="members"> <td class="text-center" style="width:5%">
{% if members %} <a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
<div class="table-responsive"> {% translate "Killboard" %}
<table class="table table-hover" id="table-members"> </a>
<thead> </td>
<tr> </tr>
<th></th> {% endfor %}
<th class="text-center">{% translate "Character" %}</th> </table>
<th class="text-center"></th> </td>
<th class="text-center">{% translate "Main Character" %}</th>
<th class="text-center">{% translate "Main Corporation" %}</th>
<th class="text-center">{% translate "Main Alliance" %}</th>
</tr> </tr>
</thead> {% endfor %}
<tbody> </tbody>
{% for member in members %} </table>
<tr> </div>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td> {% endif %}
<td class="text-center">{{ member }}</td> </div>
<td class="text-center"> <div class="tab-pane fade" id="members">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a> {% if members %}
</td> <div class="table-responsive">
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td> <table class="table table-hover" id="table-members">
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td> <thead>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td> <tr>
</tr> <th></th>
{% endfor %} <th class="text-center">{% translate "Character" %}</th>
{% for member in unregistered %} <th class="text-center"></th>
<tr class="danger"> <th class="text-center">{% translate "Main Character" %}</th>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td> <th class="text-center">{% translate "Main Corporation" %}</th>
<td class="text-center">{{ member.character_name }}</td> <th class="text-center">{% translate "Main Alliance" %}</th>
<td class="text-center"> </tr>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a> </thead>
</td> <tbody>
<td class="text-center"></td> {% for member in members %}
<td class="text-center"></td>
<td class="text-center"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
<tr> <tr>
<th></th> <td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
<th class="text-center">{% translate "Character" %}</th> <td class="text-center">{{ member }}</td>
<th class="text-center"></th> <td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
</tr> </tr>
</thead> {% endfor %}
<tbody> {% for member in unregistered %}
{% for member in unregistered %} <tr class="danger">
<tr class="danger"> <td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td> <td class="text-center">{{ member.character_name }}</td>
<td class="text-center">{{ member.character_name }}</td> <td class="text-center">
<td class="text-center"> <a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank"> </td>
{% translate "Killboard" %} <td class="text-center"></td>
</a> <td class="text-center"></td>
</td> <td class="text-center"></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="tab-pane fade" id="unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% for member in unregistered %}
<tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@@ -181,12 +179,15 @@
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js.html' %}
{% endblock %} {% endblock %}
{% block extra_css %} {% block extra_css %}
{% include 'bundles/datatables-css.html' %} {% include 'bundles/datatables-css.html' %}
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function(){ $(document).ready(function(){
$('#table-mains').DataTable({ $('#table-mains').DataTable({

View File

@@ -24,7 +24,7 @@
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td> <td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
<td class="text-center">{{ result.1.character_name }}</td> <td class="text-center">{{ result.1.character_name }}</td>
<td class="text-center">{{ result.0.corp.corporation_name }}</td> <td class="text-center">{{ result.0.corp.corporation_name }}</td>
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a></td> <td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
<td class="text-center">{{ result.1.main_character.character_name }}</td> <td class="text-center">{{ result.1.main_character.character_name }}</td>
<td class="text-center">{{ result.1.main_character.corporation_name }}</td> <td class="text-center">{{ result.1.main_character.corporation_name }}</td>
<td class="text-center">{{ result.1.main_character.alliance_name }}</td> <td class="text-center">{{ result.1.main_character.alliance_name }}</td>

View File

@@ -93,7 +93,7 @@ class AutogroupsConfigTestCase(TestCase):
group_qs = Group.objects.filter(pk=group.pk) group_qs = Group.objects.filter(pk=group.pk)
self.assertIn(group, self.member.groups.all()) self.assertIn(group, self.member.groups.all())
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False) self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
def test_update_alliance_group_membership_no_main_character(self): def test_update_alliance_group_membership_no_main_character(self):
obj = AutogroupsConfig.objects.create() obj = AutogroupsConfig.objects.create()
@@ -172,7 +172,7 @@ class AutogroupsConfigTestCase(TestCase):
group_qs = Group.objects.filter(pk=group.pk) group_qs = Group.objects.filter(pk=group.pk)
self.assertIn(group, self.member.groups.all()) self.assertIn(group, self.member.groups.all())
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False) self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
def test_update_corp_group_membership_no_state(self): def test_update_corp_group_membership_no_state(self):
obj = AutogroupsConfig.objects.create(corp_groups=True) obj = AutogroupsConfig.objects.create(corp_groups=True)

View File

@@ -8,10 +8,10 @@ Needs to be called with a context containing three objects:
--> -->
{% extends 'allianceauth/base.html' %} {% extends "allianceauth/base-bs5.html" %}
{% load evelinks %} {% load evelinks %}
{% block page_title %}Evelinks examples{% endblock %} {% block page_title %}Evelinks Examples{% endblock page_title %}
{% block content %} {% block content %}
@@ -25,60 +25,57 @@ Needs to be called with a context containing three objects:
<div class="col-md-4"> <div class="col-md-4">
<h3>evewho</h3> <h3>evewho</h3>
<p><a href="{{ my_character|evewho_character_url}}">character from character object</a></p> <p><a href="{{ my_character|evewho_character_url }}">character from character object</a></p>
<p><a href="{{ my_corporation|evewho_corporation_url}}">corporation form corporation object</a></p> <p><a href="{{ my_corporation|evewho_corporation_url }}">corporation form corporation object</a></p>
<p><a href="{{ my_character|evewho_corporation_url}}">corporation from charachter object</a></p> <p><a href="{{ my_character|evewho_corporation_url }}">corporation from charachter object</a></p>
<p><a href="{{ my_alliance|evewho_alliance_url}}">alliance from alliance object</a></p> <p><a href="{{ my_alliance|evewho_alliance_url }}">alliance from alliance object</a></p>
<p><a href="{{ my_character|evewho_alliance_url}}">alliance from character object</a></p> <p><a href="{{ my_character|evewho_alliance_url }}">alliance from character object</a></p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<h3>dotlan</h3> <h3>dotlan</h3>
<p><a href="{{ my_character|dotlan_corporation_url}}">corporation form character object</a></p> <p><a href="{{ my_character|dotlan_corporation_url }}">corporation form character object</a></p>
<p><a href="{{ my_corporation|dotlan_corporation_url}}">corporation form corporation object</a></p> <p><a href="{{ my_corporation|dotlan_corporation_url }}">corporation form corporation object</a></p>
<p><a href="{{ my_character|dotlan_alliance_url}}">alliance from character object</a></p> <p><a href="{{ my_character|dotlan_alliance_url }}">alliance from character object</a></p>
<p><a href="{{ my_alliance|dotlan_alliance_url}}">alliance from alliance object</a></p> <p><a href="{{ my_alliance|dotlan_alliance_url }}">alliance from alliance object</a></p>
<p><a href="{{ 'Black Rise'|dotlan_region_url}}">region from name string</a></p> <p><a href="{{ 'Black Rise'|dotlan_region_url }}">region from name string</a></p>
<p><a href="{{ 'Tama'|dotlan_solar_system_url}}">solar system from name string</a></p> <p><a href="{{ 'Tama'|dotlan_solar_system_url }}">solar system from name string</a></p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<h3>zkillboard</h3> <h3>zkillboard</h3>
<p><a href="{{ my_character|zkillboard_character_url}}">character from character object</a></p> <p><a href="{{ my_character|zkillboard_character_url }}">character from character object</a></p>
<p><a href="{{ my_character|zkillboard_corporation_url}}">corporation from character object</a></p> <p><a href="{{ my_character|zkillboard_corporation_url }}">corporation from character object</a></p>
<p><a href="{{ my_corporation|zkillboard_corporation_url}}">corporation form corporation object</a></p> <p><a href="{{ my_corporation|zkillboard_corporation_url }}">corporation form corporation object</a></p>
<p><a href="{{ my_character|zkillboard_alliance_url}}">alliance from character object</a></p> <p><a href="{{ my_character|zkillboard_alliance_url }}">alliance from character object</a></p>
<p><a href="{{ my_alliance|zkillboard_alliance_url}}">alliance from alliance object</a></p> <p><a href="{{ my_alliance|zkillboard_alliance_url }}">alliance from alliance object</a></p>
<p><a href="{{ 10000069|zkillboard_region_url}}">region from ID</a></p> <p><a href="{{ 10000069|zkillboard_region_url }}">region from ID</a></p>
<p><a href="{{ 30002813|zkillboard_solar_system_url}}">solar sytem from ID</a></p> <p><a href="{{ 30002813|zkillboard_solar_system_url }}">solar sytem from ID</a></p>
</div> </div>
</div> </div>
</div>
<h2>image URLs</h2> <h2>image URLs</h2>
<div class="rows"> <div class="rows">
<div class="col-md-4"> <div class="col-md-4">
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128}}"></p> <p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128 }}"></p>
<p>character from character object: <img src="{{ my_character|character_portrait_url:128}}"></p> <p>character from character object: <img src="{{ my_character|character_portrait_url:128 }}"></p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<p>corporation from ID: <img src="{{ my_character.corporation_id|corporation_logo_url:128}}"></p> <p>corporation from ID: <img src="{{ my_character.corporation_id|corporation_logo_url:128 }}"></p>
<p>corporation from character object: <img src="{{ my_character|corporation_logo_url:128}}"></p> <p>corporation from character object: <img src="{{ my_character|corporation_logo_url:128 }}"></p>
<p>corporation from corporation object: <img src="{{ my_corporation|corporation_logo_url:128}}"></p> <p>corporation from corporation object: <img src="{{ my_corporation|corporation_logo_url:128 }}"></p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<p>alliance from ID: <img src="{{ my_character.alliance_id|alliance_logo_url:128}}"></p> <p>alliance from ID: <img src="{{ my_character.alliance_id|alliance_logo_url:128 }}"></p>
<p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128}}"></p> <p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128 }}"></p>
<p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128}}"></p> <p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128 }}"></p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock content %}

View File

@@ -1,7 +1,8 @@
from allianceauth.menu.hooks import MenuItemHook
from . import urls from . import urls
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth import hooks from allianceauth import hooks
from allianceauth.services.hooks import MenuItemHook, UrlHook from allianceauth.services.hooks import UrlHook
@hooks.register('menu_item_hook') @hooks.register('menu_item_hook')

View File

@@ -2,7 +2,6 @@
import datetime import datetime
from django.db import migrations, models from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -15,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='fatlink', model_name='fatlink',
name='fatdatetime', name='fatdatetime',
field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=utc)), field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=datetime.timezone.utc)),
), ),
] ]

View File

@@ -1,27 +1,28 @@
{% extends 'allianceauth/base.html' %} {% extends 'allianceauth/base-bs5.html' %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Fleet Participation" %}{% endblock %} {% block page_title %}
{% translate "Fleet Participation" %}
{% endblock %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1> <h1 class="page-header text-center">{% translate "Character not found!" %}</h1>
<div class="col-lg-12 container" id="example"> <div class="col-lg-12 container" id="example">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">{{ character_name }}</div> <div class="panel-heading">{{ character_name }}</div>
<div class="panel-body"> <div class="panel-body">
<div class="col-lg-2 col-sm-2"> <div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}"> <img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
</div> </div>
<div class="col-lg-10 col-sm-2"> <div class="col-lg-10 col-sm-2">
<div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div> <div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div>
{% translate "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% translate "Add it here" %}</a> {% translate "before attempting to click fleet attendance links." %} {% translate "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% translate "Add it here" %}</a> {% translate "before attempting to click fleet attendance links." %}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

View File

@@ -1,31 +1,31 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load i18n %} {% load i18n %}
{% block page_title %}
{% block page_title %}{% translate "Create Fatlink" %}{% endblock page_title %} {% translate "Create Fatlink" %}
{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Create Fleet Operation" %}</h1> <h1 class="page-header text-center">{% translate "Create Fleet Operation" %}</h1>
<div class="container-fluid"> <div class="container-fluid">
{% if badrequest %} {% if badrequest %}
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div> <div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
{% endif %} {% endif %}
{% for message in errormessages %} {% for message in errormessages %}<div class="alert alert-danger" role="alert">{{ message }}</div>{% endfor %}
<div class="alert alert-danger" role="alert">{{ message }}</div> <div class="col-md-4 offset-md-4">
{% endfor %}
<div class="col-md-4 col-md-offset-4">
<div class="row"> <div class="row">
<form class="form-signin" role="form" action="" method="POST"> <form class="form-signin" role="form" action="" method="POST">
{% csrf_token %} {% csrf_token %}
{{ form|bootstrap }} {{ form|bootstrap }}
<br> <br>
<button class="btn btn-lg btn-primary btn-block" type="submit" name="submit_fat">{% translate "Create fatlink" %}</button> <button class="btn btn-lg btn-primary btn-block"
</form> type="submit"
name="submit_fat">
{% translate "Create fatlink" %}
</button>
</form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -1,11 +1,11 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %} {% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Edit fatlink" %} "{{ fatlink }}" <h1 class="page-header text-center">{% translate "Edit fatlink" %} "{{ fatlink }}"
<div class="text-right"> <div class="text-end">
<form> <form>
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True"> <button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
{% translate "Delete fat" %} {% translate "Delete fat" %}

View File

@@ -1,4 +1,4 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %} {% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
@@ -7,7 +7,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %} <h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
{% if char_id %} {% if char_id %}
<div class="text-right"> <div class="text-end">
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a> <a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a>
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% translate "Next month" %}</a> <a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% translate "Next month" %}</a>
</div> </div>
@@ -33,39 +33,39 @@
{% endfor %} {% endfor %}
</table> </table>
{% if created_fats %} {% if created_fats %}
<h2> <h2>
{% blocktranslate count links=n_created_fats trimmed %} {% blocktranslate count links=n_created_fats trimmed %}
{{ user }} has created one link this month. {{ user }} has created one link this month.
{% plural %} {% plural %}
{{ user }} has created {{ links }} links this month. {{ user }} has created {{ links }} links this month.
{% endblocktranslate %} {% endblocktranslate %}
</h2> </h2>
{% if created_fats %} {% if created_fats %}
<table class="table"> <table class="table">
<tr> <tr>
<th class="text-center">{% translate "Fleet" %}</th> <th class="text-center">{% translate "Fleet" %}</th>
<th class="text-center">{% translate "Creator" %}</th> <th class="text-center">{% translate "Creator" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th> <th class="text-center">{% translate "Eve Time" %}</th>
<th class="text-center">{% translate "Duration" %}</th> <th class="text-center">{% translate "Duration" %}</th>
<th class="text-center">{% translate "Edit" %}</th> <th class="text-center">{% translate "Edit" %}</th>
</tr> </tr>
{% for link in created_fats %} {% for link in created_fats %}
<tr> <tr>
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td> <td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a></td>
<td class="text-center">{{ link.creator.username }}</td> <td class="text-center">{{ link.creator.username }}</td>
<td class="text-center">{{ link.fatdatetime }}</td> <td class="text-center">{{ link.fatdatetime }}</td>
<td class="text-center">{{ link.duration }}</td> <td class="text-center">{{ link.duration }}</td>
<td class="text-center"> <td class="text-center">
<a href="{% url 'fatlink:modify' link.hash %}"> <a href="{% url 'fatlink:modify' link.hash %}">
<button type="button" class="btn btn-info"><span <button type="button" class="btn btn-info"><span
class="glyphicon glyphicon-edit"></span></button> class="glyphicon glyphicon-edit"></span></button>
</a> </a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -1,35 +1,34 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %} {% translate "Personal fatlink statistics" %}
{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %} <h1 class="page-header text-center">
<div class="text-right"> {% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
<a href="{% url 'fatlink:personal_statistics_year' previous_year %}" class="btn btn-info">{% translate "Previous year" %}</a> <div class="text-end">
{% if next_year %} <a href="{% url "fatlink:personal_statistics_year" previous_year %}" class="btn btn-info"><i class="fa-solid fa-chevron-left"></i> {% translate "Previous year" %}</a>
<a href="{% url 'fatlink:personal_statistics_year' next_year %}" class="btn btn-info">{% translate "Next year" %}</a> {% if next_year %}
{% endif %} <a href="{% url "fatlink:personal_statistics_year" next_year %}" class="btn btn-info">{% translate "Next year" %} <i class="fa-solid fa-chevron-right"></i></a>
{% endif %}
</div> </div>
</h1> </h1>
<div class="col-lg-2 col-lg-offset-5"> <div class="col-lg-2 offset-lg-5">
<table class="table table-responsive"> <table class="table table-responsive">
<tr> <tr>
<th class="col-md-2 text-center">{% translate "Month" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Month" %}</th>
<th class="col-md-2 text-center">{% translate "Fats" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
</tr> </tr>
{% for monthnr, month, n_fats in monthlystats %} {% for monthnr, month, n_fats in monthlystats %}
<tr> <tr>
<td class="text-center"> <td class="text-center">
<a href="{% url 'fatlink:personal_statistics_month' year monthnr %}"> <a href="{% url 'fatlink:personal_statistics_month' year monthnr %}">{{ month }}</a>
{{ month }} </td>
</a> <td class="text-center">{{ n_fats }}</td>
</td> </tr>
<td class="text-center">{{ n_fats }}</td> {% endfor %}
</tr> </table>
{% endfor %}
</table>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -1,46 +1,50 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}
{% block page_title %}{% translate "Fatlink Corp Statistics" %}{% endblock page_title %} {% translate "Fatlink Corp Statistics" %}
{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %} <h1 class="page-header text-center">
<div class="text-right"> {% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<a href="{% url 'fatlink:statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a> <div class="text-end">
{% if next_month %} <a href="{% url "fatlink:statistics_corp_month" corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
<a href="{% url 'fatlink:statistics_corp_month' corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a> {% if next_month %}
{% endif %} <a href="{% url "fatlink:statistics_corp_month" corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
{% endif %}
</div> </div>
</h1> </h1>
{% if fatStats %} {% if fatStats %}
<table class="table table-responsive"> <div class="table-responsive">
<tr> <table class="table table-striped">
<th class="col-md-1"></th> <tr>
<th class="col-md-2 text-center">{% translate "Main Character" %}</th> <th scope="col" class="col-md-1"></th>
<th class="col-md-2 text-center">{% translate "Characters" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Main Character" %}</th>
<th class="col-md-2 text-center">{% translate "Fats" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Characters" %}</th>
<th class="col-md-2 text-center">{% translate "Average fats" %} <th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i> <th scope="col" class="col-md-2 text-center">
</th> {% translate "Average fats" %}
</tr> <i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
{% for memberStat in fatStats %} </th>
<tr> </tr>
<td> {% for memberStat in fatStats %}
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}"> <tr>
</td> <td>
<td class="text-center">{{ memberStat.mainchar.character_name }}</td> <img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}">
<td class="text-center">{{ memberStat.n_chars }}</td> </td>
<td class="text-center">{{ memberStat.n_fats }}</td> <td class="text-center">{{ memberStat.mainchar.character_name }}</td>
<td class="text-center">{{ memberStat.avg_fat }}</td> <td class="text-center">{{ memberStat.n_chars }}</td>
</tr> <td class="text-center">{{ memberStat.n_fats }}</td>
{% endfor %} <td class="text-center">{{ memberStat.avg_fat }}</td>
</table> </tr>
{% endfor %}
</table>
</div>
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function () { $(document).ready(function () {
$("[rel=tooltip]").tooltip(); $("[rel=tooltip]").tooltip();
}); });
{% endblock extra_script %} {% endblock extra_script %}

View File

@@ -1,48 +1,54 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}
{% block page_title %}{% translate "Fatlink statistics" %}{% endblock page_title %} {% translate "Fatlink Statistics" %}
{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %} <h1 class="page-header text-center">
<div class="text-right"> {% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<a href="{% url 'fatlink:statistics_month' previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a> <div class="text-end">
{% if next_month %} <a href="{% url "fatlink:statistics_month" previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
<a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a> {% if next_month %}
{% endif %} <a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
{% endif %}
</div> </div>
</h1> </h1>
{% if fatStats %} {% if fatStats %}
<table class="table table-responsive"> <div class="table-responsive">
<tr> <table class="table table-striped">
<th class="col-md-1"></th> <tr>
<th class="col-md-2 text-center">{% translate "Ticker" %}</th> <th scope="col" class="col-md-1"></th>
<th class="col-md-5 text-center">{% translate "Corp" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Ticker" %}</th>
<th class="col-md-2 text-center">{% translate "Members" %}</th> <th scope="col" class="col-md-5 text-center">{% translate "Corp" %}</th>
<th class="col-md-2 text-center">{% translate "Fats" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Members" %}</th>
<th class="col-md-2 text-center">{% translate "Average fats" %} <th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i> <th scope="col" class="col-md-2 text-center">
</th> {% translate "Average fats" %}
</tr> <i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
{% for corpStat in fatStats %} </th>
<tr> </tr>
<td> {% for corpStat in fatStats %}
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}"> <tr>
</td> <td>
<td class="text-center"><a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a></td> <img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
<td class="text-center">{{ corpStat.corp.corporation_name }}</td> </td>
<td class="text-center">{{ corpStat.corp.member_count }}</td> <td class="text-center">
<td class="text-center">{{ corpStat.n_fats }}</td> <a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a>
<td class="text-center">{{ corpStat.avg_fat }}</td> </td>
</tr> <td class="text-center">{{ corpStat.corp.corporation_name }}</td>
{% endfor %} <td class="text-center">{{ corpStat.corp.member_count }}</td>
</table> <td class="text-center">{{ corpStat.n_fats }}</td>
<td class="text-center">{{ corpStat.avg_fat }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function () { $(document).ready(function () {
$("[rel=tooltip]").tooltip(); $("[rel=tooltip]").tooltip();
}); });
{% endblock extra_script %} {% endblock extra_script %}

View File

@@ -1,99 +1,107 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %} {% translate "Fatlink view" %}
{% endblock page_title %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Participation data" %}</h1> <h1 class="page-header text-center">{% translate "Participation data" %}</h1>
<table class="table"> <div class="table-responsive">
<tr> <table class="table table-striped">
<th class="col-md-11">
<h4><b>{% translate "Most recent clicked fatlinks" %}</b>
</h4>
</th>
<th class="col-md-1">
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info">
{% translate "Personal statistics" %}
</a>
</th>
</tr>
</table>
{% if fats %}
<table class="table table-responsive">
<tr>
<th class="text-center">{% translate "Fleet" %}</th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "System" %}</th>
<th class="text-center">{% translate "Ship" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th>
</tr>
{% for fat in fats %}
<tr>
<td class="text-center">{{ fat.fatlink.fleet }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %}
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}
<td class="text-center">{{ fat.shiptype }}</td>
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
{% endif %}
{% if perms.auth.fleetactivitytracking%}
<table class="table">
<tr> <tr>
<th class="col-md-10"> <th class="col-md-10">
<h4><b>{% translate "Most recent fatlinks" %}</b> <h4>
<b>{% translate "Most recent clicked fatlinks" %}</b>
</h4> </h4>
</th> </th>
<th class="col-md-1"> <th class="col-md-2 align-self-end">
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info"> <a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info"><i class="fa-solid fa-circle-info fa-fw"></i>{% translate "Personal statistics" %}</a>
{% translate "View statistics" %}
</a>
</th>
<th class="col-md-1">
<a href="{% url 'fatlink:create' %}" class="btn btn-success">
{% translate "Create fatlink" %}
</a>
</th> </th>
</tr> </tr>
</table> </table>
{% if fatlinks %} </div>
<table class="table"> {% if fats %}
<tr> <div class="table-responsive">
<th class="text-center">{% translate "Name" %}</th> <table class="table table-striped">
<th class="text-center">{% translate "Creator" %}</th> <tr>
<th class="text-center">{% translate "Fleet" %}</th> <th scope="col" class="text-center">{% translate "Fleet" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th> <th scope="col" class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "Duration" %}</th> <th scope="col" class="text-center">{% translate "System" %}</th>
<th class="text-center">{% translate "Edit" %}</th> <th scope="col" class="text-center">{% translate "Ship" %}</th>
</tr> <th scope="col" class="text-center">{% translate "Eve Time" %}</th>
{% for link in fatlinks %} </tr>
<tr> {% for fat in fats %}
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td> <tr>
<td class="text-center">{{ link.creator.username }}</td> <td class="text-center">{{ fat.fatlink.fleet }}</td>
<td class="text-center">{{ link.fleet }}</td> <td class="text-center">{{ fat.character.character_name }}</td>
<td class="text-center">{{ link.fatdatetime }}</td> {% if fat.station != "No Station" %}
<td class="text-center">{{ link.duration }}</td> <td class="text-center">{% translate "Docked in" %} {{ fat.system }}</td>
<td class="text-center"> {% else %}
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info"> <td class="text-center">{{ fat.system }}</td>
<span class="glyphicon glyphicon-edit"></span> {% endif %}
</a> <td class="text-center">{{ fat.shiptype }}</td>
</td> <td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table>
</table> </div>
{% else %} {% else %}
<div class="alert alert-warning text-center">{% translate "No created fatlinks on record." %}</div> <div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
{% endif %} {% endif %}
{% if perms.auth.fleetactivitytracking %}
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th class="col-md-8">
<h4>
<b>{% translate "Most recent fatlinks" %}</b>
</h4>
</th>
<th class="col-md-2 align-self-end">
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info"><i class="fa-solid fa-eye fa-fw"></i> {% translate "View statistics" %}</a>
</th>
<th class="col-md-2 align-self-end">
<a href="{% url 'fatlink:create' %}" class="btn btn-success"><i class="fa-solid fa-plus fa-fw"></i> {% translate "Create fatlink" %}</a>
</th>
</tr>
</table>
</div>
{% if fatlinks %}
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th scope="col" class="text-center">{% translate "Name" %}</th>
<th scope="col" class="text-center">{% translate "Creator" %}</th>
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
<th scope="col" class="text-center">{% translate "Duration" %}</th>
<th scope="col" class="text-center">{% translate "Edit" %}</th>
</tr>
{% for link in fatlinks %}
<tr>
<td class="text-center">
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a>
</td>
<td class="text-center">{{ link.creator.username }}</td>
<td class="text-center">{{ link.fleet }}</td>
<td class="text-center">{{ link.fatdatetime }}</td>
<td class="text-center">
{{ link.duration }}
</td>
<td class="text-center">
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
<i class="fa-solid fa-pen-to-square fa-fw"></i>
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No created fatlinks on record." %}
</div>
{% endif %}
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -1,6 +1,7 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import MenuItemHook, UrlHook from allianceauth.services.hooks import UrlHook
from allianceauth import hooks from allianceauth import hooks
from . import urls from . import urls
@@ -33,11 +34,43 @@ class GroupManagementMenuItem(MenuItemHook):
return "" return ""
"""
<li class="d-flex m-2 p-2 pt-0 pb-0 mt-0 mb-0">
<i class="fas fa-users fa-fw align-self-center me-2"></i>
<a class="nav-link flex-fill align-self-center {% navactive request 'groupmanagement:groups' %}" href="{% url 'groupmanagement:groups' %}">
{% translate "Groups" %}
</a>
</li>
"""
class GroupsMenuItem(MenuItemHook):
def __init__(self):
MenuItemHook.__init__(
self,
text=_("Groups"),
classes="fas fa-user fa-fw",
url_name="groupmanagement:groups",
order=25,
navactive=[
"groupmanagement:groups", # group list view
],
)
def render(self, request):
return MenuItemHook.render(self, request)
@hooks.register("menu_item_hook") @hooks.register("menu_item_hook")
def register_menu(): def register_manager_menu():
return GroupManagementMenuItem() return GroupManagementMenuItem()
@hooks.register("menu_item_hook")
def register_groups_menu():
return GroupsMenuItem()
@hooks.register("url_hook") @hooks.register("url_hook")
def register_urls(): def register_urls():
return UrlHook(urls, "group", r"^groups/") return UrlHook(urls, "group", r"^groups/")

View File

@@ -1,10 +1,6 @@
import functools
from django import forms from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.auth.models import Group
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models.functions import Lower
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -12,39 +8,6 @@ from .models import ReservedGroupName
class GroupAdminForm(forms.ModelForm): class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.order_by(Lower('username')),
required=False,
widget=FilteredSelectMultiple(verbose_name=_("Users"), is_stacked=False),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields["users"].initial = self.instance.user_set.all()
def save(self, commit=True):
group: Group = super().save(commit=False)
if commit:
group.save()
users = self.cleaned_data["users"]
if group.pk:
self._save_m2m_and_users(group, users)
else:
self.save_m2m = functools.partial(
self._save_m2m_and_users, group=group, users=users
)
return group
def _save_m2m_and_users(self, group, users):
"""Save m2m relations incl. users."""
group.user_set.set(users)
self._save_m2m()
def clean_name(self): def clean_name(self):
my_name = self.cleaned_data['name'] my_name = self.cleaned_data['name']
if ReservedGroupName.objects.filter(name__iexact=my_name).exists(): if ReservedGroupName.objects.filter(name__iexact=my_name).exists():

View File

@@ -1,7 +1,8 @@
from typing import Set from typing import Set
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -13,7 +14,7 @@ from allianceauth.notifications import notify
class GroupRequest(models.Model): class GroupRequest(models.Model):
"""Request from a user for joining or leaving a group.""" """Request from a user for joining or leaving a group."""
leave_request = models.BooleanField(default=False) leave_request = models.BooleanField(default=0)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE)
@@ -48,7 +49,7 @@ class RequestLog(models.Model):
request_type = models.BooleanField(null=True) request_type = models.BooleanField(null=True)
group = models.ForeignKey(Group, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE)
request_info = models.CharField(max_length=254) request_info = models.CharField(max_length=254)
action = models.BooleanField(default=False) action = models.BooleanField(default=0)
request_actor = models.ForeignKey(User, on_delete=models.CASCADE) request_actor = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)

View File

@@ -1,85 +1,75 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %} {% load i18n %}
{% load navactive %}
{% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %} {% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %}
{% block header_nav_brand %}{% translate "Audit Log" %} - {{ group.name }}{% endblock header_nav_brand %}
{% block header_nav_collapse_left %}
<li class="nav-item ">
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Back" %}</a>
</li>
{% endblock %}
{% block content %} {% block content %}
<div class="col-lg-12"> {% if entries %}
<br> <div class="table-responsive">
{% include 'groupmanagement/menu.html' %} <table class="table table-striped" id="log-entries">
<thead>
<tr>
<th scope="col">{% translate "Date/Time" %}</th>
<th scope="col">{% translate "Requestor" %}</th>
<th scope="col">{% translate "Character" %}</th>
<th scope="col">{% translate "Corporation" %}</th>
<th scope="col">{% translate "Type" %}</th>
<th scope="col">{% translate "Action" %}</th>
<th scope="col">{% translate "Actor" %}</th>
</tr>
</thead>
<div class="panel panel-default"> <tbody>
<div class="panel-heading"> {% for entry in entries %}
{{ group }} - {% translate "Audit Log" %} <tr>
</div> <td>{{ entry.date|date:"Y-M-d, H:i" }}</td>
<td>{{ entry.requestor }}</td>
<td>{{ entry.req_char }}</td>
<td>{{ entry.req_char.corporation_name }}</td>
<td>{{ entry.type_to_str }}</td>
<div class="panel-body"> {% if entry.request_type is None %}
<p> <td>{% translate "Removed" %}</td>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> {% else %}
{% translate "Back" %} <td>{{ entry.action_to_str }}</td>
</a> {% endif %}
</p>
{% if entries %} <td>{{ entry.request_actor }}</td>
<div class="table-responsive"> </tr>
<table class="table table-striped" id="log-entries"> {% endfor %}
<thead> </tbody>
<tr> </table>
<th scope="col">{% translate "Date/Time" %}</th>
<th scope="col">{% translate "Requestor" %}</th>
<th scope="col">{% translate "Character" %}</th>
<th scope="col">{% translate "Corporation" %}</th>
<th scope="col">{% translate "Type" %}</th>
<th scope="col">{% translate "Action" %}</th>
<th scope="col">{% translate "Actor" %}</th>
</tr>
</thead>
<tbody> <p class="text-muted">
{% for entry in entries %} {% translate "All times displayed are EVE/UTC." %}
<tr> </p>
<td>{{ entry.date|date:"Y-M-d, H:i" }}</td>
<td>{{ entry.requestor }}</td>
<td>{{ entry.req_char }}</td>
<td>{{ entry.req_char.corporation_name }}</td>
<td>{{ entry.type_to_str }}</td>
{% if entry.request_type is None %}
<td>{% translate "Removed" %}</td>
{% else %}
<td>{{ entry.action_to_str }}</td>
{% endif %}
<td>{{ entry.request_actor }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-muted">
{% translate "All times displayed are EVE/UTC." %}
</p>
</div>
{% else %}
<div class="clearfix"></div>
<br>
<div class="alert alert-warning text-center">
{% translate "No entries found for this group." %}
</div>
{% endif %}
</div>
</div> </div>
</div> {% else %}
<div class="clearfix"></div>
<br>
<div class="alert alert-warning text-center">
{% translate "No entries found for this group." %}
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% include 'bundles/moment-js.html' with locale=True %} {% include 'bundles/moment-js.html' with locale=True %}
{% include 'bundles/filterdropdown-js.html' %} {% include 'bundles/filterdropdown-js.html' %}
{% endblock %} {% endblock %}
{% block extra_css %} {% block extra_css %}
{% include 'bundles/datatables-css.html' %} {% include 'bundles/datatables-css-bs5.html' %}
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}

View File

@@ -1,97 +1,86 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %} {% load i18n %}
{% load evelinks %} {% load evelinks %}
{% load navactive %}
{% block page_title %}{% translate "Group Members" %}{% endblock page_title %} {% block page_title %}{% translate "Group Members" %}{% endblock page_title %}
{% block header_nav_brand %}{% translate "Group Members" %} - {{ group.name }}{% endblock header_nav_brand %}
{% block header_nav_collapse_left %}
<li class="nav-item ">
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Back" %}</a>
</li>
{% endblock %}
{% block content %} {% block content %}
<div class="col-lg-12"> {% if group.user_set %}
<br> <div class="table-responsive">
{% include 'groupmanagement/menu.html' %} <table class="table table-aa" id="tab_group_members">
<thead>
<tr>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th></th>
</tr>
</thead>
<div class="panel panel-default"> <tbody>
<div class="panel-heading"> {% for member in members %}
{{ group.name }} - {% translate 'Members' %} <tr>
</div> <td>
<img src="{{ member.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}">
{% if member.main_char %}
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
{{ member.main_char.character_name }}
</a>
{% else %}
{{ member.user.username }}
{% endif %}
<div class="panel-body"> {% if member.is_leader %}
<p> <i class="fa-solid fa-star"> title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>&nbsp;
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> {% endif %}
{% translate "Back" %} </td>
</a>
</p>
{% if group.user_set %} <td>
<div class="table-responsive"> {% if member.main_char %}
<table class="table table-aa" id="tab_group_members"> <a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
<thead> {{ member.main_char.corporation_name }}
<tr> </a><br>
<th>{% translate "Character" %}</th> {{ member.main_char.alliance_name|default_if_none:"" }}
<th>{% translate "Organization" %}</th> {% else %}
<th></th> {% translate "(unknown)" %}
</tr> {% endif %}
</thead> </td>
<td class="text-end">
<tbody> <a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
{% for member in members %} <i class="fa-solid fa-xmark"></i>
<tr> </a>
<td> </td>
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}"> </tr>
{% if member.main_char %} {% endfor %}
<a href="{{ member.main_char|evewho_character_url }}" target="_blank"> </tbody>
{{ member.main_char.character_name }} </table>
</a>
{% else %}
{{ member.user.username }}
{% endif %}
{% if member.is_leader %}
<i class="fas fa-star" title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>&nbsp;
{% endif %}
</td>
<td>
{% if member.main_char %}
<a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
{{ member.main_char.corporation_name }}
</a><br>
{{ member.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td class="text-right">
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
<i class="glyphicon glyphicon-remove"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-muted">
<i class="fas fa-star"></i>: {% translate "Group leader" %}
</p>
</div>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No group members to list." %}
</div>
{% endif %}
</div>
<p class="text-muted">
<i class="fa-solid fa-star"></i>: {% translate "Group leader" %}
</p>
</div> </div>
</div> {% else %}
<div class="alert alert-warning text-center">
{% translate "No group members to list." %}
</div>
{% endif %}
{% endblock content %} {% endblock content %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% endblock %} {% endblock %}
{% block extra_css %} {% block extra_css %}
{% include 'bundles/datatables-css.html' %} {% include 'bundles/datatables-css-bs5.html' %}
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}

View File

@@ -1,88 +1,86 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %} {% load i18n %}
{% load navactive %}
{% block page_title %}{% translate "Groups Membership" %}{% endblock page_title %} {% block page_title %}{% translate "Groups Membership" %}{% endblock page_title %}
{% block header_nav_brand %}{% translate "Groups Membership" %}{% endblock header_nav_brand %}
{% block extra_css %}{% endblock extra_css %} {% block extra_css %}{% endblock extra_css %}
{% block header_nav_collapse_left %}
<li class="nav-item ">
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Join/Leave Requests" %}</a>
</li>
{% endblock header_nav_collapse_left %}
{% block content %} {% block content %}
<div class="col-lg-12"> {% if groups %}
<br> <div class="table-responsive">
{% include 'groupmanagement/menu.html' %} <table class="table table-aa">
<thead>
<tr>
<th>{% translate "Name" %}</th>
<th>{% translate "Description" %}</th>
<th>{% translate "Status" %}</th>
<th style="white-space: nowrap;" class="text-center">{% translate "Member Count" %}</th>
<th style="min-width: 170px;"></th>
</tr>
</thead>
<div class="panel panel-default"> <tbody class="align-middle">
<div class="panel-heading"> {% for group in groups %}
{% translate "Groups" %} <tr>
</div> <td>
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
</td>
<div class="panel-body"> <td>{{ group.authgroup.description|linebreaks|urlize }}</td>
{% if groups %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% translate "Name" %}</th>
<th>{% translate "Description" %}</th>
<th>{% translate "Status" %}</th>
<th style="white-space: nowrap;">{% translate "Member Count" %}</th>
<th style="min-width: 170px;"></th>
</tr>
</thead>
<tbody> <td>
{% for group in groups %} {% if group.authgroup.hidden %}
<tr> <span class="badge bg-info">{% translate "Hidden" %}</span>
<td> {% endif %}
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a> {% if group.authgroup.open %}
</td> <span class="badge bg-success">{% translate "Open" %}</span>
{% else %}
<span class="badge bg-secondary">{% translate "Requestable" %}</span>
{% endif %}
</td>
<td>{{ group.authgroup.description|linebreaks|urlize }}</td> <td class="text-center">
{{ group.num_members }}
</td>
<td> <td class="text-end">
{% if group.authgroup.hidden %} <a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
<span class="label label-info">{% translate "Hidden" %}</span> <i class="far fa-eye"></i>
{% elif group.authgroup.open %} </a>
<span class="label label-success">{% translate "Open" %}</span>
{% else %}
<span class="label label-default">{% translate "Requestable" %}</span>
{% endif %}
</td>
<td class="text-right"> <a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
{{ group.num_members }} <i class="far fa-list-alt"></i>
</td> </a>
<td class="text-right"> <a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}"> <i class="far fa-clipboard"></i>
<i class="glyphicon glyphicon-eye-open"></i> </a>
</a> </td>
</tr>
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}"> {% endfor %}
<i class="glyphicon glyphicon-list-alt"></i> </tbody>
</a> </table>
<a id="clipboard-copy" data-clipboard-text="{{ SITE_URL }}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
<i class="glyphicon glyphicon-copy"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No groups to list." %}
</div>
{% endif %}
</div>
</div> </div>
</div> {% else %}
<div class="alert alert-warning text-center">
{% translate "No groups to list." %}
</div>
{% endif %}
{% endblock content %} {% endblock content %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/clipboard-js.html' %} {% include "bundles/clipboard-js.html" %}
<script> <script>
new ClipboardJS('#clipboard-copy'); new ClipboardJS('#clipboard-copy');
</script> </script>
{% endblock %} {% endblock extra_javascript %}

View File

@@ -1,62 +1,93 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Available Groups" %}{% endblock page_title %} {% block page_title %}{% translate "Available Groups" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %} {% block header_nav_brand %}{% translate "Available Groups" %}{% endblock header_nav_brand %}
{% if manager_perms %}
{% block header_nav_collapse_left %}
<li class="nav-item">
<a class="nav-link" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}
{% if req_count %}
<span class="badge bg-secondary">{{ req_count }}</span>
{% endif %}
</a>
</li>
{% endblock %}
{% endif %}
{% block content %} {% block content %}
<div class="col-lg-12"> {% if groups %}
<h1 class="page-header text-center">{% translate "Available Groups" %}</h1> <table class="table" id="groupsTable" >
{% if groups %} <thead>
<table class="table table-aa"> <tr>
<thead> <th>{% translate "Name" %}</th>
<tr> <th>{% translate "Description" %}</th>
<th>{% translate "Name" %}</th> <th>{% translate "Leaders" %}<span class="m-1 fw-lighter badge bg-primary">{% translate "User" %}</span><span class="m-1 fw-lighter badge bg-secondary ">{% translate "Group" %}</span></th>
<th>{% translate "Description" %}</th> <th></th>
<th></th> </tr>
</tr> </thead>
</thead>
<tbody> <tbody class>
{% for g in groups %} {% for g in groups %}
<tr> <tr>
<td>{{ g.group.name }}</td> <td>{{ g.group.name }}</td>
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td> <td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
<td class="text-right"> <td style="max-width: 30%;">
{% if g.group in user.groups.all %} {% if g.group.authgroup.group_leaders.all.count %}
{% if not g.request %} {% for leader in g.group.authgroup.group_leaders.all %}{% if leader.profile.main_character %}<span class="m-1 badge bg-primary">{{leader.profile.main_character}}</span>{% endif %}{% endfor %}
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger"> {% endif %}
{% translate "Leave" %} {% if g.group.authgroup.group_leaders.all.count %}
</a> {% for group in g.group.authgroup.group_leader_groups.all %}<span class="badge bg-secondary">{{group.name}}</span>{% endfor %}
{% else %} {% endif %}
<button type="button" class="btn btn-primary" disabled> </td>
{% translate "Pending" %} <td class="text-end">
</button> {% if g.group in user_groups %}
{% endif %} {% if not g.request %}
{% elif not g.request %} <a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
{% if g.group.authgroup.open %} {% translate "Leave" %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success"> </a>
{% translate "Join" %}
</a>
{% else %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
{% translate "Request" %}
</a>
{% endif %}
{% else %} {% else %}
<button type="button" class="btn btn-primary" disabled> <button type="button" class="btn btn-primary" disabled>
{% translate "Pending" %} {% translate "Pending" %}
</button> </button>
{% endif %} {% endif %}
</td> {% elif not g.request %}
</tr> {% if g.group.authgroup.open %}
{% endfor %} <a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
</tbody> {% translate "Join" %}
</table> </a>
{% else %} {% else %}
<div class="alert alert-warning text-center"> <a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
{% translate "No groups available." %} {% translate "Request" %}
</div> </a>
{% endif %} {% endif %}
</div> {% else %}
<button type="button" class="btn btn-primary" disabled>
{% translate "Pending" %}
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No groups available." %}
</div>
{% endif %}
{% endblock content %} {% endblock content %}
{% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css-bs5.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function () {
$('#groupsTable').DataTable();
});
{% endblock extra_script %}

View File

@@ -1,166 +1,158 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %} {% load i18n %}
{% load evelinks %} {% load evelinks %}
{% load navactive %}
{% block page_title %}{% translate "Groups Management" %}{% endblock page_title %} {% block page_title %}{% translate "Groups Management" %}{% endblock page_title %}
{% block header_nav_brand %}{% translate "Groups Management" %}{% endblock header_nav_brand %}
{% block extra_css %} {% block extra_css %}
<style>
.nav-tabs > li.active > a {
background-color: rgb(236, 240, 241) !important;
color: rgb(44, 62, 80);
}
</style>
{% endblock extra_css %} {% endblock extra_css %}
{% block content %} {% block header_nav_collapse_left %}
<div class="col-lg-12"> <li class="active">
<br> <a class="nav-link active" id="add-tab" data-bs-toggle="tab" data-bs-target="#add" type="button" role="tab" aria-controls="addd" aria-selected="true">
{% include 'groupmanagement/menu.html' %} {% translate "Join Requests" %}
<ul class="nav nav-tabs"> {% if acceptrequests %}
<li class="active"> <span class="badge bg-secondary">{{ acceptrequests|length }}</span>
<a data-toggle="tab" href="#add"> {% endif %}
{% translate "Join Requests" %} </a>
</li>
{% if acceptrequests %} {% if not auto_leave %}
<span class="badge">{{ acceptrequests|length }}</span> <li>
{% endif %} <a class="nav-link" id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave" type="button" role="tab" aria-controls="leave" aria-selected="false">
</a> {% translate "Leave Requests" %}
</li>
{% if not show_leave_tab %} {% if leaverequests %}
<li> <span class="badge bg-secondary">{{ leaverequests|length }}</span>
<a data-toggle="tab" href="#leave">
{% translate "Leave Requests" %}
{% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>
{% endif %} {% endif %}
</ul> </a>
</li>
{% endif %}
<li class="nav-item ">
<a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
</li>
<div class="panel panel-default panel-tabs-aa"> {% endblock %}
<div class="panel-body">
<div class="tab-content">
<div id="add" class="tab-pane active">
{% if acceptrequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th>{% translate "Group" %}</th>
<th></th>
</tr>
</thead>
<tbody> {% block content %}
{% for acceptrequest in acceptrequests %} <div class="tab-content">
<tr> <div id="add" class="tab-pane active">
<td> {% if acceptrequests %}
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}"> <div class="table-responsive">
{% if acceptrequest.main_char %} <table class="table table-aa">
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank"> <thead>
{{ acceptrequest.main_char.character_name }} <tr>
</a> <th>{% translate "Character" %}</th>
{% else %} <th>{% translate "Organization" %}</th>
{{ acceptrequest.user.username }} <th>{% translate "Group" %}</th>
{% endif %} <th></th>
</td> </tr>
<td> </thead>
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ acceptrequest.main_char.corporation_name }}
</a><br>
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td>{{ acceptrequest.group.name }}</td>
<td class="text-right">
<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"> <tbody class="align-middle">
{% translate "Reject" %} {% for acceptrequest in acceptrequests %}
</a> <tr>
</td> <td>
</tr> <img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
{% endfor %} {% if acceptrequest.main_char %}
</tbody> <a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
</table> {{ acceptrequest.main_char.character_name }}
</div> </a>
{% else %} {% else %}
<div class="alert alert-warning text-center">{% translate "No group add requests." %}</div> {{ acceptrequest.user.username }}
{% endif %} {% endif %}
</div> </td>
<td>
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ acceptrequest.main_char.corporation_name }}
</a><br>
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td>{{ acceptrequest.group.name }}</td>
<td class="text-end">
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
{% translate "Accept" %}
</a>
{% if not show_leave_tab %} <a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
<div id="leave" class="tab-pane"> {% translate "Reject" %}
{% if leaverequests %} </a>
<div class="table-responsive"> </td>
<table class="table table-aa"> </tr>
<thead> {% endfor %}
<tr> </tbody>
<th>{% translate "Character" %}</th> </table>
<th>{% translate "Organization" %}</th>
<th>{% translate "Group" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for leaverequest in leaverequests %}
<tr>
<td>
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
{% else %}
{{ leaverequest.user.username }}
{% endif %}
</td>
<td>
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a><br>
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td>{{ leaverequest.group.name }}</td>
<td class="text-right">
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
{% translate "Accept" %}
</a>
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
{% translate "Reject" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
{% endif %}
</div>
{% endif %}
</div>
</div> </div>
</div> {% else %}
<div class="alert alert-warning text-center">{% translate "No group add requests." %}</div>
{% endif %}
</div> </div>
{% if not auto_leave %}
<div id="leave" class="tab-pane">
{% if leaverequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th>{% translate "Group" %}</th>
<th></th>
</tr>
</thead>
<tbody class="align-middle">
{% for leaverequest in leaverequests %}
<tr>
<td>
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
{% else %}
{{ leaverequest.user.username }}
{% endif %}
</td>
<td>
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a><br>
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% translate "(unknown)" %}
{% endif %}
</td>
<td>{{ leaverequest.group.name }}</td>
<td class="text-end">
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
{% translate "Accept" %}
</a>
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
{% translate "Reject" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
{% endif %}
</div>
{% endif %}
</div>
{% endblock content %} {% endblock content %}

View File

@@ -1,27 +1,10 @@
{% load i18n %} {% load i18n %}
{% load navactive %} {% load navactive %}
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">{% translate "Toggle navigation" %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <li class="nav-item ">
<ul class="nav navbar-nav"> <a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
<li class="{% navactive request 'groupmanagement:management' %}"> </li>
<a href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a> <li class="nav-item ">
</li> <a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
<li class="{% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}"> </li>
<a href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@@ -6,22 +6,22 @@ from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.sites import AdminSite from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import Client, RequestFactory, TestCase, override_settings from django.test import TestCase, RequestFactory, Client, override_settings
from allianceauth.authentication.models import CharacterOwnership, State from allianceauth.authentication.models import CharacterOwnership, State
from allianceauth.eveonline.models import ( from allianceauth.eveonline.models import (
EveAllianceInfo, EveCharacter, EveCorporationInfo, EveCharacter, EveCorporationInfo, EveAllianceInfo
) )
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
from ..admin import Group, GroupAdmin, HasLeaderFilter
from ..models import ReservedGroupName
from . import get_admin_change_view_url from . import get_admin_change_view_url
from ..admin import HasLeaderFilter, GroupAdmin, Group
from ..models import ReservedGroupName
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True _has_auto_groups = True
from allianceauth.eveonline.autogroups.models import AutogroupsConfig from allianceauth.eveonline.autogroups.models import AutogroupsConfig
from ..admin import IsAutoGroupFilter from ..admin import IsAutoGroupFilter
else: else:
_has_auto_groups = False _has_auto_groups = False
@@ -621,16 +621,21 @@ class TestGroupAdmin2(TestCase):
response = self.client.post( response = self.client.post(
f"/admin/groupmanagement/group/{group.pk}/change/", f"/admin/groupmanagement/group/{group.pk}/change/",
data={ data={
"name": group.name, "name": f"{group.name}",
"users": [user_member.pk, user_guest.pk], "authgroup-TOTAL_FORMS": "1",
"authgroup-TOTAL_FORMS": 1, "authgroup-INITIAL_FORMS": "1",
"authgroup-INITIAL_FORMS": 1, "authgroup-MIN_NUM_FORMS": "0",
"authgroup-MIN_NUM_FORMS": 0, "authgroup-MAX_NUM_FORMS": "1",
"authgroup-MAX_NUM_FORMS": 1, "authgroup-0-description": "",
"authgroup-0-states": member_state.pk, "authgroup-0-states": f"{member_state.pk}",
"authgroup-0-internal": "on", "authgroup-0-internal": "on",
"authgroup-0-hidden": "on", "authgroup-0-hidden": "on",
"authgroup-0-group": group.pk, "authgroup-0-group": f"{group.pk}",
"authgroup-__prefix__-description": "",
"authgroup-__prefix__-internal": "on",
"authgroup-__prefix__-hidden": "on",
"authgroup-__prefix__-group": f"{group.pk}",
"_save": "Save"
} }
) )
# then # then
@@ -639,85 +644,6 @@ class TestGroupAdmin2(TestCase):
self.assertIn(group, user_member.groups.all()) self.assertIn(group, user_member.groups.all())
self.assertNotIn(group, user_guest.groups.all()) self.assertNotIn(group, user_guest.groups.all())
def test_should_add_user_to_existing_group(self):
# given
user_bruce = AuthUtils.create_user("Bruce Wayne")
user_lex = AuthUtils.create_user("Lex Luthor")
group = Group.objects.create(name="dummy")
user_bruce.groups.add(group)
self.client.force_login(self.superuser)
# when
response = self.client.post(
f"/admin/groupmanagement/group/{group.pk}/change/",
data={
"name": group.name,
"users": [user_bruce.pk, user_lex.pk],
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 1,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
"authgroup-0-internal": "on",
"authgroup-0-hidden": "on",
"authgroup-0-group": group.pk,
}
)
# then
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/groupmanagement/group/")
self.assertIn(group, user_bruce.groups.all())
self.assertIn(group, user_lex.groups.all())
def test_should_remove_user_from_existing_group(self):
# given
user_bruce = AuthUtils.create_user("Bruce Wayne")
user_lex = AuthUtils.create_user("Lex Luthor")
group = Group.objects.create(name="dummy")
user_bruce.groups.add(group)
user_lex.groups.add(group)
self.client.force_login(self.superuser)
# when
response = self.client.post(
f"/admin/groupmanagement/group/{group.pk}/change/",
data={
"name": group.name,
"users": user_bruce.pk,
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 1,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
"authgroup-0-internal": "on",
"authgroup-0-hidden": "on",
"authgroup-0-group": group.pk,
}
)
# then
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/groupmanagement/group/")
self.assertIn(group, user_bruce.groups.all())
self.assertNotIn(group, user_lex.groups.all())
def test_should_include_user_when_creating_group(self):
# given
user_bruce = AuthUtils.create_user("Bruce Wayne")
self.client.force_login(self.superuser)
# when
response = self.client.post(
"/admin/groupmanagement/group/add/",
data={
"name": "new group",
"users": user_bruce.pk,
"authgroup-TOTAL_FORMS": 1,
"authgroup-INITIAL_FORMS": 0,
"authgroup-MIN_NUM_FORMS": 0,
"authgroup-MAX_NUM_FORMS": 1,
}
)
# then
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/groupmanagement/group/")
group = Group.objects.get(name="new group")
self.assertIn(group, user_bruce.groups.all())
class TestReservedGroupNameAdmin(TestCase): class TestReservedGroupNameAdmin(TestCase):
@classmethod @classmethod

View File

@@ -1,7 +1,6 @@
from django.test import RequestFactory, TestCase, override_settings from django.test import RequestFactory, TestCase, override_settings
from django.urls import reverse from django.urls import reverse
from allianceauth.groupmanagement.models import Group, GroupRequest
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
from .. import views from .. import views
@@ -17,7 +16,6 @@ class TestViews(TestCase):
self.factory = RequestFactory() self.factory = RequestFactory()
self.user = AuthUtils.create_user('Peter Parker') self.user = AuthUtils.create_user('Peter Parker')
self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne') self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne')
self.group = Group.objects.create(name="Example group")
# set permissions # set permissions
AuthUtils.add_permission_to_user_by_name( AuthUtils.add_permission_to_user_by_name(
@@ -66,7 +64,7 @@ class TestViews(TestCase):
content = response_content_to_str(response) content = response_content_to_str(response)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn('<a data-toggle="tab" href="#leave">', content) self.assertIn('id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
self.assertIn('<div id="leave" class="tab-pane">', content) self.assertIn('<div id="leave" class="tab-pane">', content)
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True) @override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
@@ -83,21 +81,5 @@ class TestViews(TestCase):
content = response_content_to_str(response) content = response_content_to_str(response)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertNotIn('<a data-toggle="tab" href="#leave">', content) self.assertNotIn('id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
self.assertNotIn('<div id="leave" class="tab-pane">', content) self.assertNotIn('<div id="leave" class="tab-pane">', content)
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
def test_should_not_hide_leave_requests_tab_when_there_are_open_requests(self):
# given
request = self.factory.get(reverse('groupmanagement:management'))
request.user = self.user_with_manage_permission
GroupRequest.objects.create(user=self.user, group=self.group, leave_request=True)
# when
response = views.group_management(request)
# then
content = response_content_to_str(response)
self.assertEqual(response.status_code, 200)
self.assertIn('<a data-toggle="tab" href="#leave">', content)
self.assertIn('<div id="leave" class="tab-pane">', content)

View File

@@ -2,12 +2,13 @@ import logging
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Count from django.db.models import Count
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth.notifications import notify from allianceauth.notifications import notify
@@ -15,6 +16,7 @@ from allianceauth.notifications import notify
from .managers import GroupManager from .managers import GroupManager
from .models import GroupRequest, RequestLog from .models import GroupRequest, RequestLog
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -43,15 +45,10 @@ def group_management(request):
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format( logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
request.user, len(acceptrequests), len(leaverequests))) request.user, len(acceptrequests), len(leaverequests)))
show_leave_tab = (
getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False)
and not GroupRequest.objects.filter(leave_request=True).exists()
)
render_items = { render_items = {
'acceptrequests': acceptrequests, 'acceptrequests': acceptrequests,
'leaverequests': leaverequests, 'leaverequests': leaverequests,
'show_leave_tab': show_leave_tab, 'auto_leave': getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False),
} }
return render(request, 'groupmanagement/index.html', context=render_items) return render(request, 'groupmanagement/index.html', context=render_items)
@@ -90,7 +87,7 @@ def group_membership_audit(request, group_id):
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404("Group does not exist") raise Http404("Group does not exist")
render_items = {'group': group.name} render_items = {'group': group}
entries = RequestLog.objects.filter(group=group).order_by('-date') entries = RequestLog.objects.filter(group=group).order_by('-date')
render_items['entries'] = entries render_items['entries'] = entries
@@ -314,8 +311,10 @@ def groups_view(request):
groups_qs = GroupManager.get_joinable_groups_for_user( groups_qs = GroupManager.get_joinable_groups_for_user(
request.user, include_hidden=False request.user, include_hidden=False
) )
groups_qs = groups_qs.order_by('name') groups_qs = groups_qs.order_by('name').select_related("authgroup").prefetch_related('authgroup__group_leaders', 'authgroup__group_leaders__profile__main_character', 'authgroup__group_leader_groups')
groups = [] groups = []
## TODO see about making this faster
for group in groups_qs: for group in groups_qs:
group_request = GroupRequest.objects\ group_request = GroupRequest.objects\
.filter(user=request.user)\ .filter(user=request.user)\
@@ -325,7 +324,14 @@ def groups_view(request):
'request': group_request[0] if group_request else None 'request': group_request[0] if group_request else None
}) })
context = {'groups': groups} count = 0
perms = GroupManager.can_manage_groups(request.user)
if perms:
count = GroupManager.pending_requests_count_for_user(request.user)
user_groups_list = list(request.user.groups.all())
context = {'groups': groups, "manager_perms": perms, "req_count":count, "user_groups": user_groups_list}
return render(request, 'groupmanagement/groups.html', context=context) return render(request, 'groupmanagement/groups.html', context=context)

View File

@@ -91,7 +91,7 @@ def get_app_modules():
def get_app_submodules(module_name): def get_app_submodules(module_name):
""" """pyt
Get a specific sub module of the app Get a specific sub module of the app
:param module_name: module name to get :param module_name: module name to get
:return: name, module tuple :return: name, module tuple
@@ -122,3 +122,17 @@ def get_hooks(name):
""" """
register_all_hooks() register_all_hooks()
return _hooks.get(name, []) return _hooks.get(name, [])
class DashboardItemHook:
def __init__(self, view_function, order:int=10):
self.view_function = view_function
self.order = order
def render(self, request):
try:
logger.debug(f"Rendering {self.view_function} to dashboard")
return self.view_function(request)
except Exception as e:
logger.exception(f"Rendering {self.view_function} failed!")
return ""

View File

@@ -1,7 +1,8 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth import hooks from allianceauth import hooks
from allianceauth.services.hooks import MenuItemHook, UrlHook from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import UrlHook
from . import urls from . import urls
from .models import Application from .models import Application

View File

@@ -1,4 +1,4 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Choose a Corp" %}{% endblock page_title %} {% block page_title %}{% translate "Choose a Corp" %}{% endblock page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %} {% block page_title %}{% translate "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}

View File

@@ -8,7 +8,7 @@
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Personal Applications" %} <h1 class="page-header text-center">{% translate "Personal Applications" %}
<div class="text-right"> <div class="text-end">
{% if create %} {% if create %}
<a href="{% url 'hrapplications:create_view' %}"> <a href="{% url 'hrapplications:create_view' %}">
<button type="button" class="btn btn-success">{% translate "Create Application" %}</button> <button type="button" class="btn btn-success">{% translate "Create Application" %}</button>
@@ -33,11 +33,11 @@
<td class="text-center">{{ personal_app.form.corp.corporation_name }}</td> <td class="text-center">{{ personal_app.form.corp.corporation_name }}</td>
<td class="text-center"> <td class="text-center">
{% if personal_app.approved == None %} {% if personal_app.approved == None %}
<div class="label label-warning">{% translate "Pending" %}</div> <div class="badge bg-warning">{% translate "Pending" %}</div>
{% elif personal_app.approved == True %} {% elif personal_app.approved == True %}
<div class="label label-success">{% translate "Approved" %}</div> <div class="badge bg-success">{% translate "Approved" %}</div>
{% else %} {% else %}
<div class="label label-danger">{% translate "Rejected" %}</div> <div class="badge bg-danger">{% translate "Rejected" %}</div>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">
@@ -58,7 +58,7 @@
{% endif %} {% endif %}
{% if perms.auth.human_resources %} {% if perms.auth.human_resources %}
<h1 class="page-header text-center">{% translate "Application Management" %} <h1 class="page-header text-center">{% translate "Application Management" %}
<div class="text-right"> <div class="text-end">
<!-- Button trigger modal --> <!-- Button trigger modal -->
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal"> <button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal">
{% translate "Search Applications" %} {% translate "Search Applications" %}
@@ -91,14 +91,14 @@
<td class="text-center"> <td class="text-center">
{% if app.approved == None %} {% if app.approved == None %}
{% if app.reviewer_str %} {% if app.reviewer_str %}
<div class="label label-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div> <div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
{% else %} {% else %}
<div class="label label-warning">{% translate "Pending" %}</div> <div class="badge bg-warning">{% translate "Pending" %}</div>
{% endif %} {% endif %}
{% elif app.approved == True %} {% elif app.approved == True %}
<div class="label label-success">{% translate "Approved" %}</div> <div class="badge bg-success">{% translate "Approved" %}</div>
{% else %} {% else %}
<div class="label label-danger">{% translate "Rejected" %}</div> <div class="badge bg-danger">{% translate "Rejected" %}</div>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">
@@ -135,14 +135,14 @@
<td class="text-center"> <td class="text-center">
{% if app.approved == None %} {% if app.approved == None %}
{% if app.reviewer_str %} {% if app.reviewer_str %}
<div class="label label-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div> <div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
{% else %} {% else %}
<div class="label label-warning">{% translate "Pending" %}</div> <div class="badge bg-warning">{% translate "Pending" %}</div>
{% endif %} {% endif %}
{% elif app.approved == True %} {% elif app.approved == True %}
<div class="label label-success">{% translate "Approved" %}</div> <div class="badge bg-success">{% translate "Approved" %}</div>
{% else %} {% else %}
<div class="label label-danger">{% translate "Rejected" %}</div> <div class="badge bg-danger">{% translate "Rejected" %}</div>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">

View File

@@ -9,7 +9,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
{% if perms.auth.human_resources %} {% if perms.auth.human_resources %}
<h1 class="page-header text-center">{% translate "Application Search Results" %} <h1 class="page-header text-center">{% translate "Application Search Results" %}
<div class="text-right"> <div class="text-end">
<!-- Button trigger modal --> <!-- Button trigger modal -->
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal"> <button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal">
{% translate "Search Applications" %} {% translate "Search Applications" %}
@@ -34,11 +34,11 @@
<td class="text-center">{{ app.form.corp }}</td> <td class="text-center">{{ app.form.corp }}</td>
<td class="text-center"> <td class="text-center">
{% if app.approved == None %} {% if app.approved == None %}
<div class="label label-warning">{% translate "Pending" %}</div> <div class="badge bg-warning">{% translate "Pending" %}</div>
{% elif app.approved == True %} {% elif app.approved == True %}
<div class="label label-success">{% translate "Approved" %}</div> <div class="badge bg-success">{% translate "Approved" %}</div>
{% else %} {% else %}
<div class="label label-danger">{% translate "Rejected" %}</div> <div class="badge bg-danger">{% translate "Rejected" %}</div>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
from django.contrib import admin
from . import models
@admin.register(models.MenuItem)
class MenuItemAdmin(admin.ModelAdmin):
list_display = ['text', 'hide', 'parent', 'url', 'icon_classes', 'rank']
ordering = ('rank',)

19
allianceauth/menu/apps.py Normal file
View File

@@ -0,0 +1,19 @@
import logging
from django.apps import AppConfig
from django.db.utils import ProgrammingError, OperationalError
logger = logging.getLogger(__name__)
class MenuConfig(AppConfig):
name = "allianceauth.menu"
label = "menu"
def ready(self):
try:
logger.debug("Syncing MenuItem Hooks")
from allianceauth.menu.providers import MenuItem
MenuItem.sync_hook_models()
except (ProgrammingError, OperationalError):
logger.warning("Migrations not completed for MenuItems")

View File

@@ -0,0 +1,42 @@
from django.template.loader import render_to_string
from typing import List, Optional
class MenuItemHook:
"""
Auth Hook for generating Side Menu Items
"""
def __init__(self, text: str, classes: str, url_name: str, order: Optional[int] = None, navactive: List = []):
"""
:param text: The text shown as menu item, e.g. usually the name of the app.
:type text: str
:param classes: The classes that should be applied to the menu item icon
:type classes: List[str]
:param url_name: The name of the Django URL to use
:type url_name: str
:param order: An integer which specifies the order of the menu item, lowest to highest. Community apps are free to use any order above `1000`. Numbers below are served for Auth.
:type order: Optional[int], optional
:param navactive: A list of views or namespaces the link should be highlighted on. See [django-navhelper](https://github.com/geelweb/django-navhelper#navactive) for usage. Defaults to the supplied `url_name`.
:type navactive: List, optional
"""
self.text = text
self.classes = classes
self.url_name = url_name
self.template = 'public/menuitem.html'
self.order = order if order is not None else 9999
# count is an integer shown next to the menu item as badge when count != None
# apps need to set the count in their child class, e.g. in render() method
self.count = None
navactive = navactive or []
navactive.append(url_name)
self.navactive = navactive
def render(self, request):
return render_to_string(self.template,
{'item': self},
request=request)

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.2 on 2022-08-28 14:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='MenuItem',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hook_function', models.CharField(max_length=500)),
('icon_classes', models.CharField(max_length=150)),
('text', models.CharField(max_length=150)),
('url', models.CharField(blank=True, default=None, max_length=2048, null=True)),
('rank', models.IntegerField(default=1000)),
('hide', models.BooleanField(default=False)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='menu.menuitem')),
],
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.2 on 2022-08-28 14:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('menu', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='menuitem',
name='hook_function',
field=models.CharField(blank=True, default=None, max_length=500, null=True),
),
migrations.AlterField(
model_name='menuitem',
name='icon_classes',
field=models.CharField(blank=True, default=None, max_length=150, null=True),
),
migrations.AlterField(
model_name='menuitem',
name='text',
field=models.CharField(blank=True, default=None, max_length=150, null=True),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.0.8 on 2023-02-05 07:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('menu', '0002_alter_menuitem_hook_function_and_more'),
]
operations = [
migrations.AddIndex(
model_name='menuitem',
index=models.Index(fields=['rank'], name='menu_menuit_rank_e880ab_idx'),
),
]

View File

@@ -0,0 +1,39 @@
# Generated by Django 4.0.10 on 2023-07-16 11:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('menu', '0003_menuitem_menu_menuit_rank_e880ab_idx'),
]
operations = [
migrations.AlterField(
model_name='menuitem',
name='hide',
field=models.BooleanField(default=False, help_text='Hide this menu item. If this item is a header all items under it will be hidden too.'),
),
migrations.AlterField(
model_name='menuitem',
name='icon_classes',
field=models.CharField(blank=True, default=None, help_text='Font Awesome classes to show as icon on menu', max_length=150, null=True),
),
migrations.AlterField(
model_name='menuitem',
name='parent',
field=models.ForeignKey(blank=True, help_text='Parent Header. (Optional)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='menu.menuitem'),
),
migrations.AlterField(
model_name='menuitem',
name='rank',
field=models.IntegerField(default=1000, help_text='Order of the menu. Lowest First.'),
),
migrations.AlterField(
model_name='menuitem',
name='text',
field=models.CharField(blank=True, default=None, help_text='Text to show on menu', max_length=150, null=True),
),
]

View File

174
allianceauth/menu/models.py Normal file
View File

@@ -0,0 +1,174 @@
import logging
from allianceauth.hooks import get_hooks
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
logger = logging.getLogger(__name__)
class MenuItem(models.Model):
# Auto Generated model from an auth_hook
hook_function = models.CharField(
max_length=500, default=None, null=True, blank=True)
# User Made Model
icon_classes = models.CharField(
max_length=150, default=None, null=True, blank=True, help_text="Font Awesome classes to show as icon on menu")
text = models.CharField(
max_length=150, default=None, null=True, blank=True, help_text="Text to show on menu")
url = models.CharField(max_length=2048, default=None,
null=True, blank=True)
# Put it under a header?
parent = models.ForeignKey(
'self', on_delete=models.SET_NULL, null=True, blank=True, help_text="Parent Header. (Optional)")
# Put it where? lowest first
rank = models.IntegerField(default=1000, help_text="Order of the menu. Lowest First.")
# Hide it fully? Hiding a parent will hide all it's children
hide = models.BooleanField(default=False, help_text="Hide this menu item. If this item is a header all items under it will be hidden too.")
class Meta:
indexes = [
models.Index(fields=['rank', ]),
]
def __str__(self) -> str:
return self.text
@property
def classes(self): # Helper function to make this model closer to the hook functions
return self.icon_classes
@staticmethod
def hook_to_name(mh):
return f"{mh.__class__.__module__}.{mh.__class__.__name__}"
@staticmethod
def sync_hook_models():
# TODO define aa way for hooks to predefine a "parent" to create a sub menu from modules
menu_hooks = get_hooks('menu_item_hook')
hook_functions = []
for hook in menu_hooks:
mh = hook()
cls = MenuItem.hook_to_name(mh)
try:
# if it exists update the text only
# Users can adjust ranks so lets not change it if they have.
mi = MenuItem.objects.get(hook_function=cls)
mi.text = getattr(mh, "text", mh.__class__.__name__)
mi.save()
except MenuItem.DoesNotExist:
# This is a new hook, Make the database model.
MenuItem.objects.create(
hook_function=cls,
rank=getattr(mh, "order", 500),
text=getattr(mh, "text", mh.__class__.__name__)
)
hook_functions.append(cls)
# Get rid of any legacy hooks from modules removed
MenuItem.objects.filter(hook_function__isnull=False).exclude(
hook_function__in=hook_functions).delete()
@classmethod
def filter_items(cls, menu_item: dict):
"""
filter any items with no valid children from a menu
"""
count_items = len(menu_item['items'])
if count_items: # if we have children confirm we can see them
for i in menu_item['items']:
if len(i['render']) == 0:
count_items -= 1
if count_items == 0: # no children left dont render header
return False
return True
else:
return True
@classmethod
def render_menu(cls, request):
"""
Return the sorted side menu items with any items the user can't see removed.
"""
# Override all the items to the bs5 theme
template = "menu/menu-item-bs5.html"
# TODO discuss permissions for user defined links
# Turn all the hooks into functions
menu_hooks = get_hooks('menu_item_hook')
items = {}
for fn in menu_hooks:
f = fn()
items[cls.hook_to_name(f)] = f
menu_items = MenuItem.objects.all().order_by("rank")
menu = {}
for mi in menu_items:
if mi.hide:
# hidden item, skip it completely
continue
try:
_cnt = 0
_render = None
if mi.hook_function:
# This is a module hook, so we need to render it as the developer intended
# TODO add a new attribute for apps that want to override it in the new theme
items[mi.hook_function].template = template
_render = items[mi.hook_function].render(request)
_cnt = items[mi.hook_function].count
else:
# This is a user defined menu item so we render it with defaults.
_render = render_to_string(template,
{'item': mi},
request=request)
parent = mi.id
if mi.parent_id: # Set it if present
parent = mi.parent_id
if parent not in menu: # this will cause the menu headers to be out of order
menu[parent] = {"items": [],
"count": 0,
"render": None,
"text": "None",
"rank": 9999,
}
_mi = {
"count": _cnt,
"render": _render,
"text": mi.text,
"rank": mi.rank,
"classes": (mi.icon_classes if mi.icon_classes != "" else "fas fa-folder"),
"hide": mi.hide
}
if parent != mi.id:
# this is a sub item
menu[parent]["items"].append(_mi)
if _cnt:
#add its count to the header count
menu[parent]["count"] += _cnt
else:
if len(menu[parent]["items"]):
# this is a top folder dont update the count.
del(_mi["count"])
menu[parent].update(_mi)
except Exception as e:
logger.exception(e)
# reset to list
menu = list(menu.values())
# sort the menu list as the parents may be out of order.
menu.sort(key=lambda i: i['rank'])
# ensure no empty groups
menu = filter(cls.filter_items, menu)
return menu

View File

@@ -0,0 +1,10 @@
from django.template.loader import render_to_string
from allianceauth.hooks import get_hooks
from .models import MenuItem
class MenuProvider():
def __init__(self) -> None:
pass

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