Compare commits

..

10 Commits

Author SHA1 Message Date
Ariel Rin
9ce1939040 Version Bump 3.8.0 2023-11-09 00:00:28 +10:00
Ariel Rin
322131cd4f Update source language strings 2023-11-08 23:56:16 +10:00
Ariel Rin
55e6e92da5 Merge branch 'transifex' into 'master'
Update from Transifex

See merge request allianceauth/allianceauth!1562
2023-11-08 13:48:26 +00:00
Ariel Rin
e5d29629a5 Update from Transifex 2023-11-08 13:48:26 +00:00
Ariel Rin
26e187e4c8 Merge branch 'assign-users-on-group-form' into 'master'
New Feature: Assign/remove users on group form

See merge request allianceauth/allianceauth!1543
2023-11-08 13:05:25 +00:00
Erik Kalkoken
3480c4e0e8 New Feature: Assign/remove users on group form 2023-11-08 13:05:24 +00:00
Ariel Rin
1544f097e0 Merge branch 'fix-leave-tab-for-autoleave' into 'master'
Fix leave tab for autoleave

See merge request allianceauth/allianceauth!1536
2023-11-08 13:04:27 +00:00
Erik Kalkoken
2477c31656 Fix leave tab for autoleave 2023-11-08 13:04:26 +00:00
Ariel Rin
0dc631d69e Merge branch 'master' into 'master'
Stop renaming discord nick when setting has it disabled

See merge request allianceauth/allianceauth!1540
2023-11-08 13:04:12 +00:00
Dusty Meg
2a9981cdb9 Stop renaming discord nick when setting has it disabled 2023-11-08 13:04:11 +00:00
255 changed files with 3607 additions and 5089 deletions

View File

@@ -25,7 +25,7 @@ before_script:
pre-commit-check:
<<: *only-default
stage: pre-commit
image: python:3.11-bullseye
image: python:3.10-bullseye
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
@@ -112,19 +112,6 @@ 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
@@ -187,22 +174,9 @@ 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.11-bullseye
image: python:3.10-bullseye
before_script:
- python -m pip install --upgrade pip
@@ -221,13 +195,13 @@ build-test:
test-docs:
<<: *only-default
image: python:3.11-bullseye
image: python:3.10-bullseye
script:
- tox -e docs
- tox -e docs
deploy_production:
stage: deploy
image: python:3.11-bullseye
image: python:3.10-bullseye
before_script:
- python -m pip install --upgrade pip
@@ -243,10 +217,10 @@ deploy_production:
build-image:
before_script: []
image: docker:24.0
image: docker:20.10.10
stage: docker
services:
- docker:24.0-dind
- docker:20.10.10-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
@@ -256,10 +230,12 @@ build-image:
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
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-)
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
rules:
- if: $CI_COMMIT_TAG
when: delayed
@@ -267,19 +243,17 @@ build-image:
build-image-dev:
before_script: []
image: docker:24.0
image: docker:20.10.10
stage: docker
services:
- docker:24.0-dind
- docker:20.10.10-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 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
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
rules:
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
when: manual
@@ -288,19 +262,17 @@ build-image-dev:
build-image-mr:
before_script: []
image: docker:24.0
image: docker:20.10.10
stage: docker
services:
- docker:24.0-dind
- docker:20.10.10-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 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
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
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual

View File

@@ -66,20 +66,14 @@ repos:
swagger\.json
)
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [ --target-version=4.0 ]
- repo: https://github.com/asottile/pyupgrade
rev: v3.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,7 +23,4 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
install:
- method: pip
path: .
extra_requirements:
- docs
- requirements: docs/requirements.txt

View File

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

View File

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

10
.tx/transifex.yml Normal file
View File

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

View File

@@ -5,7 +5,7 @@ manage online service access.
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '4.0.0a4'
__version__ = '3.8.0'
__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, AnalyticsTokens
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
@admin.register(AnalyticsIdentifier)
@@ -13,3 +13,9 @@ 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,3 +4,6 @@ from django.apps import AppConfig
class AnalyticsConfig(AppConfig):
name = 'allianceauth.analytics'
label = 'analytics'
def ready(self):
import allianceauth.analytics.signals

View File

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

View File

@@ -0,0 +1,52 @@
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

@@ -1,18 +0,0 @@
# 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

@@ -1,64 +0,0 @@
# 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

@@ -1,28 +0,0 @@
# 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,19 +7,22 @@ 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):
@@ -29,5 +32,7 @@ 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)
secret = models.CharField(max_length=254, blank=True)
send_page_views = models.BooleanField(default=False)
send_celery_tasks = models.BooleanField(default=False)
send_stats = models.BooleanField(default=False)
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)

View File

@@ -0,0 +1,55 @@
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,6 +3,7 @@ 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,
@@ -11,14 +12,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/mp/collect"
COLLECTION_URL = f"{BASE_URL}/mp/collect"
DEBUG_URL = f"{BASE_URL}debug/collect"
COLLECTION_URL = f"{BASE_URL}collect"
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
# Force sending of analytics data during in a debug/test environment
# Useful for developers working on this feature.
# Force sending of analytics data during in a debug/test environemt
# Usefull for developers working on this feature.
logger.warning(
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
"This debug instance will send analytics data!")
@@ -30,38 +31,40 @@ if settings.DEBUG is True:
ANALYTICS_URL = DEBUG_URL
def analytics_event(namespace: str,
task: str,
label: str = "",
result: str = "",
value: int = 1,
def analytics_event(category: str,
action: str,
label: str,
value: int = 0,
event_type: str = 'Celery'):
"""
Send a Google Analytics Event for each token stored
Includes check for if its enabled/disabled
Args:
`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
`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
`event_type` (str): Optional, Celery or Stats only, Default to Celery
"""
for token in AnalyticsTokens.objects.filter(type='GA-V4'):
if event_type == 'Stats':
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':
allowed = token.send_stats
else:
allowed = False
if allowed is True:
tracking_id = token.token
send_ga_tracking_celery_event.s(
measurement_id=token.token,
secret=token.secret,
namespace=namespace,
task=task,
tracking_id=tracking_id,
client_id=client_id,
category=category,
action=action,
label=label,
result=result,
value=value).apply_async(priority=9)
@@ -69,104 +72,136 @@ def analytics_event(namespace: str,
def analytics_daily_stats():
"""Celery Task: Do not call directly
Gathers a series of daily statistics
Sends analytics events containing them
Gathers a series of daily statistics and 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(namespace='allianceauth.analytics',
task='send_install_stats',
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='existence',
value=1,
event_type='Stats')
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='users',
value=users,
event_type='Stats')
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='tokens',
value=tokens,
event_type='Stats')
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='addons',
value=addons,
event_type='Stats')
for appconfig in apps.get_app_configs():
analytics_event(namespace='allianceauth.analytics',
task='send_extension_stats',
analytics_event(category='allianceauth.analytics',
action='send_extension_stats',
label=appconfig.label,
value=1,
event_type='Stats')
@shared_task()
def send_ga_tracking_celery_event(
measurement_id: str,
secret: str,
namespace: str,
task: str,
label: str = "",
result: str = "",
value: int = 1):
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 an events to GA
Sends Page View events to GA, Called only via analytics.middleware
Parameters
----------
`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
"""
`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
parameters = {
'measurement_id': measurement_id,
'api_secret': secret
}
Returns
-------
requests.Reponse Object
"""
headers = {"User-Agent": useragent}
payload = {
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
"user_properties": {
"allianceauth_version": {
"value": "allianceauth_version"
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'pageview',
'dp': page,
'dt': title,
'ul': locale,
'ua': useragent,
'aip': 1,
'an': "allianceauth",
'av': __version__
}
},
'non_personalized_ads': True,
"events": [{
"name": "celery_event",
"params": {
"namespace": namespace,
"task": task,
'result': result,
'label': label,
"value": value
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:
"""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
`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
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"}
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__
}
}]
}
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"
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
return response

View File

@@ -0,0 +1,109 @@
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

@@ -0,0 +1,24 @@
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,5 +23,4 @@ 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,11 +4,12 @@ from django.test.utils import override_settings
from allianceauth.analytics.tasks import (
analytics_event,
send_ga_tracking_celery_event)
send_ga_tracking_celery_event,
send_ga_tracking_web_view)
from allianceauth.utils.testing import NoSocketsTestCase
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/mp/collect'
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/collect'
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
@@ -17,53 +18,195 @@ class TestAnalyticsTasks(NoSocketsTestCase):
def test_analytics_event(self, requests_mocker):
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
analytics_event(
namespace='allianceauth.analytics',
task='send_tests',
label='test',
value=1,
result="Success",
event_type='Stats')
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'}]
def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
# given
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
token = 'G-6LYSMYK8DE'
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
# when
task = send_ga_tracking_celery_event(
token,
secret,
category,
action,
label,
value)
response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value)
# then
self.assertEqual(task, 200)
self.assertEqual(response.status_code, 200)
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
# given
requests_mocker.register_uri(
'POST',
GOOGLE_ANALYTICS_DEBUG_URL,
json={"validationMessages": []}
json={"hitParsingResult":[{'valid': True}]}
)
token = 'G-6LYSMYK8DE'
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
# when
task = send_ga_tracking_celery_event(
token,
secret,
category,
action,
label,
value)
json_response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value).json()
# then
self.assertTrue(task, 200)
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."
)

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,30 +1,5 @@
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 = [UserProfileInline]
inlines = BaseUserAdmin.inlines + [UserProfileInline]
ordering = ('username', )
list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True

View File

@@ -1,45 +0,0 @@
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,6 +1,3 @@
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,7 +31,6 @@ 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.
#
@@ -43,13 +42,4 @@ 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

@@ -1,18 +0,0 @@
# 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,13 +101,6 @@ 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

@@ -1,46 +0,0 @@
{% 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

@@ -1,20 +0,0 @@
{% 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,15 +1,190 @@
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% extends "allianceauth/base.html" %}
{% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
{% block header_nav_brand %}
{% translate "Dashboard" %}
{% endblock %}
{% block content %}
<div class="d-flex justify-content-around align-self-center flex-wrap">
{% for dash in views %}
{{ dash | safe }}
{% endfor %}
<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>
{% endblock %}

View File

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

View File

@@ -7,7 +7,6 @@
<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.">
@@ -31,7 +30,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,5 +1,4 @@
import logging
from allianceauth.hooks import get_hooks
from django_registration.backends.activation.views import (
REGISTRATION_SALT, ActivationView as BaseActivationView,
@@ -43,51 +42,23 @@ def index(request):
return redirect('authentication:dashboard')
def dashboard_groups(request):
@login_required
def dashboard(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,14 +12,13 @@ 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) > 4:
elif len(args) > 3:
parser.error("Too many arguments.")
# First find the path to Alliance Auth
@@ -33,7 +32,6 @@ 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,5 +1,4 @@
from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import UrlHook
from allianceauth.services.hooks import MenuItemHook, UrlHook
from django.utils.translation import gettext_lazy as _
from allianceauth import hooks
from allianceauth.corputils import urls

View File

@@ -1,40 +1,37 @@
{% extends "allianceauth/base-bs5.html" %}
{% extends 'allianceauth/base.html' %}
{% load i18n %}
{% block page_title %}
{% translate "Corporation Member Data" %}
{% endblock page_title %}
{% block page_title %}{% translate "Corporation Member Data" %}{% endblock %}
{% block content %}
<div>
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Corporation Member Data" %}</h1>
<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>
<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>
</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 member_data %}
{% 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>
</div>
{% endblock content %}
{% endblock %}

View File

@@ -3,175 +3,177 @@
{% load humanize %}
{% block member_data %}
{% if corpstats %}
<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 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>
<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 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="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 %}
<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>
<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 }}
<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>
</div>
</div>
</td>
<td>
<table class="table table-hover">
{% for alt in main.alts %}
{% if forloop.first %}
</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 %}
<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>
<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>
</tr>
{% 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>
{% 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 %}
{% 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>
<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>
<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>
{% 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>
</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>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
</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 %}
</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>
</div>
</div>
</div>
@@ -179,15 +181,12 @@
</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="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></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">{{ 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(), pre_groups | group_qs, ordered=False)
self.assertQuerysetEqual(self.member.groups.all(), map(repr, 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(), pre_groups | group_qs, ordered=False)
self.assertQuerysetEqual(self.member.groups.all(), map(repr, 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-bs5.html" %}
{% extends 'allianceauth/base.html' %}
{% load evelinks %}
{% block page_title %}Evelinks Examples{% endblock page_title %}
{% block page_title %}Evelinks examples{% endblock %}
{% block content %}
@@ -25,57 +25,60 @@ 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 content %}
{% endblock %}

View File

@@ -1,8 +1,7 @@
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 UrlHook
from allianceauth.services.hooks import MenuItemHook, UrlHook
@hooks.register('menu_item_hook')

View File

@@ -2,6 +2,7 @@
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
@@ -14,6 +15,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=datetime.timezone.utc)),
field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=utc)),
),
]

View File

@@ -1,28 +1,27 @@
{% extends 'allianceauth/base-bs5.html' %}
{% extends 'allianceauth/base.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>
<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>
{% endblock %}

View File

@@ -1,31 +1,31 @@
{% extends "allianceauth/base-bs5.html" %}
{% extends "allianceauth/base.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 offset-md-4">
{% for message in errormessages %}
<div class="alert alert-danger" role="alert">{{ message }}</div>
{% endfor %}
<div class="col-md-4 col-md-offset-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-bs5.html" %}
{% extends "allianceauth/base.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-end">
<div class="text-right">
<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-bs5.html" %}
{% extends "allianceauth/base.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-end">
<div class="text-right">
<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="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 %}
<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 %}
</table>
{% endif %}
</table>
{% endif %}
{% endif %}
</div>
{% endblock content %}

View File

@@ -1,34 +1,35 @@
{% extends "allianceauth/base-bs5.html" %}
{% extends "allianceauth/base.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-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 %}
<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 %}
</div>
</h1>
<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 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>
</div>
{% endblock content %}

View File

@@ -1,50 +1,46 @@
{% extends "allianceauth/base-bs5.html" %}
{% extends "allianceauth/base.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-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 %}
<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 %}
</div>
</h1>
{% if fatStats %}
<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>
<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>
{% 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,54 +1,48 @@
{% extends "allianceauth/base-bs5.html" %}
{% extends "allianceauth/base.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-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 %}
<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 %}
</div>
</h1>
{% if fatStats %}
<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>
<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>
{% 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,107 +1,99 @@
{% extends "allianceauth/base-bs5.html" %}
{% extends "allianceauth/base.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>
<div class="table-responsive">
<table class="table table-striped">
<table class="table">
<tr>
<th class="col-md-10">
<h4>
<b>{% translate "Most recent clicked fatlinks" %}</b>
<th class="col-md-11">
<h4><b>{% translate "Most recent clicked fatlinks" %}</b>
</h4>
</th>
<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 class="col-md-1">
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info">
{% translate "Personal statistics" %}
</a>
</th>
</tr>
</table>
{% if fats %}
<table class="table table-responsive">
<tr>
<th class="text-center">{% translate "Fleet" %}</th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "System" %}</th>
<th class="text-center">{% translate "Ship" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th>
</tr>
{% for fat in fats %}
<tr>
<td class="text-center">{{ fat.fatlink.fleet }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %}
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}
<td class="text-center">{{ fat.shiptype }}</td>
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
{% endif %}
{% if perms.auth.fleetactivitytracking%}
<table class="table">
<tr>
<th class="col-md-10">
<h4><b>{% translate "Most recent 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>
</tr>
</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>
{% 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>
{% else %}
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
<div class="alert alert-warning text-center">{% translate "No created fatlinks 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,7 +1,6 @@
from django.utils.translation import gettext_lazy as _
from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import UrlHook
from allianceauth.services.hooks import MenuItemHook, UrlHook
from allianceauth import hooks
from . import urls
@@ -34,43 +33,11 @@ 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_manager_menu():
def register_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,6 +1,10 @@
import functools
from django import forms
from django.contrib.auth.models import Group
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.db.models.functions import Lower
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -8,6 +12,39 @@ from .models import ReservedGroupName
class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.order_by(Lower('username')),
required=False,
widget=FilteredSelectMultiple(verbose_name=_("Users"), is_stacked=False),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields["users"].initial = self.instance.user_set.all()
def save(self, commit=True):
group: Group = super().save(commit=False)
if commit:
group.save()
users = self.cleaned_data["users"]
if group.pk:
self._save_m2m_and_users(group, users)
else:
self.save_m2m = functools.partial(
self._save_m2m_and_users, group=group, users=users
)
return group
def _save_m2m_and_users(self, group, users):
"""Save m2m relations incl. users."""
group.user_set.set(users)
self._save_m2m()
def clean_name(self):
my_name = self.cleaned_data['name']
if ReservedGroupName.objects.filter(name__iexact=my_name).exists():

View File

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

View File

@@ -1,75 +1,85 @@
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% extends "allianceauth/base.html" %}
{% 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 %}
{% 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>
<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>
<div class="col-lg-12">
<br>
<div class="alert alert-warning text-center">
{% translate "No entries found for this group." %}
{% include 'groupmanagement/menu.html' %}
<div class="panel panel-default">
<div class="panel-heading">
{{ group }} - {% translate "Audit Log" %}
</div>
<div class="panel-body">
<p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% translate "Back" %}
</a>
</p>
{% 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>
<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>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %}
{% include 'bundles/datatables-js.html' %}
{% include 'bundles/moment-js.html' with locale=True %}
{% include 'bundles/filterdropdown-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css-bs5.html' %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% block extra_script %}

View File

@@ -1,86 +1,97 @@
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% extends "allianceauth/base.html" %}
{% 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 %}
{% 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="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<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 panel-default">
<div class="panel-heading">
{{ group.name }} - {% translate 'Members' %}
</div>
{% if member.is_leader %}
<i class="fa-solid fa-star"> title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>&nbsp;
{% endif %}
</td>
<div class="panel-body">
<p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% translate "Back" %}
</a>
</p>
<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>
{% 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>
<p class="text-muted">
<i class="fa-solid 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>
{% endblock content %}
{% block extra_javascript %}
{% include 'bundles/datatables-js-bs5.html' %}
{% include 'bundles/datatables-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css-bs5.html' %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% block extra_script %}

View File

@@ -1,86 +1,88 @@
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% extends "allianceauth/base.html" %}
{% 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 %}
{% 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="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<tbody class="align-middle">
{% for group in groups %}
<tr>
<td>
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
</td>
<div class="panel panel-default">
<div class="panel-heading">
{% translate "Groups" %}
</div>
<td>{{ group.authgroup.description|linebreaks|urlize }}</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>
{% 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>
<tbody>
{% for group in groups %}
<tr>
<td>
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
</td>
<td class="text-center">
{{ group.num_members }}
</td>
<td>{{ group.authgroup.description|linebreaks|urlize }}</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>
{% 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>
<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">
{{ group.num_members }}
</td>
<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>
<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>
</div>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No groups to list." %}
</div>
{% endif %}
</div>
{% endblock content %}
{% block extra_javascript %}
{% include "bundles/clipboard-js.html" %}
{% include 'bundles/clipboard-js.html' %}
<script>
new ClipboardJS('#clipboard-copy');
</script>
{% endblock extra_javascript %}
{% endblock %}

View File

@@ -1,93 +1,62 @@
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% extends "allianceauth/base.html" %}
{% load i18n %}
{% block page_title %}{% translate "Available Groups" %}{% endblock page_title %}
{% block header_nav_brand %}{% translate "Available Groups" %}{% endblock header_nav_brand %}
{% block extra_css %}{% endblock extra_css %}
{% 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 %}
{% 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 class>
{% for g in groups %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Available Groups" %}</h1>
{% if groups %}
<table class="table table-aa">
<thead>
<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>
<th>{% translate "Name" %}</th>
<th>{% translate "Description" %}</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 %}
{% 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 %}
{% 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 %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-warning text-center">
{% translate "No groups available." %}
</div>
{% endif %}
</div>
{% 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,158 +1,166 @@
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% extends "allianceauth/base.html" %}
{% 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 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" %}
{% if acceptrequests %}
<span class="badge bg-secondary">{{ 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 leaverequests %}
<span class="badge bg-secondary">{{ leaverequests|length }}</span>
{% endif %}
</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>
{% endblock %}
{% 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>
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<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>
<ul class="nav nav-tabs">
<li class="active">
<a data-toggle="tab" href="#add">
{% translate "Join Requests" %}
<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>
{% if acceptrequests %}
<span class="badge">{{ acceptrequests|length }}</span>
{% endif %}
</a>
</li>
{% 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>
{% if not show_leave_tab %}
<li>
<a data-toggle="tab" href="#leave">
{% translate "Leave Requests" %}
<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>
{% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>
{% endif %}
</ul>
<div class="panel panel-default panel-tabs-aa">
<div class="panel-body">
<div class="tab-content">
<div id="add" class="tab-pane active">
{% if acceptrequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% translate "Character" %}</th>
<th>{% translate "Organization" %}</th>
<th>{% translate "Group" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% 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>
<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>
{% 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>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -1,10 +1,27 @@
{% 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>
<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>
<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>

View File

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

View File

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

View File

@@ -2,13 +2,12 @@ import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Count
from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import gettext_lazy as _
from allianceauth.notifications import notify
@@ -16,7 +15,6 @@ from allianceauth.notifications import notify
from .managers import GroupManager
from .models import GroupRequest, RequestLog
logger = logging.getLogger(__name__)
@@ -45,10 +43,15 @@ def group_management(request):
logger.debug("Providing user {} with {} acceptrequests and {} leaverequests.".format(
request.user, len(acceptrequests), len(leaverequests)))
show_leave_tab = (
getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False)
and not GroupRequest.objects.filter(leave_request=True).exists()
)
render_items = {
'acceptrequests': acceptrequests,
'leaverequests': leaverequests,
'auto_leave': getattr(settings, 'GROUPMANAGEMENT_AUTO_LEAVE', False),
'show_leave_tab': show_leave_tab,
}
return render(request, 'groupmanagement/index.html', context=render_items)
@@ -87,7 +90,7 @@ def group_membership_audit(request, group_id):
except ObjectDoesNotExist:
raise Http404("Group does not exist")
render_items = {'group': group}
render_items = {'group': group.name}
entries = RequestLog.objects.filter(group=group).order_by('-date')
render_items['entries'] = entries
@@ -311,10 +314,8 @@ def groups_view(request):
groups_qs = GroupManager.get_joinable_groups_for_user(
request.user, include_hidden=False
)
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_qs = groups_qs.order_by('name')
groups = []
## TODO see about making this faster
for group in groups_qs:
group_request = GroupRequest.objects\
.filter(user=request.user)\
@@ -324,14 +325,7 @@ def groups_view(request):
'request': group_request[0] if group_request else None
})
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}
context = {'groups': groups}
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,17 +122,3 @@ 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,8 +1,7 @@
from django.utils.translation import gettext_lazy as _
from allianceauth import hooks
from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import UrlHook
from allianceauth.services.hooks import MenuItemHook, UrlHook
from . import urls
from .models import Application

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
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',)

View File

@@ -1,19 +0,0 @@
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

@@ -1,42 +0,0 @@
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

@@ -1,28 +0,0 @@
# 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

@@ -1,28 +0,0 @@
# 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

@@ -1,17 +0,0 @@
# 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

@@ -1,39 +0,0 @@
# 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

@@ -1,174 +0,0 @@
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

@@ -1,10 +0,0 @@
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

View File

@@ -1,7 +0,0 @@
{% for data in menu_items %}
{% if data.items|length > 0 %}
{% include "menu/menu-item-bs5.html" with item=data %}
{% else %}
{{ data.render }}
{% endif %}
{% endfor %}

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