mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 06:06:19 +01:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7898594909 | ||
|
|
cfd12ee3cc | ||
|
|
2c9177b19f | ||
|
|
abff26fb6e | ||
|
|
e8c3b5225c | ||
|
|
98fd1dcc4c | ||
|
|
cfe46e4ca5 | ||
|
|
4675193416 | ||
|
|
a84fa1ca69 | ||
|
|
8f6cb0b9bb | ||
|
|
1c8634f1c8 | ||
|
|
2a21599d45 | ||
|
|
e379c01655 | ||
|
|
afa3d2e7cc | ||
|
|
e5ed33aeec | ||
|
|
b12471e775 | ||
|
|
5e70dab11f | ||
|
|
f728c786b3 | ||
|
|
7056912d54 | ||
|
|
7efed950ca | ||
|
|
886acf2005 | ||
|
|
b2dec3bff2 | ||
|
|
f0a402e141 | ||
|
|
2e2afd7923 | ||
|
|
e9ea09bc56 | ||
|
|
186fa1be03 | ||
|
|
37d1d84fc3 | ||
|
|
ee24706e43 | ||
|
|
07e85727ea | ||
|
|
4912f0f8f0 | ||
|
|
424246df26 | ||
|
|
563e2210ef | ||
|
|
02a1078005 | ||
|
|
30107de44e | ||
|
|
77a08cd218 | ||
|
|
e5a09027e5 | ||
|
|
24376262f0 | ||
|
|
efe0c6963b | ||
|
|
a4644028ae | ||
|
|
3a77b4a429 | ||
|
|
52b6c5d341 | ||
|
|
fa375a551c | ||
|
|
00a93e6fe9 | ||
|
|
656e69d4b2 | ||
|
|
3b55d370d0 | ||
|
|
5c126ffe82 | ||
|
|
99be753836 | ||
|
|
2e78aa5f26 | ||
|
|
567d97f38a | ||
|
|
d6821b3fd6 | ||
|
|
90375246fd | ||
|
|
a2f217ace5 | ||
|
|
25cf2fdcd5 | ||
|
|
4305ae7995 | ||
|
|
4aff4006e3 | ||
|
|
55c188f2d0 | ||
|
|
f36f824a4b | ||
|
|
6fbf33bcdd | ||
|
|
ed3c2c8529 | ||
|
|
05d7fb1f63 | ||
|
|
3b19db2564 | ||
|
|
98aa44c070 | ||
|
|
8d46ee65af | ||
|
|
49780b871d | ||
|
|
2b7d24fc28 | ||
|
|
b8f86a618f | ||
|
|
9921011742 |
@@ -25,7 +25,7 @@ before_script:
|
||||
pre-commit-check:
|
||||
<<: *only-default
|
||||
stage: pre-commit
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.11-bullseye
|
||||
variables:
|
||||
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||
cache:
|
||||
@@ -112,6 +112,19 @@ test-pvpy-core:
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
test-3.12-core:
|
||||
<<: *only-default
|
||||
image: python:3.12-rc-bullseye
|
||||
script:
|
||||
- tox -e py312-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
test-3.8-all:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
@@ -174,9 +187,22 @@ test-pvpy-all:
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
test-3.12-all:
|
||||
<<: *only-default
|
||||
image: python:3.12-rc-bullseye
|
||||
script:
|
||||
- tox -e py312-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
build-test:
|
||||
stage: test
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.11-bullseye
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
@@ -195,13 +221,13 @@ build-test:
|
||||
|
||||
test-docs:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.11-bullseye
|
||||
script:
|
||||
- tox -e docs
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.10-bullseye
|
||||
image: python:3.11-bullseye
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
@@ -217,10 +243,10 @@ deploy_production:
|
||||
|
||||
build-image:
|
||||
before_script: []
|
||||
image: docker:20.10.10
|
||||
image: docker:24.0
|
||||
stage: docker
|
||||
services:
|
||||
- docker:20.10.10-dind
|
||||
- docker:24.0-dind
|
||||
script: |
|
||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
|
||||
@@ -230,12 +256,19 @@ build-image:
|
||||
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
|
||||
|
||||
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
|
||||
docker tag $IMAGE_TAG $CURRENT_TAG
|
||||
docker tag $IMAGE_TAG $MINOR_TAG
|
||||
docker tag $IMAGE_TAG $MAJOR_TAG
|
||||
docker tag $IMAGE_TAG $LATEST_TAG
|
||||
docker image push --all-tags $CI_REGISTRY_IMAGE/auth
|
||||
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
docker buildx create --use --name new-builder
|
||||
docker buildx build .
|
||||
--tag $IMAGE_TAG
|
||||
--tag $CURRENT_TAG
|
||||
--tag $MINOR_TAG
|
||||
--tag $MAJOR_TAG
|
||||
--tag $LATEST_TAG
|
||||
--file docker/Dockerfile
|
||||
--platform linux/amd64,linux/arm64
|
||||
--push
|
||||
--build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: delayed
|
||||
@@ -243,17 +276,24 @@ build-image:
|
||||
|
||||
build-image-dev:
|
||||
before_script: []
|
||||
image: docker:20.10.10
|
||||
image: docker:24.0
|
||||
stage: docker
|
||||
services:
|
||||
- docker:20.10.10-dind
|
||||
- docker:24.0-dind
|
||||
script: |
|
||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
|
||||
|
||||
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
|
||||
docker push $IMAGE_TAG
|
||||
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
docker buildx create --use --name new-builder
|
||||
docker buildx build .
|
||||
--tag $IMAGE_TAG
|
||||
--file docker/Dockerfile
|
||||
--platform linux/amd64,linux/arm64
|
||||
--push
|
||||
--build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
|
||||
rules:
|
||||
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
|
||||
when: manual
|
||||
@@ -262,17 +302,24 @@ build-image-dev:
|
||||
|
||||
build-image-mr:
|
||||
before_script: []
|
||||
image: docker:20.10.10
|
||||
image: docker:24.0
|
||||
stage: docker
|
||||
services:
|
||||
- docker:20.10.10-dind
|
||||
- docker:24.0-dind
|
||||
script: |
|
||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA
|
||||
|
||||
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
|
||||
docker push $IMAGE_TAG
|
||||
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
docker buildx create --use --name new-builder
|
||||
docker buildx build .
|
||||
--tag $IMAGE_TAG
|
||||
--file docker/Dockerfile
|
||||
--platform linux/amd64,linux/arm64
|
||||
--push
|
||||
--build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: manual
|
||||
|
||||
@@ -66,14 +66,20 @@ repos:
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.14.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [ --target-version=4.0 ]
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.10.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [ --py38-plus ]
|
||||
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.14.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version=4.2]
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: [ --include-version-classifiers ]
|
||||
|
||||
@@ -23,4 +23,7 @@ formats: all
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
|
||||
@@ -5,7 +5,7 @@ manage online service access.
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '3.7.0'
|
||||
__version__ = '4.0.0a3'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
|
||||
from .models import AnalyticsIdentifier, AnalyticsTokens
|
||||
|
||||
|
||||
@admin.register(AnalyticsIdentifier)
|
||||
@@ -13,9 +13,3 @@ class AnalyticsIdentifierAdmin(admin.ModelAdmin):
|
||||
class AnalyticsTokensAdmin(admin.ModelAdmin):
|
||||
search_fields = ['name', ]
|
||||
list_display = ('name', 'type',)
|
||||
|
||||
|
||||
@admin.register(AnalyticsPath)
|
||||
class AnalyticsPathAdmin(admin.ModelAdmin):
|
||||
search_fields = ['ignore_path', ]
|
||||
list_display = ('ignore_path',)
|
||||
|
||||
@@ -4,6 +4,3 @@ from django.apps import AppConfig
|
||||
class AnalyticsConfig(AppConfig):
|
||||
name = 'allianceauth.analytics'
|
||||
label = 'analytics'
|
||||
|
||||
def ready(self):
|
||||
import allianceauth.analytics.signals
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
"model": "analytics.AnalyticsTokens",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "AA Team Public Google Analytics (Universal)",
|
||||
"name": "AA Team Public Google Analytics (V4)",
|
||||
"type": "GA-V4",
|
||||
"token": "UA-186249766-2",
|
||||
"send_page_views": "False",
|
||||
"send_celery_tasks": "False",
|
||||
"token": "G-6LYSMYK8DE",
|
||||
"secret": "KLlpjLZ-SRGozS5f5wb_kw",
|
||||
"send_stats": "False"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from .models import AnalyticsTokens, AnalyticsIdentifier
|
||||
from .tasks import send_ga_tracking_web_view
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class AnalyticsMiddleware(MiddlewareMixin):
|
||||
def process_response(self, request, response):
|
||||
"""Django Middleware: Process Page Views and creates Analytics Celery Tasks"""
|
||||
if getattr(settings, "ANALYTICS_DISABLED", False):
|
||||
return response
|
||||
analyticstokens = AnalyticsTokens.objects.all()
|
||||
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
|
||||
try:
|
||||
title = BeautifulSoup(
|
||||
response.content, "html.parser").html.head.title.text
|
||||
except AttributeError:
|
||||
title = ''
|
||||
for token in analyticstokens:
|
||||
# Check if Page View Sending is Disabled
|
||||
if token.send_page_views is False:
|
||||
continue
|
||||
# Check Exclusions
|
||||
ignore = False
|
||||
for ignore_path in token.ignore_paths.values():
|
||||
ignore_path_regex = re.compile(ignore_path["ignore_path"])
|
||||
if re.search(ignore_path_regex, request.path) is not None:
|
||||
ignore = True
|
||||
|
||||
if ignore is True:
|
||||
continue
|
||||
|
||||
tracking_id = token.token
|
||||
locale = request.LANGUAGE_CODE
|
||||
path = request.path
|
||||
try:
|
||||
useragent = request.headers["User-Agent"]
|
||||
except KeyError:
|
||||
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
|
||||
send_ga_tracking_web_view.s(tracking_id=tracking_id,
|
||||
client_id=client_id,
|
||||
page=path,
|
||||
title=title,
|
||||
locale=locale,
|
||||
useragent=useragent).\
|
||||
apply_async(priority=9)
|
||||
return response
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.6 on 2022-08-30 05:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0006_more_ignore_paths'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='analyticstokens',
|
||||
name='secret',
|
||||
field=models.CharField(blank=True, max_length=254),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,64 @@
|
||||
# Generated by Django 3.1.4 on 2020-12-30 08:53
|
||||
|
||||
from django.db import migrations
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
|
||||
def add_aa_team_token(apps, schema_editor):
|
||||
# We can't import the Person model directly as it may be a newer
|
||||
# version than this migration expects. We use the historical version.
|
||||
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
|
||||
token = Tokens()
|
||||
try:
|
||||
ua_token = Tokens.objects.get(token="UA-186249766-2")
|
||||
original_send_page_views = ua_token.send_page_views
|
||||
original_send_celery_tasks = ua_token.send_celery_tasks
|
||||
original_send_stats = ua_token.send_stats
|
||||
except ObjectDoesNotExist:
|
||||
original_send_page_views = True
|
||||
original_send_celery_tasks = True
|
||||
original_send_stats = True
|
||||
|
||||
try:
|
||||
user_notifications_count = AnalyticsPath.objects.get(ignore_path=r"^\/user_notifications_count\/.*",)
|
||||
except ObjectDoesNotExist:
|
||||
user_notifications_count = AnalyticsPath.objects.create(ignore_path=r"^\/user_notifications_count\/.*")
|
||||
|
||||
try:
|
||||
admin = AnalyticsPath.objects.get(ignore_path=r"^\/admin\/.*")
|
||||
except ObjectDoesNotExist:
|
||||
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
|
||||
|
||||
try:
|
||||
account_activate = AnalyticsPath.objects.get(ignore_path=r"^\/account\/activate\/.*")
|
||||
except ObjectDoesNotExist:
|
||||
account_activate = AnalyticsPath.objects.create(ignore_path=r"^\/account\/activate\/.*")
|
||||
|
||||
token.type = 'GA-V4'
|
||||
token.token = 'G-6LYSMYK8DE'
|
||||
token.secret = 'KLlpjLZ-SRGozS5f5wb_kw'
|
||||
token.send_page_views = original_send_page_views
|
||||
token.send_celery_tasks = original_send_celery_tasks
|
||||
token.send_stats = original_send_stats
|
||||
token.name = 'AA Team Public Google Analytics (V4)'
|
||||
token.save()
|
||||
token.ignore_paths.add(admin, user_notifications_count, account_activate)
|
||||
token.save()
|
||||
|
||||
|
||||
def remove_aa_team_token(apps, schema_editor):
|
||||
# Have to define some code to remove this identifier
|
||||
# In case of migration rollback?
|
||||
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||
token = Tokens.objects.filter(token="G-6LYSMYK8DE").delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0007_analyticstokens_secret'),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(
|
||||
add_aa_team_token, remove_aa_team_token)]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.0.10 on 2023-05-08 05:24
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0008_add_AA_GA-4_Team_Token '),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='analyticstokens',
|
||||
name='ignore_paths',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='analyticstokens',
|
||||
name='send_celery_tasks',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='analyticstokens',
|
||||
name='send_page_views',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='AnalyticsPath',
|
||||
),
|
||||
]
|
||||
@@ -7,22 +7,19 @@ from uuid import uuid4
|
||||
|
||||
class AnalyticsIdentifier(models.Model):
|
||||
|
||||
identifier = models.UUIDField(default=uuid4,
|
||||
editable=False)
|
||||
identifier = models.UUIDField(
|
||||
default=uuid4,
|
||||
editable=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk and AnalyticsIdentifier.objects.exists():
|
||||
# Force a single object
|
||||
raise ValidationError('There is can be only one \
|
||||
AnalyticsIdentifier instance')
|
||||
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
|
||||
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class AnalyticsPath(models.Model):
|
||||
ignore_path = models.CharField(max_length=254, default="/example/", help_text="Regex Expression, If matched no Analytics Page View is sent")
|
||||
|
||||
|
||||
class AnalyticsTokens(models.Model):
|
||||
|
||||
class Analytics_Type(models.TextChoices):
|
||||
@@ -32,7 +29,5 @@ class AnalyticsTokens(models.Model):
|
||||
name = models.CharField(max_length=254)
|
||||
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
|
||||
token = models.CharField(max_length=254, blank=False)
|
||||
send_page_views = models.BooleanField(default=False)
|
||||
send_celery_tasks = models.BooleanField(default=False)
|
||||
secret = models.CharField(max_length=254, blank=True)
|
||||
send_stats = models.BooleanField(default=False)
|
||||
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import logging
|
||||
from celery.signals import task_failure, task_success
|
||||
from django.conf import settings
|
||||
from allianceauth.analytics.tasks import analytics_event
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@task_failure.connect
|
||||
def process_failure_signal(
|
||||
exception, traceback,
|
||||
sender, task_id, signal,
|
||||
args, kwargs, einfo, **kw):
|
||||
logger.debug("Celery task_failure signal %s" % sender.__class__.__name__)
|
||||
if getattr(settings, "ANALYTICS_DISABLED", False):
|
||||
return
|
||||
|
||||
category = sender.__module__
|
||||
|
||||
if 'allianceauth.analytics' not in category:
|
||||
if category.endswith(".tasks"):
|
||||
category = category[:-6]
|
||||
|
||||
action = sender.__name__
|
||||
|
||||
label = f"{exception.__class__.__name__}"
|
||||
|
||||
analytics_event(category=category,
|
||||
action=action,
|
||||
label=label)
|
||||
|
||||
|
||||
@task_success.connect
|
||||
def celery_success_signal(sender, result=None, **kw):
|
||||
logger.debug("Celery task_success signal %s" % sender.__class__.__name__)
|
||||
if getattr(settings, "ANALYTICS_DISABLED", False):
|
||||
return
|
||||
|
||||
category = sender.__module__
|
||||
|
||||
if 'allianceauth.analytics' not in category:
|
||||
if category.endswith(".tasks"):
|
||||
category = category[:-6]
|
||||
|
||||
action = sender.__name__
|
||||
label = "Success"
|
||||
|
||||
value = 0
|
||||
if isinstance(result, int):
|
||||
value = result
|
||||
|
||||
analytics_event(category=category,
|
||||
action=action,
|
||||
label=label,
|
||||
value=value)
|
||||
@@ -3,7 +3,6 @@ import logging
|
||||
from django.conf import settings
|
||||
from django.apps import apps
|
||||
from celery import shared_task
|
||||
from allianceauth import __version__
|
||||
from .models import AnalyticsTokens, AnalyticsIdentifier
|
||||
from .utils import (
|
||||
install_stat_addons,
|
||||
@@ -12,14 +11,14 @@ from .utils import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
BASE_URL = "https://www.google-analytics.com/"
|
||||
BASE_URL = "https://www.google-analytics.com"
|
||||
|
||||
DEBUG_URL = f"{BASE_URL}debug/collect"
|
||||
COLLECTION_URL = f"{BASE_URL}collect"
|
||||
DEBUG_URL = f"{BASE_URL}/debug/mp/collect"
|
||||
COLLECTION_URL = f"{BASE_URL}/mp/collect"
|
||||
|
||||
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
|
||||
# Force sending of analytics data during in a debug/test environemt
|
||||
# Usefull for developers working on this feature.
|
||||
# Force sending of analytics data during in a debug/test environment
|
||||
# Useful for developers working on this feature.
|
||||
logger.warning(
|
||||
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
|
||||
"This debug instance will send analytics data!")
|
||||
@@ -31,40 +30,38 @@ if settings.DEBUG is True:
|
||||
ANALYTICS_URL = DEBUG_URL
|
||||
|
||||
|
||||
def analytics_event(category: str,
|
||||
action: str,
|
||||
label: str,
|
||||
value: int = 0,
|
||||
def analytics_event(namespace: str,
|
||||
task: str,
|
||||
label: str = "",
|
||||
result: str = "",
|
||||
value: int = 1,
|
||||
event_type: str = 'Celery'):
|
||||
"""
|
||||
Send a Google Analytics Event for each token stored
|
||||
Includes check for if its enabled/disabled
|
||||
|
||||
Args:
|
||||
`category` (str): Celery Namespace
|
||||
`action` (str): Task Name
|
||||
`label` (str): Optional, Task Success/Exception
|
||||
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
||||
`namespace` (str): Celery Namespace
|
||||
`task` (str): Task Name
|
||||
`label` (str): Optional, additional task label
|
||||
`result` (str): Optional, Task Success/Exception
|
||||
`value` (int): Optional, If bulk, Query size, can be a Boolean
|
||||
`event_type` (str): Optional, Celery or Stats only, Default to Celery
|
||||
"""
|
||||
analyticstokens = AnalyticsTokens.objects.all()
|
||||
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
|
||||
for token in analyticstokens:
|
||||
if event_type == 'Celery':
|
||||
allowed = token.send_celery_tasks
|
||||
elif event_type == 'Stats':
|
||||
for token in AnalyticsTokens.objects.filter(type='GA-V4'):
|
||||
if event_type == 'Stats':
|
||||
allowed = token.send_stats
|
||||
else:
|
||||
allowed = False
|
||||
|
||||
if allowed is True:
|
||||
tracking_id = token.token
|
||||
send_ga_tracking_celery_event.s(
|
||||
tracking_id=tracking_id,
|
||||
client_id=client_id,
|
||||
category=category,
|
||||
action=action,
|
||||
measurement_id=token.token,
|
||||
secret=token.secret,
|
||||
namespace=namespace,
|
||||
task=task,
|
||||
label=label,
|
||||
result=result,
|
||||
value=value).apply_async(priority=9)
|
||||
|
||||
|
||||
@@ -72,136 +69,104 @@ def analytics_event(category: str,
|
||||
def analytics_daily_stats():
|
||||
"""Celery Task: Do not call directly
|
||||
|
||||
Gathers a series of daily statistics and sends analytics events containing them
|
||||
Gathers a series of daily statistics
|
||||
Sends analytics events containing them
|
||||
"""
|
||||
users = install_stat_users()
|
||||
tokens = install_stat_tokens()
|
||||
addons = install_stat_addons()
|
||||
logger.debug("Running Daily Analytics Upload")
|
||||
|
||||
analytics_event(category='allianceauth.analytics',
|
||||
action='send_install_stats',
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_install_stats',
|
||||
label='existence',
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
analytics_event(category='allianceauth.analytics',
|
||||
action='send_install_stats',
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_install_stats',
|
||||
label='users',
|
||||
value=users,
|
||||
event_type='Stats')
|
||||
analytics_event(category='allianceauth.analytics',
|
||||
action='send_install_stats',
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_install_stats',
|
||||
label='tokens',
|
||||
value=tokens,
|
||||
event_type='Stats')
|
||||
analytics_event(category='allianceauth.analytics',
|
||||
action='send_install_stats',
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_install_stats',
|
||||
label='addons',
|
||||
value=addons,
|
||||
event_type='Stats')
|
||||
|
||||
for appconfig in apps.get_app_configs():
|
||||
analytics_event(category='allianceauth.analytics',
|
||||
action='send_extension_stats',
|
||||
analytics_event(namespace='allianceauth.analytics',
|
||||
task='send_extension_stats',
|
||||
label=appconfig.label,
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
|
||||
|
||||
@shared_task()
|
||||
def send_ga_tracking_web_view(
|
||||
tracking_id: str,
|
||||
client_id: str,
|
||||
page: str,
|
||||
title: str,
|
||||
locale: str,
|
||||
useragent: str) -> requests.Response:
|
||||
|
||||
"""Celery Task: Do not call directly
|
||||
|
||||
Sends Page View events to GA, Called only via analytics.middleware
|
||||
|
||||
Parameters
|
||||
----------
|
||||
`tracking_id` (str): Unique Server Identifier
|
||||
`client_id` (str): GA Token
|
||||
`page` (str): Page Path
|
||||
`title` (str): Page Title
|
||||
`locale` (str): Browser Language
|
||||
`useragent` (str): Browser UserAgent
|
||||
|
||||
Returns
|
||||
-------
|
||||
requests.Reponse Object
|
||||
"""
|
||||
headers = {"User-Agent": useragent}
|
||||
|
||||
payload = {
|
||||
'v': '1',
|
||||
'tid': tracking_id,
|
||||
'cid': client_id,
|
||||
't': 'pageview',
|
||||
'dp': page,
|
||||
'dt': title,
|
||||
'ul': locale,
|
||||
'ua': useragent,
|
||||
'aip': 1,
|
||||
'an': "allianceauth",
|
||||
'av': __version__
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
ANALYTICS_URL, data=payload,
|
||||
timeout=5, headers=headers)
|
||||
logger.debug(f"Analytics Page View HTTP{response.status_code}")
|
||||
return response
|
||||
|
||||
|
||||
@shared_task()
|
||||
def send_ga_tracking_celery_event(
|
||||
tracking_id: str,
|
||||
client_id: str,
|
||||
category: str,
|
||||
action: str,
|
||||
label: str,
|
||||
value: int) -> requests.Response:
|
||||
measurement_id: str,
|
||||
secret: str,
|
||||
namespace: str,
|
||||
task: str,
|
||||
label: str = "",
|
||||
result: str = "",
|
||||
value: int = 1):
|
||||
"""Celery Task: Do not call directly
|
||||
|
||||
Sends Page View events to GA, Called only via analytics.middleware
|
||||
Sends an events to GA
|
||||
|
||||
Parameters
|
||||
----------
|
||||
`tracking_id` (str): Unique Server Identifier
|
||||
`client_id` (str): GA Token
|
||||
`category` (str): Celery Namespace
|
||||
`action` (str): Task Name
|
||||
`label` (str): Optional, Task Success/Exception
|
||||
`measurement_id` (str): GA Token
|
||||
`secret` (str): GA Authentication Secret
|
||||
`namespace` (str): Celery Namespace
|
||||
`task` (str): Task Name
|
||||
`label` (str): Optional, additional task label
|
||||
`result` (str): Optional, Task Success/Exception
|
||||
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
||||
|
||||
Returns
|
||||
-------
|
||||
requests.Reponse Object
|
||||
"""
|
||||
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
|
||||
parameters = {
|
||||
'measurement_id': measurement_id,
|
||||
'api_secret': secret
|
||||
}
|
||||
|
||||
payload = {
|
||||
'v': '1',
|
||||
'tid': tracking_id,
|
||||
'cid': client_id,
|
||||
't': 'event',
|
||||
'ec': category,
|
||||
'ea': action,
|
||||
'el': label,
|
||||
'ev': value,
|
||||
'aip': 1,
|
||||
'an': "allianceauth",
|
||||
'av': __version__
|
||||
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
|
||||
"user_properties": {
|
||||
"allianceauth_version": {
|
||||
"value": "allianceauth_version"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
ANALYTICS_URL, data=payload,
|
||||
timeout=5, headers=headers)
|
||||
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
|
||||
return response
|
||||
},
|
||||
'non_personalized_ads': True,
|
||||
"events": [{
|
||||
"name": "celery_event",
|
||||
"params": {
|
||||
"namespace": namespace,
|
||||
"task": task,
|
||||
'result': result,
|
||||
'label': label,
|
||||
"value": value
|
||||
}
|
||||
}]
|
||||
}
|
||||
try:
|
||||
response = requests.post(
|
||||
ANALYTICS_URL,
|
||||
params=parameters,
|
||||
json=payload,
|
||||
timeout=10)
|
||||
response.raise_for_status()
|
||||
logger.debug(
|
||||
f"Analytics Celery/Stats Event HTTP{response.status_code}")
|
||||
return response.status_code
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.debug(e)
|
||||
return response.status_code
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
logger.debug(e)
|
||||
return "Failed"
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
from unittest.mock import patch
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
import requests_mock
|
||||
|
||||
from django.test import override_settings
|
||||
|
||||
from allianceauth.analytics.tasks import ANALYTICS_URL
|
||||
from allianceauth.eveonline.tasks import update_character
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.utils.testing import NoSocketsTestCase
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||
@requests_mock.mock()
|
||||
class TestAnalyticsForViews(NoSocketsTestCase):
|
||||
@override_settings(ANALYTICS_DISABLED=False)
|
||||
def test_should_run_analytics(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.post(ANALYTICS_URL)
|
||||
user = AuthUtils.create_user("Bruce Wayne")
|
||||
self.client.force_login(user)
|
||||
# when
|
||||
response = self.client.get("/dashboard/")
|
||||
# then
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(requests_mocker.called)
|
||||
|
||||
@override_settings(ANALYTICS_DISABLED=True)
|
||||
def test_should_not_run_analytics(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.post(ANALYTICS_URL)
|
||||
user = AuthUtils.create_user("Bruce Wayne")
|
||||
self.client.force_login(user)
|
||||
# when
|
||||
response = self.client.get("/dashboard/")
|
||||
# then
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(requests_mocker.called)
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||
@requests_mock.mock()
|
||||
class TestAnalyticsForTasks(NoSocketsTestCase):
|
||||
@override_settings(ANALYTICS_DISABLED=False)
|
||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
||||
def test_should_run_analytics_for_successful_task(
|
||||
self, requests_mocker, mock_update_character
|
||||
):
|
||||
# given
|
||||
requests_mocker.post(ANALYTICS_URL)
|
||||
user = AuthUtils.create_user("Bruce Wayne")
|
||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
||||
# when
|
||||
update_character.delay(character.character_id)
|
||||
# then
|
||||
self.assertTrue(mock_update_character.called)
|
||||
self.assertTrue(requests_mocker.called)
|
||||
payload = parse_qs(requests_mocker.last_request.text)
|
||||
self.assertListEqual(payload["el"], ["Success"])
|
||||
|
||||
@override_settings(ANALYTICS_DISABLED=True)
|
||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
||||
def test_should_not_run_analytics_for_successful_task(
|
||||
self, requests_mocker, mock_update_character
|
||||
):
|
||||
# given
|
||||
requests_mocker.post(ANALYTICS_URL)
|
||||
user = AuthUtils.create_user("Bruce Wayne")
|
||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
||||
# when
|
||||
update_character.delay(character.character_id)
|
||||
# then
|
||||
self.assertTrue(mock_update_character.called)
|
||||
self.assertFalse(requests_mocker.called)
|
||||
|
||||
@override_settings(ANALYTICS_DISABLED=False)
|
||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
||||
def test_should_run_analytics_for_failed_task(
|
||||
self, requests_mocker, mock_update_character
|
||||
):
|
||||
# given
|
||||
requests_mocker.post(ANALYTICS_URL)
|
||||
mock_update_character.side_effect = RuntimeError
|
||||
user = AuthUtils.create_user("Bruce Wayne")
|
||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
||||
# when
|
||||
update_character.delay(character.character_id)
|
||||
# then
|
||||
self.assertTrue(mock_update_character.called)
|
||||
self.assertTrue(requests_mocker.called)
|
||||
payload = parse_qs(requests_mocker.last_request.text)
|
||||
self.assertNotEqual(payload["el"], ["Success"])
|
||||
|
||||
@override_settings(ANALYTICS_DISABLED=True)
|
||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
||||
def test_should_not_run_analytics_for_failed_task(
|
||||
self, requests_mocker, mock_update_character
|
||||
):
|
||||
# given
|
||||
requests_mocker.post(ANALYTICS_URL)
|
||||
mock_update_character.side_effect = RuntimeError
|
||||
user = AuthUtils.create_user("Bruce Wayne")
|
||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
||||
# when
|
||||
update_character.delay(character.character_id)
|
||||
# then
|
||||
self.assertTrue(mock_update_character.called)
|
||||
self.assertFalse(requests_mocker.called)
|
||||
@@ -1,24 +0,0 @@
|
||||
from allianceauth.analytics.middleware import AnalyticsMiddleware
|
||||
from unittest.mock import Mock
|
||||
from django.http import HttpResponse
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
|
||||
class TestAnalyticsMiddleware(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.middleware = AnalyticsMiddleware(HttpResponse)
|
||||
self.request = Mock()
|
||||
self.request.headers = {
|
||||
"User-Agent": "AUTOMATED TEST"
|
||||
}
|
||||
self.request.path = '/testURL/'
|
||||
self.request.session = {}
|
||||
self.request.LANGUAGE_CODE = 'en'
|
||||
self.response = Mock()
|
||||
self.response.content = 'hello world'
|
||||
|
||||
def test_middleware(self):
|
||||
response = self.middleware.process_response(self.request, self.response)
|
||||
self.assertEqual(self.response, response)
|
||||
@@ -23,4 +23,5 @@ class TestAnalyticsIdentifier(TestCase):
|
||||
with self.assertRaises(ValidationError):
|
||||
AnalyticsIdentifier.objects.create(identifier=uuid_2)
|
||||
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
|
||||
self.assertEqual(AnalyticsIdentifier.objects.get(pk=1).identifier, UUID(uuid_1))
|
||||
self.assertEqual(AnalyticsIdentifier.objects.get(
|
||||
pk=1).identifier, UUID(uuid_1))
|
||||
|
||||
@@ -4,12 +4,11 @@ from django.test.utils import override_settings
|
||||
|
||||
from allianceauth.analytics.tasks import (
|
||||
analytics_event,
|
||||
send_ga_tracking_celery_event,
|
||||
send_ga_tracking_web_view)
|
||||
send_ga_tracking_celery_event)
|
||||
from allianceauth.utils.testing import NoSocketsTestCase
|
||||
|
||||
|
||||
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/collect'
|
||||
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/mp/collect'
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
@@ -18,195 +17,53 @@ class TestAnalyticsTasks(NoSocketsTestCase):
|
||||
def test_analytics_event(self, requests_mocker):
|
||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||
analytics_event(
|
||||
category='allianceauth.analytics',
|
||||
action='send_tests',
|
||||
label='test',
|
||||
value=1,
|
||||
event_type='Stats')
|
||||
|
||||
def test_send_ga_tracking_web_view_sent(self, requests_mocker):
|
||||
"""This test sends if the event SENDS to google.
|
||||
Not if it was successful.
|
||||
"""
|
||||
# given
|
||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||
tracking_id = 'UA-186249766-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
page = '/index/'
|
||||
title = 'Hello World'
|
||||
locale = 'en'
|
||||
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
# when
|
||||
response = send_ga_tracking_web_view(
|
||||
tracking_id,
|
||||
client_id,
|
||||
page,
|
||||
title,
|
||||
locale,
|
||||
useragent)
|
||||
# then
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_send_ga_tracking_web_view_success(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={"hitParsingResult":[{'valid': True}]}
|
||||
)
|
||||
tracking_id = 'UA-186249766-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
page = '/index/'
|
||||
title = 'Hello World'
|
||||
locale = 'en'
|
||||
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
# when
|
||||
json_response = send_ga_tracking_web_view(
|
||||
tracking_id,
|
||||
client_id,
|
||||
page,
|
||||
title,
|
||||
locale,
|
||||
useragent).json()
|
||||
# then
|
||||
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
||||
|
||||
def test_send_ga_tracking_web_view_invalid_token(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={
|
||||
"hitParsingResult":[
|
||||
{
|
||||
'valid': False,
|
||||
'parserMessage': [
|
||||
{
|
||||
'messageType': 'INFO',
|
||||
'description': 'IP Address from this hit was anonymized to 1.132.110.0.',
|
||||
'messageCode': 'VALUE_MODIFIED'
|
||||
},
|
||||
{
|
||||
'messageType': 'ERROR',
|
||||
'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.",
|
||||
'messageCode': 'VALUE_INVALID', 'parameter': 'tid'
|
||||
}
|
||||
],
|
||||
'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
tracking_id = 'UA-IntentionallyBadTrackingID-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
page = '/index/'
|
||||
title = 'Hello World'
|
||||
locale = 'en'
|
||||
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
# when
|
||||
json_response = send_ga_tracking_web_view(
|
||||
tracking_id,
|
||||
client_id,
|
||||
page,
|
||||
title,
|
||||
locale,
|
||||
useragent).json()
|
||||
# then
|
||||
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
|
||||
self.assertEqual(
|
||||
json_response["hitParsingResult"][0]["parserMessage"][1]["description"],
|
||||
"The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details."
|
||||
)
|
||||
|
||||
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
|
||||
namespace='allianceauth.analytics',
|
||||
task='send_tests',
|
||||
label='test',
|
||||
value=1,
|
||||
result="Success",
|
||||
event_type='Stats')
|
||||
|
||||
def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||
tracking_id = 'UA-186249766-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
token = 'G-6LYSMYK8DE'
|
||||
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
|
||||
category = 'test'
|
||||
action = 'test'
|
||||
label = 'test'
|
||||
value = '1'
|
||||
# when
|
||||
response = send_ga_tracking_celery_event(
|
||||
tracking_id,
|
||||
client_id,
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value)
|
||||
task = send_ga_tracking_celery_event(
|
||||
token,
|
||||
secret,
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value)
|
||||
# then
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(task, 200)
|
||||
|
||||
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={"hitParsingResult":[{'valid': True}]}
|
||||
json={"validationMessages": []}
|
||||
)
|
||||
tracking_id = 'UA-186249766-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
token = 'G-6LYSMYK8DE'
|
||||
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
|
||||
category = 'test'
|
||||
action = 'test'
|
||||
label = 'test'
|
||||
value = '1'
|
||||
# when
|
||||
json_response = send_ga_tracking_celery_event(
|
||||
tracking_id,
|
||||
client_id,
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value).json()
|
||||
task = send_ga_tracking_celery_event(
|
||||
token,
|
||||
secret,
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value)
|
||||
# then
|
||||
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
||||
|
||||
def test_send_ga_tracking_celery_event_invalid_token(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={
|
||||
"hitParsingResult":[
|
||||
{
|
||||
'valid': False,
|
||||
'parserMessage': [
|
||||
{
|
||||
'messageType': 'INFO',
|
||||
'description': 'IP Address from this hit was anonymized to 1.132.110.0.',
|
||||
'messageCode': 'VALUE_MODIFIED'
|
||||
},
|
||||
{
|
||||
'messageType': 'ERROR',
|
||||
'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.",
|
||||
'messageCode': 'VALUE_INVALID', 'parameter': 'tid'
|
||||
}
|
||||
],
|
||||
'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
tracking_id = 'UA-IntentionallyBadTrackingID-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
category = 'test'
|
||||
action = 'test'
|
||||
label = 'test'
|
||||
value = '1'
|
||||
# when
|
||||
json_response = send_ga_tracking_celery_event(
|
||||
tracking_id,
|
||||
client_id,
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value).json()
|
||||
# then
|
||||
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
|
||||
self.assertEqual(
|
||||
json_response["hitParsingResult"][0]["parserMessage"][1]["description"],
|
||||
"The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details."
|
||||
)
|
||||
self.assertTrue(task, 200)
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
from django.apps import AppConfig
|
||||
from django.core.checks import Warning, Error, register
|
||||
|
||||
|
||||
class AllianceAuthConfig(AppConfig):
|
||||
name = 'allianceauth'
|
||||
|
||||
|
||||
@register()
|
||||
def check_settings(app_configs, **kwargs):
|
||||
from django.conf import settings
|
||||
|
||||
errors = []
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL[-1] == "/":
|
||||
errors.append(Warning(
|
||||
"'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth."))
|
||||
else:
|
||||
errors.append(Error(
|
||||
"No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules."))
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS"):
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
errors.append(Warning(
|
||||
"'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified."))
|
||||
else:
|
||||
errors.append(Error(
|
||||
"No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified"))
|
||||
|
||||
return errors
|
||||
|
||||
@@ -288,7 +288,7 @@ class UserAdmin(BaseUserAdmin):
|
||||
Behavior of groups and characters columns can be configured via settings
|
||||
"""
|
||||
|
||||
inlines = BaseUserAdmin.inlines + [UserProfileInline]
|
||||
inlines = [UserProfileInline]
|
||||
ordering = ('username', )
|
||||
list_select_related = ('profile__state', 'profile__main_character')
|
||||
show_full_result_count = True
|
||||
|
||||
45
allianceauth/authentication/auth_hooks.py
Normal file
45
allianceauth/authentication/auth_hooks.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from allianceauth.hooks import DashboardItemHook
|
||||
from allianceauth import hooks
|
||||
from .views import dashboard_characters, dashboard_groups, dashboard_admin
|
||||
|
||||
|
||||
class UserCharactersHook(DashboardItemHook):
|
||||
def __init__(self):
|
||||
DashboardItemHook.__init__(
|
||||
self,
|
||||
dashboard_characters,
|
||||
5
|
||||
)
|
||||
|
||||
|
||||
class UserGroupsHook(DashboardItemHook):
|
||||
def __init__(self):
|
||||
DashboardItemHook.__init__(
|
||||
self,
|
||||
dashboard_groups,
|
||||
5
|
||||
)
|
||||
|
||||
|
||||
class AdminHook(DashboardItemHook):
|
||||
def __init__(self):
|
||||
DashboardItemHook.__init__(
|
||||
self,
|
||||
dashboard_admin,
|
||||
0
|
||||
)
|
||||
|
||||
|
||||
@hooks.register('dashboard_hook')
|
||||
def register_character_hook():
|
||||
return UserCharactersHook()
|
||||
|
||||
|
||||
@hooks.register('dashboard_hook')
|
||||
def register_groups_hook():
|
||||
return UserGroupsHook()
|
||||
|
||||
|
||||
@hooks.register('dashboard_hook')
|
||||
def register_admin_hook():
|
||||
return AdminHook()
|
||||
@@ -1,3 +1,6 @@
|
||||
from django.urls import include
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from functools import wraps
|
||||
from typing import Callable, Iterable, Optional
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class UserSettingsMiddleware(MiddlewareMixin):
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
# AA v3 NIGHT_MODE
|
||||
# Set our Night mode flag from the DB
|
||||
# Null = hasnt been set by the user ever, dont act.
|
||||
#
|
||||
@@ -42,4 +43,13 @@ class UserSettingsMiddleware(MiddlewareMixin):
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
# AA v4 Themes
|
||||
# Null = has not been set by the user ever, dont act
|
||||
# DEFAULT_THEME or DEFAULT_THEME_DARK will be used in get_theme()
|
||||
try:
|
||||
if request.user.profile.theme is not None:
|
||||
request.session["THEME"] = request.user.profile.theme
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
return response
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.0.10 on 2023-10-07 07:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0021_alter_userprofile_language'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userprofile',
|
||||
name='theme',
|
||||
field=models.CharField(blank=True, help_text='Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps', max_length=200, null=True, verbose_name='Theme'),
|
||||
),
|
||||
]
|
||||
@@ -101,6 +101,13 @@ class UserProfile(models.Model):
|
||||
_("Night Mode"),
|
||||
blank=True,
|
||||
null=True)
|
||||
theme = models.CharField(
|
||||
_("Theme"),
|
||||
max_length=200,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps"
|
||||
)
|
||||
|
||||
def assign_state(self, state=None, commit=True):
|
||||
if not state:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{% load i18n %}
|
||||
<div class="col-12 col-xl-8 align-self-stretch p-2 ps-0">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<h4 class="ms-auto me-auto">
|
||||
{% translate "Characters" %}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}">
|
||||
<span class="d-md-inline m-2">{% translate 'Add Character' %}</span>
|
||||
</a>
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Change Main' %}">
|
||||
<span class="d-md-inline m-2">{% translate 'Change Main' %}</span>
|
||||
</a>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% translate "Name" %}</th>
|
||||
<th class="text-center">{% translate "Corp" %}</th>
|
||||
<th class="text-center">{% translate "Alliance" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar rounded-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name|default_if_none:"" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
{% load i18n %}
|
||||
<div class="col-12 col-xl-4 align-self-stretch py-2 ps-2">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Membership" %}</h4>
|
||||
<div class="card-body">
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<h6 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h6>
|
||||
<table class="table">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td class="text-center">{{ group.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,190 +1,15 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||
|
||||
{% block header_nav_brand %}
|
||||
{% translate "Dashboard" %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">{% translate "Dashboard" %}</h1>
|
||||
{% if user.is_staff %}
|
||||
{% include 'allianceauth/admin-status/include.html' %}
|
||||
{% endif %}
|
||||
<div class="col-sm-12">
|
||||
<div class="row vertical-flexbox-row2">
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-primary" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% blocktranslate with state=request.user.profile.state %}
|
||||
Main Character (State: {{ state }})
|
||||
{% endblocktranslate %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if request.user.profile.main_character %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<div class="hidden-xs">
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_128 }}" alt="{{ main.character_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.character_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_128 }}" alt="{{ main.corporation_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.corporation_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
{% if main.alliance_id %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_128 }}" alt="{{ main.alliance_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.alliance_name }}</td>
|
||||
<tr>
|
||||
</table>
|
||||
{% elif main.faction_id %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar" src="{{ main.faction_logo_url_128 }}" alt="{{ main.faction_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.faction_name }}</td>
|
||||
<tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table visible-xs-block">
|
||||
<p>
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_64 }}" alt="{{ main.corporation_name }}">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}" alt="{{ main.corporation_name }}">
|
||||
{% if main.alliance_id %}
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}" alt="{{ main.alliance_name }}">
|
||||
{% endif %}
|
||||
{% if main.faction_id %}
|
||||
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}" alt="{{ main.faction_name }}">
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ main.character_name }}</strong><br>
|
||||
{{ main.corporation_name }}<br>
|
||||
{% if main.alliance_id %}
|
||||
{{ main.alliance_name }}<br>
|
||||
{% endif %}
|
||||
{% if main.faction_id %}
|
||||
{{ main.faction_name }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% translate "No main character set." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<p>
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% translate 'Add Character' %}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<p>
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% translate "Change Main" %}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-success" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% translate "Group Memberships" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="height: 240px;overflow-y:auto;">
|
||||
<table class="table table-aa">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title text-center" style="text-align: center">
|
||||
{% translate 'Characters' %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-aa hidden-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% translate 'Name' %}</th>
|
||||
<th class="text-center">{% translate 'Corp' %}</th>
|
||||
<th class="text-center">{% translate 'Alliance' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name|default:"" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-aa visible-xs-block" style="width: 100%">
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align: middle">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
||||
</td>
|
||||
<td class="text-center" style="vertical-align: middle; width: 100%">
|
||||
<strong>{{ char.character_name }}</strong><br>
|
||||
{{ char.corporation_name }}<br>
|
||||
{{ char.alliance_name|default:"" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-around align-self-center flex-wrap">
|
||||
{% for dash in views %}
|
||||
{{ dash | safe }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||
{% block page_title %}{% translate "Dashboard" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
|
||||
<div class="col-sm-12">
|
||||
<table class="table table-aa" id="table_tokens" style="width:100%">
|
||||
<div>
|
||||
<table class="table table-aa" id="table_tokens" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Scopes" %}</th>
|
||||
<th class="text-right">{% translate "Actions" %}</th>
|
||||
<th class="text-end">{% translate "Actions" %}</th>
|
||||
<th>{% translate "Character" %}</th>
|
||||
|
||||
</tr>
|
||||
@@ -18,24 +18,27 @@
|
||||
<tbody>
|
||||
{% for t in tokens %}
|
||||
<tr>
|
||||
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td>
|
||||
<td nowrap class="text-right"><a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a> <a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a></td>
|
||||
<td>{{t.character_name}}</td>
|
||||
<td style="white-space:initial;">{% for s in t.scopes.all %}<span class="badge bg-secondary">{{ s.name }}</span>{% endfor %}</td>
|
||||
<td nowrap class="text-end">
|
||||
<a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a>
|
||||
<a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a>
|
||||
</td>
|
||||
<td>{{ t.character_name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% endblock %}
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
{% endblock extra_javascript %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
{% include "bundles/datatables-css-bs5.html" %}
|
||||
{% endblock extra_css %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
@@ -59,4 +62,4 @@
|
||||
"stateSave": true,
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<!-- TODO Bundle all the site specific stuff up into its own template for easy overide -->
|
||||
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||
@@ -30,7 +31,7 @@
|
||||
}
|
||||
|
||||
.panel-transparent {
|
||||
background: rgba(48, 48, 48, 0.7);
|
||||
background: rgba(48 48 48 / 0.7);
|
||||
color: #ffffff;
|
||||
padding-bottom: 21px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from allianceauth.hooks import get_hooks
|
||||
|
||||
from django_registration.backends.activation.views import (
|
||||
REGISTRATION_SALT, ActivationView as BaseActivationView,
|
||||
@@ -42,23 +43,51 @@ def index(request):
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
def dashboard_groups(request):
|
||||
groups = request.user.groups.all()
|
||||
if _has_auto_groups:
|
||||
groups = groups\
|
||||
.filter(managedalliancegroup__isnull=True)\
|
||||
.filter(managedcorpgroup__isnull=True)
|
||||
groups = groups.order_by('name')
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
}
|
||||
return render_to_string('authentication/dashboard.groups.html', context=context, request=request)
|
||||
|
||||
|
||||
def dashboard_characters(request):
|
||||
characters = EveCharacter.objects\
|
||||
.filter(character_ownership__user=request.user)\
|
||||
.select_related()\
|
||||
.order_by('character_name')
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
'characters': characters
|
||||
}
|
||||
return render_to_string('authentication/dashboard.characters.html', context=context, request=request)
|
||||
|
||||
|
||||
def dashboard_admin(request):
|
||||
if request.user.is_superuser:
|
||||
return render_to_string('allianceauth/admin-status/include.html', request=request)
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
_dash_items = list()
|
||||
hooks = get_hooks('dashboard_hook')
|
||||
items = [fn() for fn in hooks]
|
||||
items.sort(key=lambda i: i.order)
|
||||
for item in items:
|
||||
_dash_items.append(item.render(request))
|
||||
|
||||
context = {
|
||||
'views': _dash_items,
|
||||
}
|
||||
return render(request, 'authentication/dashboard.html', context)
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ def create_project(parser, options, args):
|
||||
'python': shutil.which('python'),
|
||||
'gunicorn': shutil.which('gunicorn'),
|
||||
'celery': shutil.which('celery'),
|
||||
'memmon': shutil.which('memmon'),
|
||||
'extensions': ['py', 'conf', 'json'],
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from allianceauth.corputils import urls
|
||||
|
||||
@@ -1,37 +1,40 @@
|
||||
{% extends 'allianceauth/base.html' %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
{% block page_title %}{% translate "Corporation Member Data" %}{% endblock %}
|
||||
{% block page_title %}
|
||||
{% translate "Corporation Member Data" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<div>
|
||||
<h1 class="page-header text-center">{% translate "Corporation Member Data" %}</h1>
|
||||
<div class="col-lg-10 col-lg-offset-1 container">
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
|
||||
{% for corpstat in available %}
|
||||
<li>
|
||||
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
|
||||
{% for corpstat in available %}
|
||||
<li>
|
||||
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% if perms.corputils.add_corpstats %}
|
||||
<li>
|
||||
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
|
||||
</li>
|
||||
{% if perms.corputils.add_corpstats %}
|
||||
<li>
|
||||
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate "Search all corporations..." %}{% endif %}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
{% block member_data %}{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate 'Search all corporations...' %}{% endif %}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% block member_data %}
|
||||
{% endblock member_data %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock content %}
|
||||
|
||||
@@ -3,177 +3,175 @@
|
||||
{% load humanize %}
|
||||
{% block member_data %}
|
||||
{% if corpstats %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
|
||||
</td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center col-lg-6">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
|
||||
</td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center col-lg-6">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<ul class="nav nav-pills pull-left">
|
||||
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li>
|
||||
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li>
|
||||
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
|
||||
</ul>
|
||||
<div class="pull-right hidden-xs">
|
||||
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}
|
||||
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<ul class="nav nav-pills pull-left">
|
||||
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li>
|
||||
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li>
|
||||
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
|
||||
</ul>
|
||||
<div class="pull-right hidden-xs">
|
||||
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}
|
||||
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="mains">
|
||||
{% if mains %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="table-mains">
|
||||
<thead>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="mains">
|
||||
{% if mains %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="table-mains">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for id, main in mains.items %}
|
||||
<tr>
|
||||
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for id, main in mains.items %}
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align:middle">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
|
||||
<div class="caption text-center">
|
||||
{{ main.main }}
|
||||
</div>
|
||||
<td class="text-center" style="vertical-align:middle">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
|
||||
<div class="caption text-center">
|
||||
{{ main.main }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<table class="table table-hover">
|
||||
{% for alt in main.alts %}
|
||||
{% if forloop.first %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center">{% translate "Corporation" %}</th>
|
||||
<th class="text-center">{% translate "Alliance" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<table class="table table-hover">
|
||||
{% for alt in main.alts %}
|
||||
{% if forloop.first %}
|
||||
<tr>
|
||||
<td class="text-center" style="width:5%">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
||||
<td class="text-center" style="width:5%">
|
||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
|
||||
{% translate "Killboard" %}
|
||||
</a>
|
||||
</td>
|
||||
<th></th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center">{% translate "Corporation" %}</th>
|
||||
<th class="text-center">{% translate "Alliance" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="members">
|
||||
{% if members %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="table-members">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% translate "Main Character" %}</th>
|
||||
<th class="text-center">{% translate "Main Corporation" %}</th>
|
||||
<th class="text-center">{% translate "Main Alliance" %}</th>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="text-center" style="width:5%">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
||||
<td class="text-center" style="width:5%">
|
||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
|
||||
{% translate "Killboard" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
|
||||
<td class="text-center">{{ member }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="unregistered">
|
||||
{% if unregistered %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="table-unregistered">
|
||||
<thead>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="members">
|
||||
{% if members %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="table-members">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% translate "Main Character" %}</th>
|
||||
<th class="text-center">{% translate "Main Corporation" %}</th>
|
||||
<th class="text-center">{% translate "Main Alliance" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center"></th>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
|
||||
<td class="text-center">{{ member }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
|
||||
{% translate "Killboard" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="unregistered">
|
||||
{% if unregistered %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="table-unregistered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">
|
||||
{% translate "Killboard" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,12 +179,15 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$('#table-mains').DataTable({
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
|
||||
<td class="text-center">{{ result.1.character_name }}</td>
|
||||
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
|
||||
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a></td>
|
||||
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
|
||||
<td class="text-center">{{ result.1.main_character.character_name }}</td>
|
||||
<td class="text-center">{{ result.1.main_character.corporation_name }}</td>
|
||||
<td class="text-center">{{ result.1.main_character.alliance_name }}</td>
|
||||
|
||||
@@ -93,7 +93,7 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
group_qs = Group.objects.filter(pk=group.pk)
|
||||
|
||||
self.assertIn(group, self.member.groups.all())
|
||||
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
|
||||
self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
|
||||
|
||||
def test_update_alliance_group_membership_no_main_character(self):
|
||||
obj = AutogroupsConfig.objects.create()
|
||||
@@ -172,7 +172,7 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
group_qs = Group.objects.filter(pk=group.pk)
|
||||
|
||||
self.assertIn(group, self.member.groups.all())
|
||||
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
|
||||
self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
|
||||
|
||||
def test_update_corp_group_membership_no_state(self):
|
||||
obj = AutogroupsConfig.objects.create(corp_groups=True)
|
||||
|
||||
@@ -8,10 +8,10 @@ Needs to be called with a context containing three objects:
|
||||
|
||||
-->
|
||||
|
||||
{% extends 'allianceauth/base.html' %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load evelinks %}
|
||||
|
||||
{% block page_title %}Evelinks examples{% endblock %}
|
||||
{% block page_title %}Evelinks Examples{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -25,60 +25,57 @@ Needs to be called with a context containing three objects:
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>evewho</h3>
|
||||
<p><a href="{{ my_character|evewho_character_url}}">character from character object</a></p>
|
||||
<p><a href="{{ my_corporation|evewho_corporation_url}}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|evewho_corporation_url}}">corporation from charachter object</a></p>
|
||||
<p><a href="{{ my_alliance|evewho_alliance_url}}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ my_character|evewho_alliance_url}}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_character|evewho_character_url }}">character from character object</a></p>
|
||||
<p><a href="{{ my_corporation|evewho_corporation_url }}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|evewho_corporation_url }}">corporation from charachter object</a></p>
|
||||
<p><a href="{{ my_alliance|evewho_alliance_url }}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ my_character|evewho_alliance_url }}">alliance from character object</a></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>dotlan</h3>
|
||||
<p><a href="{{ my_character|dotlan_corporation_url}}">corporation form character object</a></p>
|
||||
<p><a href="{{ my_corporation|dotlan_corporation_url}}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|dotlan_alliance_url}}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_alliance|dotlan_alliance_url}}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ 'Black Rise'|dotlan_region_url}}">region from name string</a></p>
|
||||
<p><a href="{{ 'Tama'|dotlan_solar_system_url}}">solar system from name string</a></p>
|
||||
<p><a href="{{ my_character|dotlan_corporation_url }}">corporation form character object</a></p>
|
||||
<p><a href="{{ my_corporation|dotlan_corporation_url }}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|dotlan_alliance_url }}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_alliance|dotlan_alliance_url }}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ 'Black Rise'|dotlan_region_url }}">region from name string</a></p>
|
||||
<p><a href="{{ 'Tama'|dotlan_solar_system_url }}">solar system from name string</a></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>zkillboard</h3>
|
||||
<p><a href="{{ my_character|zkillboard_character_url}}">character from character object</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_corporation_url}}">corporation from character object</a></p>
|
||||
<p><a href="{{ my_corporation|zkillboard_corporation_url}}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_alliance_url}}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_alliance|zkillboard_alliance_url}}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ 10000069|zkillboard_region_url}}">region from ID</a></p>
|
||||
<p><a href="{{ 30002813|zkillboard_solar_system_url}}">solar sytem from ID</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_character_url }}">character from character object</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_corporation_url }}">corporation from character object</a></p>
|
||||
<p><a href="{{ my_corporation|zkillboard_corporation_url }}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_alliance_url }}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_alliance|zkillboard_alliance_url }}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ 10000069|zkillboard_region_url }}">region from ID</a></p>
|
||||
<p><a href="{{ 30002813|zkillboard_solar_system_url }}">solar sytem from ID</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>image URLs</h2>
|
||||
|
||||
<div class="rows">
|
||||
|
||||
<div class="col-md-4">
|
||||
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128}}"></p>
|
||||
<p>character from character object: <img src="{{ my_character|character_portrait_url:128}}"></p>
|
||||
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128 }}"></p>
|
||||
<p>character from character object: <img src="{{ my_character|character_portrait_url:128 }}"></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<p>corporation from ID: <img src="{{ my_character.corporation_id|corporation_logo_url:128}}"></p>
|
||||
<p>corporation from character object: <img src="{{ my_character|corporation_logo_url:128}}"></p>
|
||||
<p>corporation from corporation object: <img src="{{ my_corporation|corporation_logo_url:128}}"></p>
|
||||
<p>corporation from ID: <img src="{{ my_character.corporation_id|corporation_logo_url:128 }}"></p>
|
||||
<p>corporation from character object: <img src="{{ my_character|corporation_logo_url:128 }}"></p>
|
||||
<p>corporation from corporation object: <img src="{{ my_corporation|corporation_logo_url:128 }}"></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<p>alliance from ID: <img src="{{ my_character.alliance_id|alliance_logo_url:128}}"></p>
|
||||
<p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128}}"></p>
|
||||
<p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128}}"></p>
|
||||
<p>alliance from ID: <img src="{{ my_character.alliance_id|alliance_logo_url:128 }}"></p>
|
||||
<p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128 }}"></p>
|
||||
<p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128 }}"></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
from . import urls
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -15,6 +14,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='fatlink',
|
||||
name='fatdatetime',
|
||||
field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=utc)),
|
||||
field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=datetime.timezone.utc)),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
{% extends 'allianceauth/base.html' %}
|
||||
{% extends 'allianceauth/base-bs5.html' %}
|
||||
{% load i18n %}
|
||||
{% block page_title %}{% translate "Fleet Participation" %}{% endblock %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Fleet Participation" %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1>
|
||||
<div class="col-lg-12 container" id="example">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ character_name }}</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-2 col-sm-2">
|
||||
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div>
|
||||
{% translate "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% translate "Add it here" %}</a> {% translate "before attempting to click fleet attendance links." %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1>
|
||||
<div class="col-lg-12 container" id="example">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ character_name }}</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-2 col-sm-2">
|
||||
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div>
|
||||
{% translate "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% translate "Add it here" %}</a> {% translate "before attempting to click fleet attendance links." %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Create Fatlink" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Create Fatlink" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Create Fleet Operation" %}</h1>
|
||||
|
||||
<div class="container-fluid">
|
||||
{% if badrequest %}
|
||||
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
|
||||
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
|
||||
{% endif %}
|
||||
{% for message in errormessages %}
|
||||
<div class="alert alert-danger" role="alert">{{ message }}</div>
|
||||
{% endfor %}
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
{% for message in errormessages %}<div class="alert alert-danger" role="alert">{{ message }}</div>{% endfor %}
|
||||
<div class="col-md-4 offset-md-4">
|
||||
<div class="row">
|
||||
<form class="form-signin" role="form" action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<br>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit" name="submit_fat">{% translate "Create fatlink" %}</button>
|
||||
</form>
|
||||
<form class="form-signin" role="form" action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<br>
|
||||
<button class="btn btn-lg btn-primary btn-block"
|
||||
type="submit"
|
||||
name="submit_fat">
|
||||
{% translate "Create fatlink" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Edit fatlink" %} "{{ fatlink }}"
|
||||
<div class="text-right">
|
||||
<div class="text-end">
|
||||
<form>
|
||||
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
|
||||
{% translate "Delete fat" %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
{% if char_id %}
|
||||
<div class="text-right">
|
||||
<div class="text-end">
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||
</div>
|
||||
@@ -33,39 +33,39 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>
|
||||
{% blocktranslate count links=n_created_fats trimmed %}
|
||||
{{ user }} has created one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has created {{ links }} links this month.
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Fleet" %}</th>
|
||||
<th class="text-center">{% translate "Creator" %}</th>
|
||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
||||
<th class="text-center">{% translate "Duration" %}</th>
|
||||
<th class="text-center">{% translate "Edit" %}</th>
|
||||
</tr>
|
||||
{% for link in created_fats %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}">
|
||||
<button type="button" class="btn btn-info"><span
|
||||
class="glyphicon glyphicon-edit"></span></button>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<h2>
|
||||
{% blocktranslate count links=n_created_fats trimmed %}
|
||||
{{ user }} has created one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has created {{ links }} links this month.
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Fleet" %}</th>
|
||||
<th class="text-center">{% translate "Creator" %}</th>
|
||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
||||
<th class="text-center">{% translate "Duration" %}</th>
|
||||
<th class="text-center">{% translate "Edit" %}</th>
|
||||
</tr>
|
||||
{% for link in created_fats %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}">
|
||||
<button type="button" class="btn btn-info"><span
|
||||
class="glyphicon glyphicon-edit"></span></button>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Personal fatlink statistics" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:personal_statistics_year' previous_year %}" class="btn btn-info">{% translate "Previous year" %}</a>
|
||||
{% if next_year %}
|
||||
<a href="{% url 'fatlink:personal_statistics_year' next_year %}" class="btn btn-info">{% translate "Next year" %}</a>
|
||||
{% endif %}
|
||||
<h1 class="page-header text-center">
|
||||
{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
|
||||
<div class="text-end">
|
||||
<a href="{% url "fatlink:personal_statistics_year" previous_year %}" class="btn btn-info"><i class="fa-solid fa-chevron-left"></i> {% translate "Previous year" %}</a>
|
||||
{% if next_year %}
|
||||
<a href="{% url "fatlink:personal_statistics_year" next_year %}" class="btn btn-info">{% translate "Next year" %} <i class="fa-solid fa-chevron-right"></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h1>
|
||||
<div class="col-lg-2 col-lg-offset-5">
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-2 text-center">{% translate "Month" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||
</tr>
|
||||
{% for monthnr, month, n_fats in monthlystats %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:personal_statistics_month' year monthnr %}">
|
||||
{{ month }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">{{ n_fats }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="col-lg-2 offset-lg-5">
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Month" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||
</tr>
|
||||
{% for monthnr, month, n_fats in monthlystats %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:personal_statistics_month' year monthnr %}">{{ month }}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ n_fats }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Fatlink Corp Statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Fatlink Corp Statistics" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
<a href="{% url 'fatlink:statistics_corp_month' corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||
{% endif %}
|
||||
<h1 class="page-header text-center">
|
||||
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-end">
|
||||
<a href="{% url "fatlink:statistics_corp_month" corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
<a href="{% url "fatlink:statistics_corp_month" corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h1>
|
||||
{% if fatStats %}
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-1"></th>
|
||||
<th class="col-md-2 text-center">{% translate "Main Character" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Characters" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Average fats" %}
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
|
||||
</th>
|
||||
</tr>
|
||||
{% for memberStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}">
|
||||
</td>
|
||||
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
|
||||
<td class="text-center">{{ memberStat.n_chars }}</td>
|
||||
<td class="text-center">{{ memberStat.n_fats }}</td>
|
||||
<td class="text-center">{{ memberStat.avg_fat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th scope="col" class="col-md-1"></th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Main Character" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Characters" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">
|
||||
{% translate "Average fats" %}
|
||||
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
|
||||
</th>
|
||||
</tr>
|
||||
{% for memberStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}">
|
||||
</td>
|
||||
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
|
||||
<td class="text-center">{{ memberStat.n_chars }}</td>
|
||||
<td class="text-center">{{ memberStat.n_fats }}</td>
|
||||
<td class="text-center">{{ memberStat.avg_fat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function () {
|
||||
$("[rel=tooltip]").tooltip();
|
||||
});
|
||||
$(document).ready(function () {
|
||||
$("[rel=tooltip]").tooltip();
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Fatlink statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Fatlink Statistics" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:statistics_month' previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
<a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||
{% endif %}
|
||||
<h1 class="page-header text-center">
|
||||
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-end">
|
||||
<a href="{% url "fatlink:statistics_month" previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
<a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h1>
|
||||
{% if fatStats %}
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-1"></th>
|
||||
<th class="col-md-2 text-center">{% translate "Ticker" %}</th>
|
||||
<th class="col-md-5 text-center">{% translate "Corp" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Members" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||
<th class="col-md-2 text-center">{% translate "Average fats" %}
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
|
||||
</th>
|
||||
</tr>
|
||||
{% for corpStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
|
||||
</td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a></td>
|
||||
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
|
||||
<td class="text-center">{{ corpStat.corp.member_count }}</td>
|
||||
<td class="text-center">{{ corpStat.n_fats }}</td>
|
||||
<td class="text-center">{{ corpStat.avg_fat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th scope="col" class="col-md-1"></th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Ticker" %}</th>
|
||||
<th scope="col" class="col-md-5 text-center">{% translate "Corp" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Members" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||
<th scope="col" class="col-md-2 text-center">
|
||||
{% translate "Average fats" %}
|
||||
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
|
||||
</th>
|
||||
</tr>
|
||||
{% for corpStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a>
|
||||
</td>
|
||||
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
|
||||
<td class="text-center">{{ corpStat.corp.member_count }}</td>
|
||||
<td class="text-center">{{ corpStat.n_fats }}</td>
|
||||
<td class="text-center">{{ corpStat.avg_fat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function () {
|
||||
$("[rel=tooltip]").tooltip();
|
||||
});
|
||||
$(document).ready(function () {
|
||||
$("[rel=tooltip]").tooltip();
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -1,99 +1,107 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Fatlink view" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Participation data" %}</h1>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="col-md-11">
|
||||
<h4><b>{% translate "Most recent clicked fatlinks" %}</b>
|
||||
</h4>
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info">
|
||||
{% translate "Personal statistics" %}
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
{% if fats %}
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Fleet" %}</th>
|
||||
<th class="text-center">{% translate "Character" %}</th>
|
||||
<th class="text-center">{% translate "System" %}</th>
|
||||
<th class="text-center">{% translate "Ship" %}</th>
|
||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
||||
</tr>
|
||||
{% for fat in fats %}
|
||||
<tr>
|
||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ fat.shiptype }}</td>
|
||||
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.auth.fleetactivitytracking%}
|
||||
<table class="table">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th class="col-md-10">
|
||||
<h4><b>{% translate "Most recent fatlinks" %}</b>
|
||||
<h4>
|
||||
<b>{% translate "Most recent clicked fatlinks" %}</b>
|
||||
</h4>
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info">
|
||||
{% translate "View statistics" %}
|
||||
</a>
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
<a href="{% url 'fatlink:create' %}" class="btn btn-success">
|
||||
{% translate "Create fatlink" %}
|
||||
</a>
|
||||
<th class="col-md-2 align-self-end">
|
||||
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info"><i class="fa-solid fa-circle-info fa-fw"></i>{% translate "Personal statistics" %}</a>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
{% if fatlinks %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Name" %}</th>
|
||||
<th class="text-center">{% translate "Creator" %}</th>
|
||||
<th class="text-center">{% translate "Fleet" %}</th>
|
||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
||||
<th class="text-center">{% translate "Duration" %}</th>
|
||||
<th class="text-center">{% translate "Edit" %}</th>
|
||||
</tr>
|
||||
{% for link in fatlinks %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-edit"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
{% if fats %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Character" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "System" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Ship" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
|
||||
</tr>
|
||||
{% for fat in fats %}
|
||||
<tr>
|
||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% translate "Docked in" %} {{ fat.system }}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ fat.shiptype }}</td>
|
||||
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% translate "No created fatlinks on record." %}</div>
|
||||
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
|
||||
{% endif %}
|
||||
{% if perms.auth.fleetactivitytracking %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th class="col-md-8">
|
||||
<h4>
|
||||
<b>{% translate "Most recent fatlinks" %}</b>
|
||||
</h4>
|
||||
</th>
|
||||
<th class="col-md-2 align-self-end">
|
||||
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info"><i class="fa-solid fa-eye fa-fw"></i> {% translate "View statistics" %}</a>
|
||||
</th>
|
||||
<th class="col-md-2 align-self-end">
|
||||
<a href="{% url 'fatlink:create' %}" class="btn btn-success"><i class="fa-solid fa-plus fa-fw"></i> {% translate "Create fatlink" %}</a>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% if fatlinks %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th scope="col" class="text-center">{% translate "Name" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Creator" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Duration" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Edit" %}</th>
|
||||
</tr>
|
||||
{% for link in fatlinks %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">
|
||||
{{ link.duration }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
|
||||
<i class="fa-solid fa-pen-to-square fa-fw"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No created fatlinks on record." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
from allianceauth import hooks
|
||||
|
||||
from . import urls
|
||||
@@ -33,11 +34,43 @@ class GroupManagementMenuItem(MenuItemHook):
|
||||
return ""
|
||||
|
||||
|
||||
"""
|
||||
<li class="d-flex m-2 p-2 pt-0 pb-0 mt-0 mb-0">
|
||||
<i class="fas fa-users fa-fw align-self-center me-2"></i>
|
||||
<a class="nav-link flex-fill align-self-center {% navactive request 'groupmanagement:groups' %}" href="{% url 'groupmanagement:groups' %}">
|
||||
{% translate "Groups" %}
|
||||
</a>
|
||||
</li>
|
||||
"""
|
||||
|
||||
|
||||
class GroupsMenuItem(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(
|
||||
self,
|
||||
text=_("Groups"),
|
||||
classes="fas fa-user fa-fw",
|
||||
url_name="groupmanagement:groups",
|
||||
order=25,
|
||||
navactive=[
|
||||
"groupmanagement:groups", # group list view
|
||||
],
|
||||
)
|
||||
|
||||
def render(self, request):
|
||||
return MenuItemHook.render(self, request)
|
||||
|
||||
|
||||
@hooks.register("menu_item_hook")
|
||||
def register_menu():
|
||||
def register_manager_menu():
|
||||
return GroupManagementMenuItem()
|
||||
|
||||
|
||||
@hooks.register("menu_item_hook")
|
||||
def register_groups_menu():
|
||||
return GroupsMenuItem()
|
||||
|
||||
|
||||
@hooks.register("url_hook")
|
||||
def register_urls():
|
||||
return UrlHook(urls, "group", r"^groups/")
|
||||
|
||||
@@ -1,85 +1,75 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
|
||||
{% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %}
|
||||
{% block header_nav_brand %}{% translate "Audit Log" %} - {{ group.name }}{% endblock header_nav_brand %}
|
||||
|
||||
{% block header_nav_collapse_left %}
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Back" %}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
{% if entries %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="log-entries">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% translate "Date/Time" %}</th>
|
||||
<th scope="col">{% translate "Requestor" %}</th>
|
||||
<th scope="col">{% translate "Character" %}</th>
|
||||
<th scope="col">{% translate "Corporation" %}</th>
|
||||
<th scope="col">{% translate "Type" %}</th>
|
||||
<th scope="col">{% translate "Action" %}</th>
|
||||
<th scope="col">{% translate "Actor" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{{ group }} - {% translate "Audit Log" %}
|
||||
</div>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<td>{{ entry.date|date:"Y-M-d, H:i" }}</td>
|
||||
<td>{{ entry.requestor }}</td>
|
||||
<td>{{ entry.req_char }}</td>
|
||||
<td>{{ entry.req_char.corporation_name }}</td>
|
||||
<td>{{ entry.type_to_str }}</td>
|
||||
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
{% translate "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if entry.request_type is None %}
|
||||
<td>{% translate "Removed" %}</td>
|
||||
{% else %}
|
||||
<td>{{ entry.action_to_str }}</td>
|
||||
{% endif %}
|
||||
|
||||
{% if entries %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="log-entries">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% translate "Date/Time" %}</th>
|
||||
<th scope="col">{% translate "Requestor" %}</th>
|
||||
<th scope="col">{% translate "Character" %}</th>
|
||||
<th scope="col">{% translate "Corporation" %}</th>
|
||||
<th scope="col">{% translate "Type" %}</th>
|
||||
<th scope="col">{% translate "Action" %}</th>
|
||||
<th scope="col">{% translate "Actor" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<td>{{ entry.request_actor }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<td>{{ entry.date|date:"Y-M-d, H:i" }}</td>
|
||||
<td>{{ entry.requestor }}</td>
|
||||
<td>{{ entry.req_char }}</td>
|
||||
<td>{{ entry.req_char.corporation_name }}</td>
|
||||
<td>{{ entry.type_to_str }}</td>
|
||||
|
||||
{% if entry.request_type is None %}
|
||||
<td>{% translate "Removed" %}</td>
|
||||
{% else %}
|
||||
<td>{{ entry.action_to_str }}</td>
|
||||
{% endif %}
|
||||
|
||||
<td>{{ entry.request_actor }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="text-muted">
|
||||
{% translate "All times displayed are EVE/UTC." %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No entries found for this group." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-muted">
|
||||
{% translate "All times displayed are EVE/UTC." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No entries found for this group." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
{% include 'bundles/moment-js.html' with locale=True %}
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% include 'bundles/datatables-css-bs5.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
|
||||
@@ -1,97 +1,86 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
{% load navactive %}
|
||||
|
||||
{% block page_title %}{% translate "Group Members" %}{% endblock page_title %}
|
||||
{% block header_nav_brand %}{% translate "Group Members" %} - {{ group.name }}{% endblock header_nav_brand %}
|
||||
|
||||
{% block header_nav_collapse_left %}
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Back" %}</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
{% if group.user_set %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa" id="tab_group_members">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{{ group.name }} - {% translate 'Members' %}
|
||||
</div>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}">
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ member.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ member.user.username }}
|
||||
{% endif %}
|
||||
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
{% translate "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if member.is_leader %}
|
||||
<i class="fa-solid fa-star"> title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
{% if group.user_set %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa" id="tab_group_members">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}">
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ member.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ member.user.username }}
|
||||
{% endif %}
|
||||
|
||||
{% if member.is_leader %}
|
||||
<i class="fas fa-star" title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ member.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ member.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="text-right">
|
||||
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
|
||||
<i class="glyphicon glyphicon-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="text-muted">
|
||||
<i class="fas fa-star"></i>: {% translate "Group leader" %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No group members to list." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<td>
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ member.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ member.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="text-muted">
|
||||
<i class="fa-solid fa-star"></i>: {% translate "Group leader" %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No group members to list." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% include 'bundles/datatables-css-bs5.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
|
||||
@@ -1,88 +1,86 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
|
||||
{% block page_title %}{% translate "Groups Membership" %}{% endblock page_title %}
|
||||
{% block header_nav_brand %}{% translate "Groups Membership" %}{% endblock header_nav_brand %}
|
||||
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
|
||||
{% block header_nav_collapse_left %}
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Join/Leave Requests" %}</a>
|
||||
</li>
|
||||
{% endblock header_nav_collapse_left %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
{% if groups %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Name" %}</th>
|
||||
<th>{% translate "Description" %}</th>
|
||||
<th>{% translate "Status" %}</th>
|
||||
<th style="white-space: nowrap;" class="text-center">{% translate "Member Count" %}</th>
|
||||
<th style="min-width: 170px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{% translate "Groups" %}
|
||||
</div>
|
||||
<tbody class="align-middle">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
|
||||
</td>
|
||||
|
||||
<div class="panel-body">
|
||||
{% if groups %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Name" %}</th>
|
||||
<th>{% translate "Description" %}</th>
|
||||
<th>{% translate "Status" %}</th>
|
||||
<th style="white-space: nowrap;">{% translate "Member Count" %}</th>
|
||||
<th style="min-width: 170px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<td>{{ group.authgroup.description|linebreaks|urlize }}</td>
|
||||
|
||||
<tbody>
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if group.authgroup.hidden %}
|
||||
<span class="badge bg-info">{% translate "Hidden" %}</span>
|
||||
{% endif %}
|
||||
{% if group.authgroup.open %}
|
||||
<span class="badge bg-success">{% translate "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{% translate "Requestable" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td>{{ group.authgroup.description|linebreaks|urlize }}</td>
|
||||
<td class="text-center">
|
||||
{{ group.num_members }}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{% if group.authgroup.hidden %}
|
||||
<span class="label label-info">{% translate "Hidden" %}</span>
|
||||
{% elif group.authgroup.open %}
|
||||
<span class="label label-success">{% translate "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-default">{% translate "Requestable" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
|
||||
<i class="far fa-eye"></i>
|
||||
</a>
|
||||
|
||||
<td class="text-right">
|
||||
{{ group.num_members }}
|
||||
</td>
|
||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
|
||||
<i class="far fa-list-alt"></i>
|
||||
</a>
|
||||
|
||||
<td class="text-right">
|
||||
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
</a>
|
||||
|
||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
|
||||
<i class="glyphicon glyphicon-list-alt"></i>
|
||||
</a>
|
||||
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ SITE_URL }}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
|
||||
<i class="glyphicon glyphicon-copy"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No groups to list." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
|
||||
<i class="far fa-clipboard"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No groups to list." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/clipboard-js.html' %}
|
||||
{% include "bundles/clipboard-js.html" %}
|
||||
|
||||
<script>
|
||||
new ClipboardJS('#clipboard-copy');
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock extra_javascript %}
|
||||
|
||||
@@ -1,62 +1,93 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Available Groups" %}{% endblock page_title %}
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
{% block header_nav_brand %}{% translate "Available Groups" %}{% endblock header_nav_brand %}
|
||||
|
||||
{% if manager_perms %}
|
||||
{% block header_nav_collapse_left %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}
|
||||
{% if req_count %}
|
||||
<span class="badge bg-secondary">{{ req_count }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Available Groups" %}</h1>
|
||||
{% if groups %}
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Name" %}</th>
|
||||
<th>{% translate "Description" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% if groups %}
|
||||
<table class="table" id="groupsTable" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Name" %}</th>
|
||||
<th>{% translate "Description" %}</th>
|
||||
<th>{% translate "Leaders" %}<span class="m-1 fw-lighter badge bg-primary">{% translate "User" %}</span><span class="m-1 fw-lighter badge bg-secondary ">{% translate "Group" %}</span></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for g in groups %}
|
||||
<tr>
|
||||
<td>{{ g.group.name }}</td>
|
||||
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
|
||||
<td class="text-right">
|
||||
{% if g.group in user.groups.all %}
|
||||
{% if not g.request %}
|
||||
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
|
||||
{% translate "Leave" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif not g.request %}
|
||||
{% if g.group.authgroup.open %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
|
||||
{% translate "Join" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
|
||||
{% translate "Request" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<tbody class>
|
||||
{% for g in groups %}
|
||||
<tr>
|
||||
<td>{{ g.group.name }}</td>
|
||||
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
|
||||
<td style="max-width: 30%;">
|
||||
{% if g.group.authgroup.group_leaders.all.count %}
|
||||
{% for leader in g.group.authgroup.group_leaders.all %}{% if leader.profile.main_character %}<span class="m-1 badge bg-primary">{{leader.profile.main_character}}</span>{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
{% if g.group.authgroup.group_leaders.all.count %}
|
||||
{% for group in g.group.authgroup.group_leader_groups.all %}<span class="badge bg-secondary">{{group.name}}</span>{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
{% if g.group in user_groups %}
|
||||
{% if not g.request %}
|
||||
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
|
||||
{% translate "Leave" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No groups available." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif not g.request %}
|
||||
{% if g.group.authgroup.open %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
|
||||
{% translate "Join" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
|
||||
{% translate "Request" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% translate "No groups available." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js-bs5.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css-bs5.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function () {
|
||||
$('#groupsTable').DataTable();
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -1,166 +1,158 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
{% load navactive %}
|
||||
|
||||
{% block page_title %}{% translate "Groups Management" %}{% endblock page_title %}
|
||||
{% block header_nav_brand %}{% translate "Groups Management" %}{% endblock header_nav_brand %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.nav-tabs > li.active > a {
|
||||
background-color: rgb(236, 240, 241) !important;
|
||||
color: rgb(44, 62, 80);
|
||||
}
|
||||
</style>
|
||||
{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
{% block header_nav_collapse_left %}
|
||||
<li class="active">
|
||||
<a class="nav-link active" id="add-tab" data-bs-toggle="tab" data-bs-target="#add" type="button" role="tab" aria-controls="addd" aria-selected="true">
|
||||
{% translate "Join Requests" %}
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a data-toggle="tab" href="#add">
|
||||
{% translate "Join Requests" %}
|
||||
{% if acceptrequests %}
|
||||
<span class="badge bg-secondary">{{ acceptrequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if acceptrequests %}
|
||||
<span class="badge">{{ acceptrequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% if not auto_leave %}
|
||||
<li>
|
||||
<a class="nav-link" id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave" type="button" role="tab" aria-controls="leave" aria-selected="false">
|
||||
{% translate "Leave Requests" %}
|
||||
|
||||
{% if not auto_leave %}
|
||||
<li>
|
||||
<a data-toggle="tab" href="#leave">
|
||||
{% translate "Leave Requests" %}
|
||||
|
||||
{% if leaverequests %}
|
||||
<span class="badge">{{ leaverequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% if leaverequests %}
|
||||
<span class="badge bg-secondary">{{ leaverequests|length }}</span>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
|
||||
</li>
|
||||
|
||||
<div class="panel panel-default panel-tabs-aa">
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
{% endblock %}
|
||||
|
||||
<div id="add" class="tab-pane active">
|
||||
{% if acceptrequests %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th>{% translate "Group" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for acceptrequest in acceptrequests %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ acceptrequest.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ acceptrequest.group.name }}</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
||||
{% translate "Accept" %}
|
||||
</a>
|
||||
{% block content %}
|
||||
<div class="tab-content">
|
||||
<div id="add" class="tab-pane active">
|
||||
{% if acceptrequests %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th>{% translate "Group" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
||||
{% translate "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% translate "No group add requests." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<tbody class="align-middle">
|
||||
{% for acceptrequest in acceptrequests %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ acceptrequest.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ acceptrequest.group.name }}</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
||||
{% translate "Accept" %}
|
||||
</a>
|
||||
|
||||
{% if not auto_leave %}
|
||||
<div id="leave" class="tab-pane">
|
||||
{% if leaverequests %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th>{% translate "Group" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for leaverequest in leaverequests %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
|
||||
{% if leaverequest.main_char %}
|
||||
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ leaverequest.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ leaverequest.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if leaverequest.main_char %}
|
||||
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ leaverequest.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ leaverequest.group.name }}</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
|
||||
{% translate "Accept" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
|
||||
{% translate "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
||||
{% translate "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% translate "No group add requests." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not auto_leave %}
|
||||
<div id="leave" class="tab-pane">
|
||||
{% if leaverequests %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th>{% translate "Group" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="align-middle">
|
||||
{% for leaverequest in leaverequests %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
|
||||
{% if leaverequest.main_char %}
|
||||
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ leaverequest.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ leaverequest.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if leaverequest.main_char %}
|
||||
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ leaverequest.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ leaverequest.group.name }}</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
|
||||
{% translate "Accept" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
|
||||
{% translate "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,27 +1,10 @@
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">{% translate "Toggle navigation" %}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="{% navactive request 'groupmanagement:management' %}">
|
||||
<a href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
|
||||
</li>
|
||||
<li class="{% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}">
|
||||
<a href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
|
||||
</li>
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
|
||||
</li>
|
||||
|
||||
@@ -64,7 +64,7 @@ class TestViews(TestCase):
|
||||
content = response_content_to_str(response)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('<a data-toggle="tab" href="#leave">', content)
|
||||
self.assertIn('id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
|
||||
self.assertIn('<div id="leave" class="tab-pane">', content)
|
||||
|
||||
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
|
||||
@@ -81,5 +81,5 @@ class TestViews(TestCase):
|
||||
content = response_content_to_str(response)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn('<a data-toggle="tab" href="#leave">', content)
|
||||
self.assertNotIn('id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
|
||||
self.assertNotIn('<div id="leave" class="tab-pane">', content)
|
||||
|
||||
@@ -87,7 +87,7 @@ def group_membership_audit(request, group_id):
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404("Group does not exist")
|
||||
render_items = {'group': group.name}
|
||||
render_items = {'group': group}
|
||||
entries = RequestLog.objects.filter(group=group).order_by('-date')
|
||||
render_items['entries'] = entries
|
||||
|
||||
@@ -311,8 +311,10 @@ def groups_view(request):
|
||||
groups_qs = GroupManager.get_joinable_groups_for_user(
|
||||
request.user, include_hidden=False
|
||||
)
|
||||
groups_qs = groups_qs.order_by('name')
|
||||
groups_qs = groups_qs.order_by('name').select_related("authgroup").prefetch_related('authgroup__group_leaders', 'authgroup__group_leaders__profile__main_character', 'authgroup__group_leader_groups')
|
||||
groups = []
|
||||
|
||||
## TODO see about making this faster
|
||||
for group in groups_qs:
|
||||
group_request = GroupRequest.objects\
|
||||
.filter(user=request.user)\
|
||||
@@ -322,7 +324,14 @@ def groups_view(request):
|
||||
'request': group_request[0] if group_request else None
|
||||
})
|
||||
|
||||
context = {'groups': groups}
|
||||
count = 0
|
||||
perms = GroupManager.can_manage_groups(request.user)
|
||||
if perms:
|
||||
count = GroupManager.pending_requests_count_for_user(request.user)
|
||||
|
||||
user_groups_list = list(request.user.groups.all())
|
||||
context = {'groups': groups, "manager_perms": perms, "req_count":count, "user_groups": user_groups_list}
|
||||
|
||||
return render(request, 'groupmanagement/groups.html', context=context)
|
||||
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ def get_app_modules():
|
||||
|
||||
|
||||
def get_app_submodules(module_name):
|
||||
"""
|
||||
"""pyt
|
||||
Get a specific sub module of the app
|
||||
:param module_name: module name to get
|
||||
:return: name, module tuple
|
||||
@@ -122,3 +122,17 @@ def get_hooks(name):
|
||||
"""
|
||||
register_all_hooks()
|
||||
return _hooks.get(name, [])
|
||||
|
||||
|
||||
class DashboardItemHook:
|
||||
def __init__(self, view_function, order:int=10):
|
||||
self.view_function = view_function
|
||||
self.order = order
|
||||
|
||||
def render(self, request):
|
||||
try:
|
||||
logger.debug(f"Rendering {self.view_function} to dashboard")
|
||||
return self.view_function(request)
|
||||
except Exception as e:
|
||||
logger.exception(f"Rendering {self.view_function} failed!")
|
||||
return ""
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
|
||||
from . import urls
|
||||
from .models import Application
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Choose a Corp" %}{% endblock page_title %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Personal Applications" %}
|
||||
<div class="text-right">
|
||||
<div class="text-end">
|
||||
{% if create %}
|
||||
<a href="{% url 'hrapplications:create_view' %}">
|
||||
<button type="button" class="btn btn-success">{% translate "Create Application" %}</button>
|
||||
@@ -33,11 +33,11 @@
|
||||
<td class="text-center">{{ personal_app.form.corp.corporation_name }}</td>
|
||||
<td class="text-center">
|
||||
{% if personal_app.approved == None %}
|
||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
{% elif personal_app.approved == True %}
|
||||
<div class="label label-success">{% translate "Approved" %}</div>
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@@ -58,7 +58,7 @@
|
||||
{% endif %}
|
||||
{% if perms.auth.human_resources %}
|
||||
<h1 class="page-header text-center">{% translate "Application Management" %}
|
||||
<div class="text-right">
|
||||
<div class="text-end">
|
||||
<!-- Button trigger modal -->
|
||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal">
|
||||
{% translate "Search Applications" %}
|
||||
@@ -91,14 +91,14 @@
|
||||
<td class="text-center">
|
||||
{% if app.approved == None %}
|
||||
{% if app.reviewer_str %}
|
||||
<div class="label label-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
{% else %}
|
||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
{% endif %}
|
||||
{% elif app.approved == True %}
|
||||
<div class="label label-success">{% translate "Approved" %}</div>
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@@ -135,14 +135,14 @@
|
||||
<td class="text-center">
|
||||
{% if app.approved == None %}
|
||||
{% if app.reviewer_str %}
|
||||
<div class="label label-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
{% else %}
|
||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
{% endif %}
|
||||
{% elif app.approved == True %}
|
||||
<div class="label label-success">{% translate "Approved" %}</div>
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="col-lg-12">
|
||||
{% if perms.auth.human_resources %}
|
||||
<h1 class="page-header text-center">{% translate "Application Search Results" %}
|
||||
<div class="text-right">
|
||||
<div class="text-end">
|
||||
<!-- Button trigger modal -->
|
||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal">
|
||||
{% translate "Search Applications" %}
|
||||
@@ -34,11 +34,11 @@
|
||||
<td class="text-center">{{ app.form.corp }}</td>
|
||||
<td class="text-center">
|
||||
{% if app.approved == None %}
|
||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
{% elif app.approved == True %}
|
||||
<div class="label label-success">{% translate "Approved" %}</div>
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
|
||||
9
allianceauth/menu/admin.py
Normal file
9
allianceauth/menu/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
@admin.register(models.MenuItem)
|
||||
class MenuItemAdmin(admin.ModelAdmin):
|
||||
list_display = ['text', 'hide', 'parent', 'url', 'icon_classes', 'rank']
|
||||
ordering = ('rank',)
|
||||
19
allianceauth/menu/apps.py
Normal file
19
allianceauth/menu/apps.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.utils import ProgrammingError, OperationalError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MenuConfig(AppConfig):
|
||||
name = "allianceauth.menu"
|
||||
label = "menu"
|
||||
|
||||
def ready(self):
|
||||
try:
|
||||
logger.debug("Syncing MenuItem Hooks")
|
||||
from allianceauth.menu.providers import MenuItem
|
||||
MenuItem.sync_hook_models()
|
||||
except (ProgrammingError, OperationalError):
|
||||
logger.warning("Migrations not completed for MenuItems")
|
||||
42
allianceauth/menu/hooks.py
Normal file
42
allianceauth/menu/hooks.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class MenuItemHook:
|
||||
"""
|
||||
Auth Hook for generating Side Menu Items
|
||||
"""
|
||||
def __init__(self, text: str, classes: str, url_name: str, order: Optional[int] = None, navactive: List = []):
|
||||
"""
|
||||
:param text: The text shown as menu item, e.g. usually the name of the app.
|
||||
:type text: str
|
||||
:param classes: The classes that should be applied to the menu item icon
|
||||
:type classes: List[str]
|
||||
:param url_name: The name of the Django URL to use
|
||||
:type url_name: str
|
||||
:param order: An integer which specifies the order of the menu item, lowest to highest. Community apps are free to use any order above `1000`. Numbers below are served for Auth.
|
||||
:type order: Optional[int], optional
|
||||
:param navactive: A list of views or namespaces the link should be highlighted on. See [django-navhelper](https://github.com/geelweb/django-navhelper#navactive) for usage. Defaults to the supplied `url_name`.
|
||||
:type navactive: List, optional
|
||||
"""
|
||||
|
||||
self.text = text
|
||||
self.classes = classes
|
||||
self.url_name = url_name
|
||||
self.template = 'public/menuitem.html'
|
||||
self.order = order if order is not None else 9999
|
||||
|
||||
# count is an integer shown next to the menu item as badge when count != None
|
||||
# apps need to set the count in their child class, e.g. in render() method
|
||||
self.count = None
|
||||
|
||||
navactive = navactive or []
|
||||
navactive.append(url_name)
|
||||
self.navactive = navactive
|
||||
|
||||
def render(self, request):
|
||||
return render_to_string(self.template,
|
||||
{'item': self},
|
||||
request=request)
|
||||
28
allianceauth/menu/migrations/0001_initial.py
Normal file
28
allianceauth/menu/migrations/0001_initial.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.0.2 on 2022-08-28 14:00
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MenuItem',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('hook_function', models.CharField(max_length=500)),
|
||||
('icon_classes', models.CharField(max_length=150)),
|
||||
('text', models.CharField(max_length=150)),
|
||||
('url', models.CharField(blank=True, default=None, max_length=2048, null=True)),
|
||||
('rank', models.IntegerField(default=1000)),
|
||||
('hide', models.BooleanField(default=False)),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='menu.menuitem')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.0.2 on 2022-08-28 14:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('menu', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='hook_function',
|
||||
field=models.CharField(blank=True, default=None, max_length=500, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='icon_classes',
|
||||
field=models.CharField(blank=True, default=None, max_length=150, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='text',
|
||||
field=models.CharField(blank=True, default=None, max_length=150, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.0.8 on 2023-02-05 07:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('menu', '0002_alter_menuitem_hook_function_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='menuitem',
|
||||
index=models.Index(fields=['rank'], name='menu_menuit_rank_e880ab_idx'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 4.0.10 on 2023-07-16 11:41
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('menu', '0003_menuitem_menu_menuit_rank_e880ab_idx'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='hide',
|
||||
field=models.BooleanField(default=False, help_text='Hide this menu item. If this item is a header all items under it will be hidden too.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='icon_classes',
|
||||
field=models.CharField(blank=True, default=None, help_text='Font Awesome classes to show as icon on menu', max_length=150, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, help_text='Parent Header. (Optional)', null=True, on_delete=django.db.models.deletion.SET_NULL, to='menu.menuitem'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='rank',
|
||||
field=models.IntegerField(default=1000, help_text='Order of the menu. Lowest First.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='menuitem',
|
||||
name='text',
|
||||
field=models.CharField(blank=True, default=None, help_text='Text to show on menu', max_length=150, null=True),
|
||||
),
|
||||
]
|
||||
0
allianceauth/menu/migrations/__init__.py
Normal file
0
allianceauth/menu/migrations/__init__.py
Normal file
174
allianceauth/menu/models.py
Normal file
174
allianceauth/menu/models.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import logging
|
||||
from allianceauth.hooks import get_hooks
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MenuItem(models.Model):
|
||||
# Auto Generated model from an auth_hook
|
||||
hook_function = models.CharField(
|
||||
max_length=500, default=None, null=True, blank=True)
|
||||
|
||||
# User Made Model
|
||||
icon_classes = models.CharField(
|
||||
max_length=150, default=None, null=True, blank=True, help_text="Font Awesome classes to show as icon on menu")
|
||||
text = models.CharField(
|
||||
max_length=150, default=None, null=True, blank=True, help_text="Text to show on menu")
|
||||
url = models.CharField(max_length=2048, default=None,
|
||||
null=True, blank=True)
|
||||
|
||||
# Put it under a header?
|
||||
parent = models.ForeignKey(
|
||||
'self', on_delete=models.SET_NULL, null=True, blank=True, help_text="Parent Header. (Optional)")
|
||||
|
||||
# Put it where? lowest first
|
||||
rank = models.IntegerField(default=1000, help_text="Order of the menu. Lowest First.")
|
||||
|
||||
# Hide it fully? Hiding a parent will hide all it's children
|
||||
hide = models.BooleanField(default=False, help_text="Hide this menu item. If this item is a header all items under it will be hidden too.")
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=['rank', ]),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
@property
|
||||
def classes(self): # Helper function to make this model closer to the hook functions
|
||||
return self.icon_classes
|
||||
|
||||
@staticmethod
|
||||
def hook_to_name(mh):
|
||||
return f"{mh.__class__.__module__}.{mh.__class__.__name__}"
|
||||
|
||||
@staticmethod
|
||||
def sync_hook_models():
|
||||
# TODO define aa way for hooks to predefine a "parent" to create a sub menu from modules
|
||||
menu_hooks = get_hooks('menu_item_hook')
|
||||
hook_functions = []
|
||||
for hook in menu_hooks:
|
||||
mh = hook()
|
||||
cls = MenuItem.hook_to_name(mh)
|
||||
try:
|
||||
# if it exists update the text only
|
||||
# Users can adjust ranks so lets not change it if they have.
|
||||
mi = MenuItem.objects.get(hook_function=cls)
|
||||
mi.text = getattr(mh, "text", mh.__class__.__name__)
|
||||
mi.save()
|
||||
except MenuItem.DoesNotExist:
|
||||
# This is a new hook, Make the database model.
|
||||
MenuItem.objects.create(
|
||||
hook_function=cls,
|
||||
rank=getattr(mh, "order", 500),
|
||||
text=getattr(mh, "text", mh.__class__.__name__)
|
||||
)
|
||||
hook_functions.append(cls)
|
||||
|
||||
# Get rid of any legacy hooks from modules removed
|
||||
MenuItem.objects.filter(hook_function__isnull=False).exclude(
|
||||
hook_function__in=hook_functions).delete()
|
||||
|
||||
@classmethod
|
||||
def filter_items(cls, menu_item: dict):
|
||||
"""
|
||||
filter any items with no valid children from a menu
|
||||
"""
|
||||
count_items = len(menu_item['items'])
|
||||
if count_items: # if we have children confirm we can see them
|
||||
for i in menu_item['items']:
|
||||
if len(i['render']) == 0:
|
||||
count_items -= 1
|
||||
if count_items == 0: # no children left dont render header
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def render_menu(cls, request):
|
||||
"""
|
||||
Return the sorted side menu items with any items the user can't see removed.
|
||||
"""
|
||||
# Override all the items to the bs5 theme
|
||||
template = "menu/menu-item-bs5.html"
|
||||
# TODO discuss permissions for user defined links
|
||||
|
||||
# Turn all the hooks into functions
|
||||
menu_hooks = get_hooks('menu_item_hook')
|
||||
items = {}
|
||||
for fn in menu_hooks:
|
||||
f = fn()
|
||||
items[cls.hook_to_name(f)] = f
|
||||
|
||||
menu_items = MenuItem.objects.all().order_by("rank")
|
||||
|
||||
menu = {}
|
||||
for mi in menu_items:
|
||||
if mi.hide:
|
||||
# hidden item, skip it completely
|
||||
continue
|
||||
try:
|
||||
_cnt = 0
|
||||
_render = None
|
||||
if mi.hook_function:
|
||||
# This is a module hook, so we need to render it as the developer intended
|
||||
# TODO add a new attribute for apps that want to override it in the new theme
|
||||
items[mi.hook_function].template = template
|
||||
_render = items[mi.hook_function].render(request)
|
||||
_cnt = items[mi.hook_function].count
|
||||
else:
|
||||
# This is a user defined menu item so we render it with defaults.
|
||||
_render = render_to_string(template,
|
||||
{'item': mi},
|
||||
request=request)
|
||||
|
||||
parent = mi.id
|
||||
if mi.parent_id: # Set it if present
|
||||
parent = mi.parent_id
|
||||
|
||||
if parent not in menu: # this will cause the menu headers to be out of order
|
||||
menu[parent] = {"items": [],
|
||||
"count": 0,
|
||||
"render": None,
|
||||
"text": "None",
|
||||
"rank": 9999,
|
||||
}
|
||||
_mi = {
|
||||
"count": _cnt,
|
||||
"render": _render,
|
||||
"text": mi.text,
|
||||
"rank": mi.rank,
|
||||
"classes": (mi.icon_classes if mi.icon_classes != "" else "fas fa-folder"),
|
||||
"hide": mi.hide
|
||||
}
|
||||
|
||||
if parent != mi.id:
|
||||
# this is a sub item
|
||||
menu[parent]["items"].append(_mi)
|
||||
if _cnt:
|
||||
#add its count to the header count
|
||||
menu[parent]["count"] += _cnt
|
||||
else:
|
||||
if len(menu[parent]["items"]):
|
||||
# this is a top folder dont update the count.
|
||||
del(_mi["count"])
|
||||
menu[parent].update(_mi)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
# reset to list
|
||||
menu = list(menu.values())
|
||||
|
||||
# sort the menu list as the parents may be out of order.
|
||||
menu.sort(key=lambda i: i['rank'])
|
||||
|
||||
# ensure no empty groups
|
||||
menu = filter(cls.filter_items, menu)
|
||||
|
||||
return menu
|
||||
10
allianceauth/menu/providers.py
Normal file
10
allianceauth/menu/providers.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from allianceauth.hooks import get_hooks
|
||||
|
||||
from .models import MenuItem
|
||||
|
||||
|
||||
class MenuProvider():
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
7
allianceauth/menu/templates/menu/menu-block.html
Normal file
7
allianceauth/menu/templates/menu/menu-block.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% for data in menu_items %}
|
||||
{% if data.items|length > 0 %}
|
||||
{% include "menu/menu-item-bs5.html" with item=data %}
|
||||
{% else %}
|
||||
{{ data.render }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
32
allianceauth/menu/templates/menu/menu-item-bs5.html
Normal file
32
allianceauth/menu/templates/menu/menu-item-bs5.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
{% if not item.hide %}
|
||||
<li class="d-flex flex-wrap m-2 p-2 pt-0 pb-0 mt-0 mb-0 me-0 pe-0">
|
||||
<i class="nav-link {{ item.classes }} fa-fw align-self-center me-3 {% if item.navactive %}{% navactive request item.navactive|join:' ' %}{% endif %}" {% if item.items|length %} type="button" data-bs-toggle="collapse" data-bs-target="#id-{{ item.text|slugify }}" aria-expanded="false" aria-controls="" {% endif %}></i>
|
||||
<a class="nav-link flex-fill align-self-center" {% if item.items|length %} type="button" data-bs-toggle="collapse" data-bs-target="#id-{{ item.text|slugify }}" aria-expanded="false" aria-controls="" {% endif %}
|
||||
href="{% if item.url_name %}{% url item.url_name %}{% else %}{{ item.url }}{% endif %}">
|
||||
{% translate item.text %}
|
||||
</a>
|
||||
{% if item.count >= 1 %}
|
||||
<span class="badge bg-primary m-2 align-self-center {% if item.items|length == 0 %}me-4{% endif %}">
|
||||
{{ item.count }}
|
||||
</span>
|
||||
{% elif item.url %}
|
||||
<span class="pill m-2 me-4 align-self-center fas fa-external-link-alt"></span>
|
||||
{% endif %}
|
||||
{% if item.items|length > 0 %}
|
||||
<span class="pill m-2 me-4 align-self-center fas fa-solid fa-chevron-down"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#id-{{ item.text|slugify }}"
|
||||
aria-expanded="false"
|
||||
aria-controls=""></span>
|
||||
<!--<hr class="m-0 w-100">-->
|
||||
<ul class="collapse ps-1 w-100 border-start rounded-start border-light border-3" id="id-{{ item.text|slugify }}">
|
||||
{% for sub_item in item.items %}
|
||||
{{ sub_item.render }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
{% load auth_notifications %}
|
||||
<li class="nav-item {% navactive request 'notifications:' %}"
|
||||
id="menu_item_notifications">
|
||||
<a class="nav-link"
|
||||
href="{% url 'notifications:list' %}">
|
||||
<span class="fa">
|
||||
{% with unread_count=request.user|user_unread_notification_count %}
|
||||
<i class="fas fa-bell{% if unread_count %} text-danger{% endif %}"></i>
|
||||
{% endwith %}
|
||||
</span>
|
||||
<span class="d-lg-none d-md-inline m-2">
|
||||
{% translate "Notifications" %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
57
allianceauth/menu/templates/menu/menu-user.html
Normal file
57
allianceauth/menu/templates/menu/menu-user.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
{% load theme_tags %}
|
||||
|
||||
<div style="z-index:5;" class="w100 d-flex flex-column justify-content-center align-items-center text-center pb-2">
|
||||
{% if user.is_authenticated %}
|
||||
{% if request.user.profile.main_character %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<div class="p-2 position-relative m-2">
|
||||
<img class="rounded-circle" src="{{ main.character_id|character_portrait_url:64 }}" alt="{{ main.character_name }}">
|
||||
<img class="rounded-circle position-absolute bottom-0 start-0" src="{{ main.corporation_logo_url_32 }}" alt="{{ main.corporation_name }}">
|
||||
{% if main.alliance_id %}
|
||||
<img class="rounded-circle position-absolute bottom-0 end-0" src="{{ main.alliance_logo_url_32 }}" alt="{{ main.alliance_name }}">
|
||||
{% elif main.faction_id %}
|
||||
<img class="rounded-circle position-absolute bottom-0 end-0" src="{{ main.faction_logo_url_32 }}" alt="{{ main.faction_name }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
<h5>{{ main.character_name }}</h5>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<img class="rounded-circle m-2" src="{{ 1|character_portrait_url:32 }}" alt="{% translate 'No Main Character!' %}">
|
||||
<h5>{% translate "No Main Character!" %}</h5>
|
||||
{% endif %}
|
||||
|
||||
{% theme_select %}
|
||||
{% endif %}
|
||||
|
||||
<div class="btn-group m-2">
|
||||
<button type="button" class="btn btn-secondary p-1">
|
||||
{% include "public/lang_select.html" %}
|
||||
</button>
|
||||
|
||||
{% if user.is_superuser %}
|
||||
<a role="button" class="btn btn btn-secondary d-flex" href="{% url 'admin:index' %}">
|
||||
<span class="align-self-center">{% translate "Admin" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="btn-group m-2">
|
||||
{% if user.is_authenticated %}
|
||||
<a role="button" class="btn btn-info" href="{% url 'authentication:token_management' %}" title="Token Management"><i class="fa-solid fa-user-lock fa-fw"></i></a>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_superuser %}
|
||||
<a role="button" class="btn btn-info" href="https://allianceauth.readthedocs.io/" title="Alliance Auth Documentation"><i class="fa-solid fa-book fa-fw"></i></a>
|
||||
<a role="button" class="btn btn-info" href="https://discord.gg/fjnHAmk" title="Alliance Auth Discord"><i class="fa-brands fa-discord fa-fw"></i></a>
|
||||
<a role="button" class="btn btn-info" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i></a>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<a role="button" class="btn btn-danger" href="{% url 'logout' %}" title="{% translate 'Sign Out' %}"><i class="fa-solid fa-right-from-bracket fa-fw"></i></a>
|
||||
{% else %}
|
||||
<a role="button" class="btn btn-success" href="{% url 'authentication:login' %}" title="{% translate 'Sign In' %}"> <i class="fa-solid fa-right-to-bracket fa-fw"></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
25
allianceauth/menu/templates/menu/sortable-side-menu.html
Normal file
25
allianceauth/menu/templates/menu/sortable-side-menu.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
{% load menu_menu_items %}
|
||||
|
||||
<div class="col-auto px-0 " >
|
||||
<div class="collapse collapse-horizontal" tabindex="-1" id="sidebar" >
|
||||
<div style="width: 350px;">
|
||||
<div class="nav-padding navbar-dark bg-dark text-light px-0 d-flex flex-column overflow-hidden vh-100 auth-logo" >
|
||||
{% if user.is_authenticated %}
|
||||
<ul style="z-index:5;" id="sidebar-menu" class="navbar-nav flex-column mb-auto overflow-auto pt-2">
|
||||
<li class="d-flex flex-wrap m-2 p-2 pt-0 pb-0 mt-0 mb-0 me-0 pe-0">
|
||||
<i class="nav-link fas fa-tachometer-alt fa-fw align-self-center me-3 {% navactive request 'authentication:dashboard' %}"></i>
|
||||
<a class="nav-link flex-fill align-self-center" href="{% url 'authentication:dashboard' %}">
|
||||
{% translate "Dashboard" %}
|
||||
</a>
|
||||
</li>
|
||||
{% sorted_menu_items %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% include 'menu/menu-user.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
0
allianceauth/menu/templatetags/__init__.py
Normal file
0
allianceauth/menu/templatetags/__init__.py
Normal file
@@ -1,6 +1,9 @@
|
||||
from django import template
|
||||
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.menu.models import MenuItem
|
||||
|
||||
from ..providers import MenuProvider
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -21,3 +24,12 @@ def menu_items(context):
|
||||
return {
|
||||
'menu_items': process_menu_items(get_hooks('menu_item_hook'), request),
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('menu/menu-block.html', takes_context=True)
|
||||
def sorted_menu_items(context):
|
||||
request = context['request']
|
||||
menu_items = MenuItem.render_menu(request)
|
||||
return {
|
||||
'menu_items':menu_items
|
||||
}
|
||||
@@ -1,37 +1,51 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Notifications" %}{% endblock %}
|
||||
{% block page_title %}
|
||||
{% translate "Notifications" %}
|
||||
{% endblock page_title %}
|
||||
|
||||
{% block header_nav_brand %}
|
||||
{% translate "Notifications" %}
|
||||
{% endblock header_nav_brand %}
|
||||
|
||||
{% block header_nav_collapse_left %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="unread-tab" data-bs-toggle="tab" data-bs-target="#unread" type="button" role="tab" aria-controls="unread" aria-selected="true">
|
||||
{% translate "Unread" %}
|
||||
<span class="badge bg-secondary">{{ unread|length }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="read-tab" data-bs-toggle="tab" data-bs-target="#read" type="button" role="tab" aria-controls="read" aria-selected="false">
|
||||
{% translate "Read" %}
|
||||
<span class="badge bg-secondary">{{ read|length }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block header_nav_collapse_right %}
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'notifications:mark_all_read' %}" class="nav-link" title="{% translate 'Mark all notifications as read' %}">
|
||||
<i class="fas fa-check-double"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'notifications:delete_all_read' %}" class="nav-link" title="{% translate 'Delete all read notifications' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">{% translate "Notifications" %}</h1>
|
||||
|
||||
<div class="panel panel-default">
|
||||
|
||||
<div class="panel-heading clearfix">
|
||||
<ul class="nav nav-pills navbar-left">
|
||||
<li class="active"><a data-toggle="tab" href="#unread">{% translate "Unread" %}<b>({{ unread|length }})</b></a></li>
|
||||
<li><a data-toggle="tab" href="#read">{% translate "Read" %} <b>({{ read|length }})</b></a></li>
|
||||
</ul>
|
||||
<div class="nav navbar-nav navbar-right" style="margin-right: 0;">
|
||||
<a href="{% url 'notifications:mark_all_read' %}" class="btn btn-warning">{% translate "Mark All Read" %}</a>
|
||||
<a href="{% url 'notifications:delete_all_read' %}" class="btn btn-danger">{% translate "Delete All Read" %}</a>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade active show" id="unread">
|
||||
{% include "notifications/list_partial.html" with notifications=unread %}
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
|
||||
<div id="unread" class="tab-pane fade in active">
|
||||
{% include "notifications/list_partial.html" with notifications=unread %}
|
||||
</div>
|
||||
|
||||
<div id="read" class="tab-pane fade">
|
||||
{% include "notifications/list_partial.html" with notifications=read %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="read" class="tab-pane fade">
|
||||
{% include "notifications/list_partial.html" with notifications=read %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% if notifications %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Timestamp" %}</th>
|
||||
<th class="text-center">{% translate "Title" %}</th>
|
||||
<th class="text-center">{% translate "Action" %}</th>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>{% translate "Timestamp" %}</th>
|
||||
<th>{% translate "Title" %}</th>
|
||||
<th class="text-end">{% translate "Action" %}</th>
|
||||
</tr>
|
||||
{% for notif in notifications %}
|
||||
<tr class="table-{{ notif.level }}">
|
||||
<td>{{ notif.timestamp }}</td>
|
||||
<td>{{ notif.title }}</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'notifications:view' notif.id %}" class="btn btn-primary btn-sm" title="View">
|
||||
<span class="fas fa-eye"></span>
|
||||
</a>
|
||||
<a href="{% url 'notifications:remove' notif.id %}" class="btn btn-danger btn-sm" title="Remove">
|
||||
<span class="fas fa-trash"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% for notif in notifications %}
|
||||
<tr class="{{ notif.level }}">
|
||||
<td class="text-center">{{ notif.timestamp }}</td>
|
||||
<td class="text-center">{{ notif.title }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'notifications:view' notif.id %}" class="btn btn-primary" title="View">
|
||||
<span class="glyphicon glyphicon-eye-open"></span>
|
||||
</a>
|
||||
<a href="{% url 'notifications:remove' notif.id %}" class="btn btn-danger" title="Remove">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-default text-center">{% translate "No notifications." %}</div>
|
||||
<div class="alert alert-info">{% translate "No notifications." %}</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "View Notification" %}{% endblock page_title %}
|
||||
{% block page_title %}
|
||||
{% translate "View Notification" %}
|
||||
{% endblock page_title %}
|
||||
|
||||
{% block header_nav_brand %}
|
||||
{% translate "View Notification" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">
|
||||
{% translate "View Notification" %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'notifications:list' %}" class="btn btn-primary btn-lg">
|
||||
<span class="glyphicon glyphicon-arrow-left"></span>
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-{{ notif.level }}">
|
||||
<div class="panel-heading">{{ notif.timestamp }} {{ notif.title }}</div>
|
||||
<div class="panel-body"><pre>{{ notif.message }}</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end mb-4">
|
||||
<a href="{% url 'notifications:list' %}" class="btn btn-primary">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card border-{{ notif.level }}">
|
||||
<div class="card-header bg-{{ notif.level }}">
|
||||
{{ notif.timestamp }}
|
||||
{{ notif.title }}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<pre>{{ notif.message }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
from allianceauth.optimer.views import dashboard_ops
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from . import urls
|
||||
@@ -27,3 +29,17 @@ def register_menu():
|
||||
@hooks.register('url_hook')
|
||||
def register_url():
|
||||
return UrlHook(urls, 'optimer', r'^optimer/')
|
||||
|
||||
|
||||
class NextOpsHook(hooks.DashboardItemHook):
|
||||
def __init__(self): #TODO add the view permms so if they cant see it is not rendered
|
||||
hooks.DashboardItemHook.__init__(
|
||||
self,
|
||||
dashboard_ops,
|
||||
6
|
||||
)
|
||||
|
||||
|
||||
@hooks.register('dashboard_hook')
|
||||
def register_groups_hook():
|
||||
return NextOpsHook()
|
||||
|
||||
40
allianceauth/optimer/templates/optimer/dashboard.ops.html
Normal file
40
allianceauth/optimer/templates/optimer/dashboard.ops.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
<div class="col-12 col-xl-6 align-self-stretch p-2">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title text-center">{% translate "Upcoming Fleets" %}</h4>
|
||||
<div class="card-body">
|
||||
<div style="height: 300px; overflow-y:auto;">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% translate "Operation" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "Form Up System" %}</th>
|
||||
<th class="text-center">{% translate "Start Time" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ops in timers %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
{{ ops.operation_name }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
({{ ops.type }})
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ ops.system|dotlan_solar_system_url }}">{{ ops.system }}</a>
|
||||
</td>
|
||||
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
<th>{% translate "FC" %}</th>
|
||||
{% if perms.auth.optimer_management %}
|
||||
{# <th>{% translate "Creator" %}</th>#}
|
||||
<th class="text-right" style="width: 150px;">{% translate "Action" %}</th>
|
||||
<th class="text-end" style="width: 150px;">{% translate "Action" %}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -40,7 +40,7 @@
|
||||
<td>{{ ops.fc }}</td>
|
||||
{% if perms.auth.optimer_management %}
|
||||
{# <td>{{ ops.eve_character }}</td>#}
|
||||
<td class="text-right">
|
||||
<td class="text-end">
|
||||
<a href="{% url 'optimer:remove' ops.id %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% translate "Fleet Operation Timers" %}
|
||||
<div class="text-right">
|
||||
<div class="text-end">
|
||||
{% if perms.auth.optimer_management %}
|
||||
<a href="{% url 'optimer:add' %}" class="btn btn-success">{% translate "Create Operation" %}</a>
|
||||
{% endif %}
|
||||
@@ -16,10 +16,10 @@
|
||||
</h1>
|
||||
|
||||
<div class="col-lg-12 text-center row">
|
||||
<div class="label label-info text-left">
|
||||
<div class="badge bg-info text-start">
|
||||
<b>{% translate "Current Eve Time:" %} </b>
|
||||
</div>
|
||||
<strong class="label label-info text-left" id="current-time"></strong>
|
||||
<strong class="badge bg-info text-start" id="current-time"></strong>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import render, redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .form import OpForm
|
||||
|
||||
from .form import OpForm
|
||||
from .models import OpTimer, OpTimerType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -137,3 +138,15 @@ def edit_optimer(request, optimer_id):
|
||||
}
|
||||
form = OpForm(initial=data, data_list=OpTimerType.objects.all())
|
||||
return render(request, 'optimer/update.html', context={'form': form})
|
||||
|
||||
|
||||
def dashboard_ops(request):
|
||||
base_query = OpTimer.objects.select_related('eve_character', 'type')
|
||||
timers = base_query.filter(start__gte=timezone.now())[:5]
|
||||
if timers.count():
|
||||
context = {
|
||||
'timers': timers,
|
||||
}
|
||||
return render_to_string('optimer/dashboard.ops.html', context=context, request=request)
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
from . import urls
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
|
||||
|
||||
class PermissionsTool(MenuItemHook):
|
||||
|
||||
@@ -1,93 +1,95 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{{ permission.permission.codename }} - {% translate "Permissions Audit" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ permission.permission.codename }} - {% translate "Permissions Audit" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div>
|
||||
<h1 class="page-header">{% translate "Permissions Audit" %}: {{ permission.permission.codename }}</h1>
|
||||
<p>
|
||||
<a href="{% url 'permissions_tool:overview' %}" class="btn btn-default">
|
||||
<i class="glyphicon glyphicon-chevron-left"></i> {% translate "Back" %}
|
||||
<a href="{% url 'permissions_tool:overview' %}" class="btn btn-primary">
|
||||
<i class="fa-solid fa-chevron-left"></i> {% translate "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="tab_permissions_audit">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Group" %}</th>
|
||||
<th></th>
|
||||
<th>{% translate "User / Character" %}</th>
|
||||
<th>{% translate "Organization" %}</th>
|
||||
<th scope="col">{% translate "Group" %}</th>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">{% translate "User / Character" %}</th>
|
||||
<th scope="col">{% translate "Organization" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in permission.users %}
|
||||
{% include 'permissions_tool/audit_row.html' with type="User" name="Permission granted directlty" %}
|
||||
{% endfor %}
|
||||
{% for group in permission.groups %}
|
||||
{% for user in group.user_set.all %}
|
||||
{% include 'permissions_tool/audit_row.html' with type="Group" name=group%}
|
||||
{% for user in permission.users %}
|
||||
{% include "permissions_tool/audit_row.html" with type="User" name="Permission granted directly" %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for state in permission.states %}
|
||||
{% for profile in state.userprofile_set.all %}
|
||||
{% with profile.user as user %}
|
||||
{% include 'permissions_tool/audit_row.html' with type="State" name=state%}
|
||||
{% endwith %}
|
||||
{% for group in permission.groups %}
|
||||
{% for user in group.user_set.all %}
|
||||
{% include "permissions_tool/audit_row.html" with type="Group" name=group %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for state in permission.states %}
|
||||
{% for profile in state.userprofile_set.all %}
|
||||
{% with profile.user as user %}
|
||||
{% include "permissions_tool/audit_row.html" with type="State" name=state %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
{% include "bundles/filterdropdown-js.html" %}
|
||||
{% endblock extra_javascript %}
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "bundles/datatables-css-bs5.html" %}
|
||||
{% endblock extra_css %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function() {
|
||||
$(document).ready(function () {
|
||||
let groupColumn = 0;
|
||||
|
||||
$('#tab_permissions_audit').DataTable({
|
||||
columnDefs: [
|
||||
{ "visible": false, "targets": groupColumn }
|
||||
columnDefs: [{
|
||||
"visible": false,
|
||||
"targets": groupColumn
|
||||
}],
|
||||
order: [
|
||||
[groupColumn, 'asc'],
|
||||
[2, 'asc']
|
||||
],
|
||||
order: [[ groupColumn, 'asc' ], [ 2, 'asc' ] ],
|
||||
filterDropDown:
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
idx: 0,
|
||||
title: 'Source'
|
||||
}
|
||||
],
|
||||
filterDropDown: {
|
||||
columns: [{
|
||||
idx: 0,
|
||||
title: 'Source'
|
||||
}],
|
||||
bootstrap: true
|
||||
},
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
drawCallback: function ( settings ) {
|
||||
drawCallback: function (settings) {
|
||||
let api = this.api();
|
||||
let rows = api.rows( {page:'current'} ).nodes();
|
||||
let rows = api.rows({
|
||||
page: 'current'
|
||||
}).nodes();
|
||||
let last = null;
|
||||
|
||||
api.column(groupColumn, {page:'current'} ).data().each( function ( group, i ) {
|
||||
if ( last !== group ) {
|
||||
$(rows).eq( i ).before(
|
||||
'<tr class="tr-group"><td colspan="3">' + group + '</td></tr>'
|
||||
api.column(groupColumn, {
|
||||
page: 'current'
|
||||
}).data().each(function (group, i) {
|
||||
if (last !== group) {
|
||||
$(rows).eq(i).before(
|
||||
'<tr class="h4 table-secondary"><td colspan="3">' + group + '</td></tr>'
|
||||
);
|
||||
|
||||
last = group;
|
||||
}
|
||||
} );
|
||||
});
|
||||
}
|
||||
} );
|
||||
} );
|
||||
{% endblock %}
|
||||
});
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -1,25 +1,17 @@
|
||||
{% load evelinks %}
|
||||
|
||||
{% load i18n %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ type }}: {{ name }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<img src="{{ user.profile.main_character|character_portrait_url:32 }}" class="img-circle" alt="{{ user.profile.main_character.character_name }}">
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ user }}<br></strong>
|
||||
{{ user.profile.main_character.character_name }}
|
||||
</td>
|
||||
<td class="text-left">
|
||||
<td>{{ type }}: {{ name }}</td>
|
||||
<td class="text-end">
|
||||
<img src="{{ user.profile.main_character|character_portrait_url:32 }}" class="img-circle" alt="{{ user.profile.main_character.character_name }}"></td>
|
||||
<td><strong>{{ user }}<br></strong>{{ user.profile.main_character.character_name }}</td>
|
||||
<td class="text-start">
|
||||
{% if user.profile.main_character %}
|
||||
<a href="{{ user.profile.main_character|dotlan_corporation_url }}" target="_blank">
|
||||
{{ user.profile.main_character.corporation_name }}
|
||||
</a><br>
|
||||
<a href="{{ user.profile.main_character|dotlan_corporation_url }}" target="_blank">{{ user.profile.main_character.corporation_name }}</a>
|
||||
<br>
|
||||
{{ user.profile.main_character.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
(unknown)
|
||||
{% translate "(unknown)" %}
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,128 +1,107 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% extends "allianceauth/base-bs5.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Permissions Overview" %}{% endblock page_title %}
|
||||
|
||||
{% block page_title %}
|
||||
{% translate "Permissions Overview" %}
|
||||
{% endblock page_title %}
|
||||
{% block content %}
|
||||
<div class="col-sm-12">
|
||||
<h1 class="page-header">{% translate "Permissions Overview" %}</h1>
|
||||
<p>
|
||||
{% if request.GET.all != 'yes' %}
|
||||
{% blocktranslate %}Showing only applied permissions{% endblocktranslate %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=yes" class="btn btn-primary">{% translate "Show All" %}</a>
|
||||
{% else %}
|
||||
{% blocktranslate %}Showing all permissions{% endblocktranslate %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=no" class="btn btn-primary">{% translate "Show Applied" %}</a>
|
||||
{% endif %}
|
||||
{% if request.GET.all != 'yes' %}
|
||||
{% translate "Showing only applied permissions" %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=yes" class="btn btn-primary">{% translate "Show All" %}</a>
|
||||
{% else %}
|
||||
{% translate "Showing all permissions" %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=no" class="btn btn-primary">{% translate "Show Applied" %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="tab_permissions_overview" style="width:100%">
|
||||
<table class="table table-striped" id="tab_permissions_overview">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% translate "App" %}
|
||||
</th>
|
||||
<th>
|
||||
{% translate "Model" %}
|
||||
</th>
|
||||
<th>
|
||||
{% translate "Code Name" %}
|
||||
</th>
|
||||
<th>
|
||||
{% translate "Name" %}
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
{% translate "Users" %}
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
{% translate "Groups" %}
|
||||
</th>
|
||||
<th class="col-md-1">
|
||||
{% translate "States" %}
|
||||
</th>
|
||||
<th scope="col">{% translate "App" %}</th>
|
||||
<th scope="col">{% translate "Model" %}</th>
|
||||
<th scope="col">{% translate "Code Name" %}</th>
|
||||
<th scope="col">{% translate "Name" %}</th>
|
||||
<th scope="col">{% translate "Users" %}</th>
|
||||
<th scope="col">{% translate "Groups" %}</th>
|
||||
<th scope="col">{% translate "States" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for perm in permissions %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ perm.permission.content_type.app_label }}
|
||||
</td>
|
||||
<td>
|
||||
{{ perm.permission.content_type.model }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url "permissions_tool:audit" app_label=perm.permission.content_type.app_label model=perm.permission.content_type.model codename=perm.permission.codename %}">
|
||||
{{ perm.permission.codename }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ perm.permission.name }}
|
||||
</td>
|
||||
<td class="{% if perm.users > 0 %}info {% endif %}text-right">
|
||||
{{ perm.users }}
|
||||
</td>
|
||||
<td class="{% if perm.groups > 0 %}info {% endif %}text-right">
|
||||
{{ perm.groups }} ({{ perm.group_users }})
|
||||
</td>
|
||||
<td class="{% if perm.states > 0 %}info {% endif %}text-right">
|
||||
{{ perm.states }} ({{ perm.state_users }})
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for perm in permissions %}
|
||||
<tr>
|
||||
<td>{{ perm.permission.content_type.app_label }}</td>
|
||||
<td>{{ perm.permission.content_type.model }}</td>
|
||||
<td>
|
||||
<a href="{% url "permissions_tool:audit" app_label=perm.permission.content_type.app_label model=perm.permission.content_type.model codename=perm.permission.codename %}">
|
||||
{{ perm.permission.codename }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ perm.permission.name }}</td>
|
||||
<td class="{% if perm.users > 0 %}info{% endif %}text-end">{{ perm.users }}</td>
|
||||
<td class="{% if perm.groups > 0 %}info{% endif %}text-end">{{ perm.groups }} ({{ perm.group_users }})</td>
|
||||
<td class="{% if perm.states > 0 %}info{% endif %}text-end">{{ perm.states }} ({{ perm.state_users }})</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "bundles/datatables-js-bs5.html" %}
|
||||
{% include "bundles/filterdropdown-js.html" %}
|
||||
{% endblock extra_javascript %}
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "bundles/datatables-css-bs5.html" %}
|
||||
{% endblock extra_css %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function() {
|
||||
let groupColumn = 0;
|
||||
|
||||
$('#tab_permissions_overview').DataTable({
|
||||
columnDefs: [
|
||||
{ "visible": false, "targets": groupColumn }
|
||||
],
|
||||
order: [[ groupColumn, 'asc' ], [ 1, 'asc' ], [ 2, 'asc' ] ],
|
||||
filterDropDown:
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
idx: 0
|
||||
},
|
||||
{
|
||||
idx: 1
|
||||
}
|
||||
],
|
||||
bootstrap: true,
|
||||
},
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
drawCallback: function ( settings ) {
|
||||
let api = this.api();
|
||||
let rows = api.rows( {page:'current'} ).nodes();
|
||||
let last = null;
|
||||
|
||||
api.column(groupColumn, {page:'current'} ).data().each( function ( group, i ) {
|
||||
if ( last !== group ) {
|
||||
$(rows).eq( i ).before(
|
||||
'<tr class="tr-group"><td colspan="6">' + group + '</td></tr>'
|
||||
);
|
||||
|
||||
last = group;
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
{% endblock %}
|
||||
$(document).ready(function () {
|
||||
let groupColumn = 0;
|
||||
$('#tab_permissions_overview').DataTable({
|
||||
columnDefs: [{
|
||||
"visible": false,
|
||||
"targets": groupColumn
|
||||
}],
|
||||
order: [
|
||||
[groupColumn, 'asc'],
|
||||
[1, 'asc'],
|
||||
[2, 'asc']
|
||||
],
|
||||
filterDropDown: {
|
||||
columns: [{
|
||||
idx: 0
|
||||
},
|
||||
{
|
||||
idx: 1
|
||||
}
|
||||
],
|
||||
bootstrap: true,
|
||||
},
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
drawCallback: function (settings) {
|
||||
let api = this.api();
|
||||
let rows = api.rows({
|
||||
page: 'current'
|
||||
}).nodes();
|
||||
let last = null;
|
||||
api.column(groupColumn, {
|
||||
page: 'current'
|
||||
}).data().each(function (group, i) {
|
||||
if (last !== group) {
|
||||
$(rows).eq(i).before(
|
||||
'
|
||||
<tr class="h4 table-secondary">
|
||||
<td colspan="6">' + group + '</td>
|
||||
</tr>
|
||||
'
|
||||
);
|
||||
last = group;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -3,7 +3,7 @@ from django import urls
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from allianceauth.menu.models import MenuItem
|
||||
|
||||
class PermissionsToolViewsTestCase(WebTest):
|
||||
def setUp(self):
|
||||
@@ -34,13 +34,19 @@ class PermissionsToolViewsTestCase(WebTest):
|
||||
self.member.user_permissions.add(self.permission)
|
||||
AuthUtils.connect_signals()
|
||||
|
||||
# TODO find a nicer way to do this later
|
||||
MenuItem.sync_hook_models()
|
||||
|
||||
def test_menu_item(self):
|
||||
# If we change the side menu again this will fail again.
|
||||
self.app.set_user(self.member)
|
||||
response = self.app.get(urls.reverse('permissions_tool:overview'))
|
||||
|
||||
response_content = response.content.decode('utf-8')
|
||||
response_content = response.content.decode(response.charset)
|
||||
|
||||
self.assertInHTML('<li><a class="active" href="/permissions/overview/"><i class="fas fa-id-card fa-fw"></i> Permissions Audit</a></li>', response_content)
|
||||
self.assertIn("fa-id-card", response_content)
|
||||
self.assertIn('href="/permissions/overview/"', response_content)
|
||||
self.assertIn("Permissions Audit", response_content)
|
||||
|
||||
def test_permissions_overview(self):
|
||||
self.app.set_user(self.member)
|
||||
|
||||
@@ -22,6 +22,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.humanize',
|
||||
'django_celery_beat',
|
||||
'bootstrapform',
|
||||
'django_bootstrap5', # https://github.com/zostera/django-bootstrap5
|
||||
'sortedm2m',
|
||||
'esi',
|
||||
'allianceauth.authentication',
|
||||
@@ -31,6 +32,11 @@ INSTALLED_APPS = [
|
||||
'allianceauth.notifications',
|
||||
'allianceauth.thirdparty.navhelper',
|
||||
'allianceauth.analytics',
|
||||
'allianceauth.menu',
|
||||
'allianceauth.theme',
|
||||
'allianceauth.theme.darkly',
|
||||
'allianceauth.theme.flatly',
|
||||
'allianceauth.theme.materia',
|
||||
]
|
||||
|
||||
SECRET_KEY = "wow I'm a really bad default secret key"
|
||||
@@ -75,7 +81,6 @@ MIDDLEWARE = [
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'allianceauth.analytics.middleware.AnalyticsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'allianceauth.urls'
|
||||
@@ -189,6 +194,9 @@ DATABASES = {
|
||||
|
||||
SITE_NAME = 'Alliance Auth'
|
||||
|
||||
DEFAULT_THEME = "allianceauth.theme.flatly.auth_hooks.FlatlyThemeHook"
|
||||
DEFAULT_THEME_DARK = "allianceauth.theme.darkly.auth_hooks.DarklyThemeHook" # Legacy AAv3 user.profile.night_mode=1
|
||||
|
||||
LOGIN_URL = 'auth_login_user' # view that handles login logic
|
||||
|
||||
LOGIN_REDIRECT_URL = 'authentication:dashboard' # default destination when logging in if no redirect specified
|
||||
|
||||
@@ -26,7 +26,7 @@ DEBUG = False
|
||||
|
||||
# Add any additional apps to this list.
|
||||
INSTALLED_APPS += [
|
||||
|
||||
#'allianceauth.theme.bootstrap',
|
||||
]
|
||||
|
||||
# To change the logging level for extensions, uncomment the following line.
|
||||
|
||||
@@ -10,10 +10,11 @@ startsecs=10
|
||||
priority=998
|
||||
|
||||
[program:worker]
|
||||
command={{ celery }} -A {{ project_name }} worker
|
||||
command={{ celery }} -A {{ project_name }} worker --pool=threads --concurrency=5 -n %(program_name)s_%(process_num)02d
|
||||
directory={{ project_directory }}
|
||||
user=allianceserver
|
||||
numprocs=1
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
stdout_logfile={{ project_directory }}/log/worker.log
|
||||
stderr_logfile={{ project_directory }}/log/worker.log
|
||||
autostart=true
|
||||
@@ -35,6 +36,14 @@ autorestart=true
|
||||
stopsignal=INT
|
||||
|
||||
{% endif %}
|
||||
|
||||
[eventlistener:memmon]
|
||||
command={{ memmon }} -p worker_00=256MB -p gunicorn=256MB
|
||||
directory={{ project_directory }}
|
||||
events=TICK_60
|
||||
stdout_logfile={{ project_directory }}/log/memmon.log
|
||||
stderr_logfile={{ project_directory }}/log/memmon.log
|
||||
|
||||
[group:{{ project_name }}]
|
||||
programs=beat,worker{% if gunicorn %},gunicorn{% endif %}
|
||||
priority=999
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .hooks import MenuItemHook
|
||||
from ..menu.hooks import MenuItemHook
|
||||
from .hooks import ServicesHook
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from string import Formatter
|
||||
from django.urls import include, re_path
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from django.conf import settings
|
||||
@@ -8,6 +9,11 @@ from django.urls import include, re_path
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.menu.hooks import MenuItemHook
|
||||
from django.conf import settings
|
||||
from django.urls import include, re_path
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from .models import NameFormatConfig
|
||||
|
||||
@@ -135,26 +141,15 @@ class ServicesHook:
|
||||
yield fn()
|
||||
|
||||
|
||||
class MenuItemHook:
|
||||
def __init__(self, text, classes, url_name, order=None, navactive=list([])):
|
||||
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
|
||||
class MenuItemHook(MenuItemHook):
|
||||
"""
|
||||
MenuItemHook shim to allianceauth.menu.hooks
|
||||
|
||||
# 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)
|
||||
:param MenuItemHook: _description_
|
||||
:type MenuItemHook: _type_
|
||||
"""
|
||||
def __init_subclass__(cls) -> None:
|
||||
return super().__init_subclass__()
|
||||
|
||||
|
||||
class UrlHook:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user