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:
<<: *only-default
stage: pre-commit
image: python:3.10-bullseye
image: python:3.11-bullseye
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
@@ -112,6 +112,19 @@ test-pvpy-core:
path: coverage.xml
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:
<<: *only-default
image: python:3.8-bullseye
@@ -174,9 +187,22 @@ test-pvpy-all:
path: coverage.xml
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:
stage: test
image: python:3.10-bullseye
image: python:3.11-bullseye
before_script:
- python -m pip install --upgrade pip
@@ -195,13 +221,13 @@ build-test:
test-docs:
<<: *only-default
image: python:3.10-bullseye
image: python:3.11-bullseye
script:
- tox -e docs
- tox -e docs
deploy_production:
stage: deploy
image: python:3.10-bullseye
image: python:3.11-bullseye
before_script:
- python -m pip install --upgrade pip
@@ -217,10 +243,10 @@ deploy_production:
build-image:
before_script: []
image: docker:20.10.10
image: docker:24.0
stage: docker
services:
- docker:20.10.10-dind
- docker:24.0-dind
script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
@@ -230,12 +256,10 @@ build-image:
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
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 tag $IMAGE_TAG $CURRENT_TAG
docker tag $IMAGE_TAG $MINOR_TAG
docker tag $IMAGE_TAG $MAJOR_TAG
docker tag $IMAGE_TAG $LATEST_TAG
docker image push --all-tags $CI_REGISTRY_IMAGE/auth
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder
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-)
rules:
- if: $CI_COMMIT_TAG
when: delayed
@@ -243,17 +267,19 @@ build-image:
build-image-dev:
before_script: []
image: docker:20.10.10
image: docker:24.0
stage: docker
services:
- docker:20.10.10-dind
- docker:24.0-dind
script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
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 push $IMAGE_TAG
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
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:
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
when: manual
@@ -262,17 +288,19 @@ build-image-dev:
build-image-mr:
before_script: []
image: docker:20.10.10
image: docker:24.0
stage: docker
services:
- docker:20.10.10-dind
- docker:24.0-dind
script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA
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 push $IMAGE_TAG
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
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:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual

View File

@@ -66,14 +66,20 @@ repos:
swagger\.json
)
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [ --target-version=4.0 ]
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
hooks:
- id: pyupgrade
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
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs

View File

@@ -1,5 +1,5 @@
[main]
host = https://app.transifex.com
host = https://www.transifex.com
lang_map = zh-Hans: zh_Hans
[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
# Django starts so that shared_task will use this app.
__version__ = '3.8.0'
__version__ = '4.0.0a4'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}'

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
from .models import AnalyticsIdentifier, AnalyticsTokens
@admin.register(AnalyticsIdentifier)
@@ -13,9 +13,3 @@ class AnalyticsIdentifierAdmin(admin.ModelAdmin):
class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ]
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):
name = 'allianceauth.analytics'
label = 'analytics'
def ready(self):
import allianceauth.analytics.signals

View File

@@ -3,11 +3,10 @@
"model": "analytics.AnalyticsTokens",
"pk": 1,
"fields": {
"name": "AA Team Public Google Analytics (Universal)",
"name": "AA Team Public Google Analytics (V4)",
"type": "GA-V4",
"token": "UA-186249766-2",
"send_page_views": "False",
"send_celery_tasks": "False",
"token": "G-6LYSMYK8DE",
"secret": "KLlpjLZ-SRGozS5f5wb_kw",
"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):
identifier = models.UUIDField(default=uuid4,
editable=False)
identifier = models.UUIDField(
default=uuid4,
editable=False)
def save(self, *args, **kwargs):
if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object
raise ValidationError('There is can be only one \
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)
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 Analytics_Type(models.TextChoices):
@@ -32,7 +29,5 @@ class AnalyticsTokens(models.Model):
name = models.CharField(max_length=254)
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
token = models.CharField(max_length=254, blank=False)
send_page_views = models.BooleanField(default=False)
send_celery_tasks = models.BooleanField(default=False)
secret = models.CharField(max_length=254, blank=True)
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.apps import apps
from celery import shared_task
from allianceauth import __version__
from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import (
install_stat_addons,
@@ -12,14 +11,14 @@ from .utils import (
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"
COLLECTION_URL = f"{BASE_URL}collect"
DEBUG_URL = f"{BASE_URL}/debug/mp/collect"
COLLECTION_URL = f"{BASE_URL}/mp/collect"
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
# Force sending of analytics data during in a debug/test environemt
# Usefull for developers working on this feature.
# Force sending of analytics data during in a debug/test environment
# Useful for developers working on this feature.
logger.warning(
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
"This debug instance will send analytics data!")
@@ -31,40 +30,38 @@ if settings.DEBUG is True:
ANALYTICS_URL = DEBUG_URL
def analytics_event(category: str,
action: str,
label: str,
value: int = 0,
def analytics_event(namespace: str,
task: str,
label: str = "",
result: str = "",
value: int = 1,
event_type: str = 'Celery'):
"""
Send a Google Analytics Event for each token stored
Includes check for if its enabled/disabled
Args:
`category` (str): Celery Namespace
`action` (str): Task Name
`label` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a binary True/False
`namespace` (str): Celery Namespace
`task` (str): Task Name
`label` (str): Optional, additional task label
`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
"""
analyticstokens = AnalyticsTokens.objects.all()
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
for token in analyticstokens:
if event_type == 'Celery':
allowed = token.send_celery_tasks
elif event_type == 'Stats':
for token in AnalyticsTokens.objects.filter(type='GA-V4'):
if event_type == 'Stats':
allowed = token.send_stats
else:
allowed = False
if allowed is True:
tracking_id = token.token
send_ga_tracking_celery_event.s(
tracking_id=tracking_id,
client_id=client_id,
category=category,
action=action,
measurement_id=token.token,
secret=token.secret,
namespace=namespace,
task=task,
label=label,
result=result,
value=value).apply_async(priority=9)
@@ -72,136 +69,104 @@ def analytics_event(category: str,
def analytics_daily_stats():
"""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()
tokens = install_stat_tokens()
addons = install_stat_addons()
logger.debug("Running Daily Analytics Upload")
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='existence',
value=1,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='users',
value=users,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='tokens',
value=tokens,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='addons',
value=addons,
event_type='Stats')
for appconfig in apps.get_app_configs():
analytics_event(category='allianceauth.analytics',
action='send_extension_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_extension_stats',
label=appconfig.label,
value=1,
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()
def send_ga_tracking_celery_event(
tracking_id: str,
client_id: str,
category: str,
action: str,
label: str,
value: int) -> requests.Response:
measurement_id: str,
secret: str,
namespace: str,
task: str,
label: str = "",
result: str = "",
value: int = 1):
"""Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware
Sends an events to GA
Parameters
----------
`tracking_id` (str): Unique Server Identifier
`client_id` (str): GA Token
`category` (str): Celery Namespace
`action` (str): Task Name
`label` (str): Optional, Task Success/Exception
`measurement_id` (str): GA Token
`secret` (str): GA Authentication Secret
`namespace` (str): Celery Namespace
`task` (str): Task Name
`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
Returns
-------
requests.Reponse Object
"""
headers = {
"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"}
parameters = {
'measurement_id': measurement_id,
'api_secret': secret
}
payload = {
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'event',
'ec': category,
'ea': action,
'el': label,
'ev': value,
'aip': 1,
'an': "allianceauth",
'av': __version__
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
"user_properties": {
"allianceauth_version": {
"value": "allianceauth_version"
}
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
return response
},
'non_personalized_ads': True,
"events": [{
"name": "celery_event",
"params": {
"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):
AnalyticsIdentifier.objects.create(identifier=uuid_2)
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 (
analytics_event,
send_ga_tracking_celery_event,
send_ga_tracking_web_view)
send_ga_tracking_celery_event)
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)
@@ -18,195 +17,53 @@ class TestAnalyticsTasks(NoSocketsTestCase):
def test_analytics_event(self, requests_mocker):
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
analytics_event(
category='allianceauth.analytics',
action='send_tests',
label='test',
value=1,
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'}]
namespace='allianceauth.analytics',
task='send_tests',
label='test',
value=1,
result="Success",
event_type='Stats')
def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
# given
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
token = 'G-6LYSMYK8DE'
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
category = 'test'
action = 'test'
label = 'test'
value = '1'
# when
response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value)
task = send_ga_tracking_celery_event(
token,
secret,
category,
action,
label,
value)
# then
self.assertEqual(response.status_code, 200)
self.assertEqual(task, 200)
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
# given
requests_mocker.register_uri(
'POST',
GOOGLE_ANALYTICS_DEBUG_URL,
json={"hitParsingResult":[{'valid': True}]}
json={"validationMessages": []}
)
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
token = 'G-6LYSMYK8DE'
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
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()
task = send_ga_tracking_celery_event(
token,
secret,
category,
action,
label,
value)
# then
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
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."
)
self.assertTrue(task, 200)

View File

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

View File

@@ -1,5 +1,30 @@
from django.apps import AppConfig
from django.core.checks import Warning, Error, register
class AllianceAuthConfig(AppConfig):
name = 'allianceauth'
@register()
def check_settings(app_configs, **kwargs):
from django.conf import settings
errors = []
if hasattr(settings, "SITE_URL"):
if settings.SITE_URL[-1] == "/":
errors.append(Warning(
"'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth."))
else:
errors.append(Error(
"No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules."))
if hasattr(settings, "CSRF_TRUSTED_ORIGINS"):
if hasattr(settings, "SITE_URL"):
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
errors.append(Warning(
"'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified."))
else:
errors.append(Error(
"No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified"))
return errors

View File

@@ -288,7 +288,7 @@ class UserAdmin(BaseUserAdmin):
Behavior of groups and characters columns can be configured via settings
"""
inlines = BaseUserAdmin.inlines + [UserProfileInline]
inlines = [UserProfileInline]
ordering = ('username', )
list_select_related = ('profile__state', 'profile__main_character')
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 typing import Callable, Iterable, Optional

View File

@@ -31,6 +31,7 @@ class UserSettingsMiddleware(MiddlewareMixin):
except Exception as e:
logger.exception(e)
# AA v3 NIGHT_MODE
# Set our Night mode flag from the DB
# Null = hasnt been set by the user ever, dont act.
#
@@ -42,4 +43,13 @@ class UserSettingsMiddleware(MiddlewareMixin):
except Exception as 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

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"),
blank=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):
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 %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
{% block header_nav_brand %}
{% translate "Dashboard" %}
{% endblock %}
{% block content %}
<h1 class="page-header text-center">{% translate "Dashboard" %}</h1>
{% if user.is_staff %}
{% include 'allianceauth/admin-status/include.html' %}
{% endif %}
<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 class="d-flex justify-content-around align-self-center flex-wrap">
{% for dash in views %}
{{ dash | safe }}
{% endfor %}
</div>
{% endblock %}

View File

@@ -1,16 +1,16 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
{% block page_title %}{% translate "Dashboard" %}{% endblock page_title %}
{% block content %}
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
<div class="col-sm-12">
<table class="table table-aa" id="table_tokens" style="width:100%">
<div>
<table class="table table-aa" id="table_tokens" style="width: 100%;">
<thead>
<tr>
<th>{% translate "Scopes" %}</th>
<th class="text-right">{% translate "Actions" %}</th>
<th class="text-end">{% translate "Actions" %}</th>
<th>{% translate "Character" %}</th>
</tr>
@@ -18,24 +18,27 @@
<tbody>
{% for t in tokens %}
<tr>
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{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>{{t.character_name}}</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-end">
<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>
{% endfor %}
</tbody>
</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 %}
</div>
{% endblock %}
{% endblock content %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% endblock %}
{% include "bundles/datatables-js-bs5.html" %}
{% endblock extra_javascript %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% include "bundles/datatables-css-bs5.html" %}
{% endblock extra_css %}
{% block extra_script %}
$(document).ready(function(){
@@ -59,4 +62,4 @@
"stateSave": true,
});
});
{% endblock %}
{% endblock extra_script %}

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" 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: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.">
@@ -30,7 +31,7 @@
}
.panel-transparent {
background: rgba(48, 48, 48, 0.7);
background: rgba(48 48 48 / 0.7);
color: #ffffff;
padding-bottom: 21px;
}

View File

@@ -1,4 +1,5 @@
import logging
from allianceauth.hooks import get_hooks
from django_registration.backends.activation.views import (
REGISTRATION_SALT, ActivationView as BaseActivationView,
@@ -42,23 +43,51 @@ def index(request):
return redirect('authentication:dashboard')
@login_required
def dashboard(request):
def dashboard_groups(request):
groups = request.user.groups.all()
if _has_auto_groups:
groups = groups\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)
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\
.filter(character_ownership__user=request.user)\
.select_related()\
.order_by('character_name')
context = {
'groups': groups,
'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)

View File

@@ -12,13 +12,14 @@ class StartProject(BaseStartProject):
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('--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):
# Validate args
if len(args) < 2:
parser.error("Please specify a name for your Alliance Auth installation.")
elif len(args) > 3:
elif len(args) > 4:
parser.error("Too many arguments.")
# First find the path to Alliance Auth
@@ -32,6 +33,7 @@ def create_project(parser, options, args):
'python': shutil.which('python'),
'gunicorn': shutil.which('gunicorn'),
'celery': shutil.which('celery'),
'memmon': shutil.which('memmon'),
'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 allianceauth import hooks
from allianceauth.corputils import urls

View File

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

View File

@@ -3,177 +3,175 @@
{% load humanize %}
{% block member_data %}
{% if corpstats %}
<div class="row">
<div class="col-lg-12 text-center">
<table class="table">
<tr>
<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 }}">
</td>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6">
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
</td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
<div>
<table class="table">
<tr>
<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 }}">
</td>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6">
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
</td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<ul class="nav nav-pills pull-left">
<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="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
</ul>
<div class="pull-right hidden-xs">
{% 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">
<span class="glyphicon glyphicon-refresh"></span>
</a>
</div>
<div class="clearfix"></div>
<div>
<div class="panel panel-default">
<div class="panel-heading">
<ul class="nav nav-pills pull-left">
<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="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
</ul>
<div class="pull-right hidden-xs">
{% 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">
<span class="glyphicon glyphicon-refresh"></span>
</a>
</div>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade in active" id="mains">
{% if mains %}
<div class="table-responsive">
<table class="table table-hover" id="table-mains">
<thead>
<div class="clearfix"></div>
</div>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade in active" id="mains">
{% 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>
<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>
<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>
<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>
</td>
<td>
<table class="table table-hover">
{% for alt in main.alts %}
{% if forloop.first %}
<tr>
<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 %}
</div>
</td>
<td>
<table class="table table-hover">
{% for alt in main.alts %}
{% if forloop.first %}
<tr>
<td class="text-center" style="width:5%">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
</div>
</td>
<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>
<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>
{% endfor %}
</table>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="members">
{% if members %}
<div class="table-responsive">
<table class="table table-hover" id="table-members">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
<th class="text-center">{% translate "Main Character" %}</th>
<th class="text-center">{% translate "Main Corporation" %}</th>
<th class="text-center">{% translate "Main Alliance" %}</th>
{% endif %}
<tr>
<td class="text-center" style="width:5%">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
</div>
</td>
<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="badge bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
<td class="text-center">{{ member }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-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>
{% endfor %}
{% 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="label label-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td class="text-center"></td>
<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>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="members">
{% if members %}
<div class="table-responsive">
<table class="table table-hover" id="table-members">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
<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>
</thead>
<tbody>
{% for member in members %}
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
<td class="text-center">{{ member }}</td>
<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>
</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="label label-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endfor %}
{% 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>
<td class="text-center"></td>
<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>
<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>
@@ -181,12 +179,15 @@
</div>
{% endif %}
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function(){
$('#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">{{ result.1.character_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.corporation_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)
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):
obj = AutogroupsConfig.objects.create()
@@ -172,7 +172,7 @@ class AutogroupsConfigTestCase(TestCase):
group_qs = Group.objects.filter(pk=group.pk)
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):
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 %}
{% block page_title %}Evelinks examples{% endblock %}
{% block page_title %}Evelinks Examples{% endblock page_title %}
{% block content %}
@@ -25,60 +25,57 @@ Needs to be called with a context containing three objects:
<div class="col-md-4">
<h3>evewho</h3>
<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_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_character|evewho_alliance_url}}">alliance 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_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_character|evewho_alliance_url }}">alliance from character object</a></p>
</div>
<div class="col-md-4">
<h3>dotlan</h3>
<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_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="{{ '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="{{ 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_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="{{ '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>
</div>
<div class="col-md-4">
<h3>zkillboard</h3>
<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_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_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="{{ 30002813|zkillboard_solar_system_url}}">solar sytem from ID</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_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_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="{{ 30002813|zkillboard_solar_system_url }}">solar sytem from ID</a></p>
</div>
</div>
</div>
<h2>image URLs</h2>
<div class="rows">
<div class="col-md-4">
<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 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>
</div>
<div class="col-md-4">
<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 corporation object: <img src="{{ my_corporation|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 corporation object: <img src="{{ my_corporation|corporation_logo_url:128 }}"></p>
</div>
<div class="col-md-4">
<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 alliance object: <img src="{{ my_alliance|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 alliance object: <img src="{{ my_alliance|alliance_logo_url:128 }}"></p>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock content %}

View File

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

View File

@@ -2,7 +2,6 @@
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
@@ -15,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='fatlink',
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 %}
{% block page_title %}{% translate "Fleet Participation" %}{% endblock %}
{% block page_title %}
{% translate "Fleet Participation" %}
{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1>
<div class="col-lg-12 container" id="example">
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">{{ character_name }}</div>
<div class="panel-body">
<div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
</div>
<div class="col-lg-10 col-sm-2">
<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." %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1>
<div class="col-lg-12 container" id="example">
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">{{ character_name }}</div>
<div class="panel-body">
<div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
</div>
<div class="col-lg-10 col-sm-2">
<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." %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,99 +1,107 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load i18n %}
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
{% block page_title %}
{% translate "Fatlink view" %}
{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Participation data" %}</h1>
<table class="table">
<tr>
<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">
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th class="col-md-10">
<h4><b>{% translate "Most recent fatlinks" %}</b>
<h4>
<b>{% translate "Most recent clicked fatlinks" %}</b>
</h4>
</th>
<th class="col-md-1">
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info">
{% translate "View statistics" %}
</a>
</th>
<th class="col-md-1">
<a href="{% url 'fatlink:create' %}" class="btn btn-success">
{% translate "Create fatlink" %}
</a>
<th class="col-md-2 align-self-end">
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info"><i class="fa-solid fa-circle-info fa-fw"></i>{% translate "Personal statistics" %}</a>
</th>
</tr>
</table>
{% if fatlinks %}
<table class="table">
<tr>
<th class="text-center">{% translate "Name" %}</th>
<th class="text-center">{% translate "Creator" %}</th>
<th class="text-center">{% translate "Fleet" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th>
<th class="text-center">{% translate "Duration" %}</th>
<th class="text-center">{% translate "Edit" %}</th>
</tr>
{% for link in fatlinks %}
<tr>
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-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">
<span class="glyphicon glyphicon-edit"></span>
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
{% if fats %}
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
<th scope="col" class="text-center">{% translate "Character" %}</th>
<th scope="col" class="text-center">{% translate "System" %}</th>
<th scope="col" class="text-center">{% translate "Ship" %}</th>
<th scope="col" 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">{% translate "Docked in" %} {{ 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>
</div>
{% 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 %}
{% 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 %}
</div>
{% endblock content %}

View File

@@ -1,6 +1,7 @@
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 . import urls
@@ -33,11 +34,43 @@ class GroupManagementMenuItem(MenuItemHook):
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")
def register_menu():
def register_manager_menu():
return GroupManagementMenuItem()
@hooks.register("menu_item_hook")
def register_groups_menu():
return GroupsMenuItem()
@hooks.register("url_hook")
def register_urls():
return UrlHook(urls, "group", r"^groups/")

View File

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

View File

@@ -1,7 +1,8 @@
from typing import Set
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.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -13,7 +14,7 @@ from allianceauth.notifications import notify
class GroupRequest(models.Model):
"""Request from a user for joining or leaving a group."""
leave_request = models.BooleanField(default=False)
leave_request = models.BooleanField(default=0)
user = models.ForeignKey(User, 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)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
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)
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 navactive %}
{% 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 %}
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
{% if entries %}
<div class="table-responsive">
<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">
<div class="panel-heading">
{{ group }} - {% translate "Audit Log" %}
</div>
<tbody>
{% for entry in entries %}
<tr>
<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">
<p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% translate "Back" %}
</a>
</p>
{% if entry.request_type is None %}
<td>{% translate "Removed" %}</td>
{% else %}
<td>{{ entry.action_to_str }}</td>
{% endif %}
{% if entries %}
<div class="table-responsive">
<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>
<td>{{ entry.request_actor }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<tbody>
{% for entry in entries %}
<tr>
<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>
<p class="text-muted">
{% translate "All times displayed are EVE/UTC." %}
</p>
</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 %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% include 'bundles/datatables-js-bs5.html' %}
{% include 'bundles/moment-js.html' with locale=True %}
{% include 'bundles/filterdropdown-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% include 'bundles/datatables-css-bs5.html' %}
{% endblock %}
{% block extra_script %}

View File

@@ -1,97 +1,86 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %}
{% load evelinks %}
{% load navactive %}
{% 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 %}
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
{% if group.user_set %}
<div class="table-responsive">
<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">
<div class="panel-heading">
{{ group.name }} - {% translate 'Members' %}
</div>
<tbody>
{% for member in members %}
<tr>
<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">
<p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% translate "Back" %}
</a>
</p>
{% if member.is_leader %}
<i class="fa-solid fa-star"> title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>&nbsp;
{% endif %}
</td>
{% if group.user_set %}
<div class="table-responsive">
<table class="table table-aa" id="tab_group_members">
<thead>
<tr>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td>
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-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 %}
{% 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>
<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-end">
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
<i class="fa-solid fa-xmark"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-muted">
<i class="fa-solid fa-star"></i>: {% translate "Group leader" %}
</p>
</div>
</div>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No group members to list." %}
</div>
{% endif %}
{% endblock content %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% include 'bundles/datatables-js-bs5.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% include 'bundles/datatables-css-bs5.html' %}
{% endblock %}
{% block extra_script %}

View File

@@ -1,88 +1,86 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %}
{% load navactive %}
{% 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 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 %}
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
{% 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;" class="text-center">{% translate "Member Count" %}</th>
<th style="min-width: 170px;"></th>
</tr>
</thead>
<div class="panel panel-default">
<div class="panel-heading">
{% translate "Groups" %}
</div>
<tbody class="align-middle">
{% for group in groups %}
<tr>
<td>
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
</td>
<div class="panel-body">
{% 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>
<td>{{ group.authgroup.description|linebreaks|urlize }}</td>
<tbody>
{% for group in groups %}
<tr>
<td>
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
</td>
<td>
{% if group.authgroup.hidden %}
<span class="badge bg-info">{% translate "Hidden" %}</span>
{% endif %}
{% if group.authgroup.open %}
<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>
{% if group.authgroup.hidden %}
<span class="label label-info">{% translate "Hidden" %}</span>
{% elif group.authgroup.open %}
<span class="label label-success">{% translate "Open" %}</span>
{% else %}
<span class="label label-default">{% translate "Requestable" %}</span>
{% endif %}
</td>
<td class="text-end">
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
<i class="far fa-eye"></i>
</a>
<td class="text-right">
{{ group.num_members }}
</td>
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
<i class="far fa-list-alt"></i>
</a>
<td class="text-right">
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
<i class="glyphicon glyphicon-eye-open"></i>
</a>
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
<i class="glyphicon glyphicon-list-alt"></i>
</a>
<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>
<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" %}">
<i class="far fa-clipboard"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No groups to list." %}
</div>
{% endif %}
{% endblock content %}
{% block extra_javascript %}
{% include 'bundles/clipboard-js.html' %}
{% include "bundles/clipboard-js.html" %}
<script>
new ClipboardJS('#clipboard-copy');
</script>
{% endblock %}
{% endblock extra_javascript %}

View File

@@ -1,62 +1,93 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %}
{% 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 %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Available Groups" %}</h1>
{% if groups %}
<table class="table table-aa">
<thead>
<tr>
<th>{% translate "Name" %}</th>
<th>{% translate "Description" %}</th>
<th></th>
</tr>
</thead>
{% if groups %}
<table class="table" id="groupsTable" >
<thead>
<tr>
<th>{% translate "Name" %}</th>
<th>{% translate "Description" %}</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></th>
</tr>
</thead>
<tbody>
{% for g in groups %}
<tr>
<td>{{ g.group.name }}</td>
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
<td class="text-right">
{% if g.group in user.groups.all %}
{% if not g.request %}
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
{% translate "Leave" %}
</a>
{% else %}
<button type="button" class="btn btn-primary" disabled>
{% translate "Pending" %}
</button>
{% endif %}
{% elif not g.request %}
{% if g.group.authgroup.open %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
{% translate "Join" %}
</a>
{% else %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
{% translate "Request" %}
</a>
{% endif %}
<tbody class>
{% for g in groups %}
<tr>
<td>{{ g.group.name }}</td>
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
<td style="max-width: 30%;">
{% if g.group.authgroup.group_leaders.all.count %}
{% 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 %}
{% endif %}
{% if g.group.authgroup.group_leaders.all.count %}
{% for group in g.group.authgroup.group_leader_groups.all %}<span class="badge bg-secondary">{{group.name}}</span>{% endfor %}
{% endif %}
</td>
<td class="text-end">
{% if g.group in user_groups %}
{% if not g.request %}
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
{% translate "Leave" %}
</a>
{% 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 %}
</div>
{% elif not g.request %}
{% if g.group.authgroup.open %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
{% translate "Join" %}
</a>
{% else %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
{% translate "Request" %}
</a>
{% endif %}
{% 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 %}
{% 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 evelinks %}
{% load navactive %}
{% block page_title %}{% translate "Groups Management" %}{% endblock page_title %}
{% block header_nav_brand %}{% translate "Groups Management" %}{% endblock header_nav_brand %}
{% 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 %}
{% block content %}
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
{% block header_nav_collapse_left %}
<li class="active">
<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">
{% translate "Join Requests" %}
<ul class="nav nav-tabs">
<li class="active">
<a data-toggle="tab" href="#add">
{% translate "Join Requests" %}
{% if acceptrequests %}
<span class="badge bg-secondary">{{ acceptrequests|length }}</span>
{% endif %}
</a>
</li>
{% if acceptrequests %}
<span class="badge">{{ acceptrequests|length }}</span>
{% endif %}
</a>
</li>
{% if not auto_leave %}
<li>
<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">
{% translate "Leave Requests" %}
{% if not show_leave_tab %}
<li>
<a data-toggle="tab" href="#leave">
{% translate "Leave Requests" %}
{% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>
{% if leaverequests %}
<span class="badge bg-secondary">{{ leaverequests|length }}</span>
{% 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">
<div class="panel-body">
<div class="tab-content">
{% endblock %}
<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>
{% for acceptrequest in acceptrequests %}
<tr>
<td>
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
{{ acceptrequest.main_char.character_name }}
</a>
{% else %}
{{ acceptrequest.user.username }}
{% endif %}
</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-right">
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
{% translate "Accept" %}
</a>
{% block content %}
<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>
<a href="{% url 'groupmanagement:reject_request' acceptrequest.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 add requests." %}</div>
{% endif %}
</div>
<tbody class="align-middle">
{% for acceptrequest in acceptrequests %}
<tr>
<td>
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
{{ acceptrequest.main_char.character_name }}
</a>
{% else %}
{{ acceptrequest.user.username }}
{% endif %}
</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 %}
<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>
{% 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>
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
{% translate "Reject" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="alert alert-warning text-center">{% translate "No group add requests." %}</div>
{% endif %}
</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 %}

View File

@@ -1,27 +1,10 @@
{% load i18n %}
{% 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">
<ul class="nav navbar-nav">
<li class="{% navactive request 'groupmanagement:management' %}">
<a href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
</li>
<li class="{% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}">
<a href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
</li>
</ul>
</div>
</div>
</nav>
<li class="nav-item ">
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
</li>
<li class="nav-item ">
<a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
</li>

View File

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

View File

@@ -1,7 +1,6 @@
from django.test import RequestFactory, TestCase, override_settings
from django.urls import reverse
from allianceauth.groupmanagement.models import Group, GroupRequest
from allianceauth.tests.auth_utils import AuthUtils
from .. import views
@@ -17,7 +16,6 @@ class TestViews(TestCase):
self.factory = RequestFactory()
self.user = AuthUtils.create_user('Peter Parker')
self.user_with_manage_permission = AuthUtils.create_user('Bruce Wayne')
self.group = Group.objects.create(name="Example group")
# set permissions
AuthUtils.add_permission_to_user_by_name(
@@ -66,7 +64,7 @@ class TestViews(TestCase):
content = response_content_to_str(response)
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)
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
@@ -83,21 +81,5 @@ class TestViews(TestCase):
content = response_content_to_str(response)
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)
@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.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.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Count
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 allianceauth.notifications import notify
@@ -15,6 +16,7 @@ from allianceauth.notifications import notify
from .managers import GroupManager
from .models import GroupRequest, RequestLog
logger = logging.getLogger(__name__)
@@ -43,15 +45,10 @@ def group_management(request):
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
request.user, len(acceptrequests), len(leaverequests)))
show_leave_tab = (
getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False)
and not GroupRequest.objects.filter(leave_request=True).exists()
)
render_items = {
'acceptrequests': acceptrequests,
'leaverequests': leaverequests,
'show_leave_tab': show_leave_tab,
'auto_leave': getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False),
}
return render(request, 'groupmanagement/index.html', context=render_items)
@@ -90,7 +87,7 @@ def group_membership_audit(request, group_id):
except ObjectDoesNotExist:
raise Http404("Group does not exist")
render_items = {'group': group.name}
render_items = {'group': group}
entries = RequestLog.objects.filter(group=group).order_by('-date')
render_items['entries'] = entries
@@ -314,8 +311,10 @@ def groups_view(request):
groups_qs = GroupManager.get_joinable_groups_for_user(
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 = []
## TODO see about making this faster
for group in groups_qs:
group_request = GroupRequest.objects\
.filter(user=request.user)\
@@ -325,7 +324,14 @@ def groups_view(request):
'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)

View File

@@ -91,7 +91,7 @@ def get_app_modules():
def get_app_submodules(module_name):
"""
"""pyt
Get a specific sub module of the app
:param module_name: module name to get
:return: name, module tuple
@@ -122,3 +122,17 @@ def get_hooks(name):
"""
register_all_hooks()
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 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 .models import Application

View File

@@ -1,4 +1,4 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load i18n %}
{% 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 %}
{% block page_title %}{% translate "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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