mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
152ebf86f9 | ||
|
|
68069d1043 | ||
|
|
9ff926ae4d | ||
|
|
b28cbdad31 | ||
|
|
3c1bae463e | ||
|
|
91fbdb9ec1 | ||
|
|
c1abc56ebc | ||
|
|
f1582165bc | ||
|
|
0f155369a1 | ||
|
|
1e822729c3 | ||
|
|
f81c1d1b31 | ||
|
|
80ac8f7feb | ||
|
|
0360184c2d | ||
|
|
099a39a2a2 | ||
|
|
c1cd7ca64f | ||
|
|
4cc108ab7f | ||
|
|
0028310aa5 | ||
|
|
18e9453fed | ||
|
|
db74ddfdf5 | ||
|
|
e68793f363 | ||
|
|
5155af8240 | ||
|
|
575ddeed10 | ||
|
|
16e1cd94e3 | ||
|
|
2443d43c25 | ||
|
|
d2accfb312 | ||
|
|
9f86971ce2 | ||
|
|
9abb216a3d | ||
|
|
e978a8e4a5 | ||
|
|
a2040ae4dd | ||
|
|
dae374412c | ||
|
|
572294dd41 | ||
|
|
a71aeb1f02 | ||
|
|
d1487fa176 | ||
|
|
783a599081 | ||
|
|
0d9fd0049b | ||
|
|
3f17da684e | ||
|
|
323beceda1 | ||
|
|
9f81c7fa0e | ||
|
|
9c156c1115 | ||
|
|
34b94ae685 | ||
|
|
50fd900bdc | ||
|
|
1bf8ec5bc6 | ||
|
|
f849b75029 | ||
|
|
25c27793fe | ||
|
|
6e413772ad | ||
|
|
137a202e1b | ||
|
|
aaf718fe4d | ||
|
|
a193d9959b | ||
|
|
12250ef0c2 | ||
|
|
bde9802583 | ||
|
|
1b30b86d2b | ||
|
|
0707b9b98c | ||
|
|
b22a379db2 | ||
|
|
bb2e0aabbc | ||
|
|
449991d846 | ||
|
|
dd42c2b074 | ||
|
|
abff1b0add | ||
|
|
fc51f6bea2 | ||
|
|
6477c22308 | ||
|
|
329b3fecfb | ||
|
|
677505f22a | ||
|
|
f518166bd0 | ||
|
|
1f4c49f823 | ||
|
|
abcc4d47b5 | ||
|
|
3d4737df72 | ||
|
|
8f94885d8e | ||
|
|
993455d664 | ||
|
|
3cb0addee7 | ||
|
|
5530b76294 | ||
|
|
9fb51165ab | ||
|
|
a650f0730e | ||
|
|
63eb9edc9c | ||
|
|
d6e1eb9792 | ||
|
|
b459f96e6b | ||
|
|
bf32f2c1ef | ||
|
|
7ca67ebaae | ||
|
|
fa32f87a35 | ||
|
|
a630015451 | ||
|
|
bf43f59232 | ||
|
|
54910746e3 | ||
|
|
07ae68333d | ||
|
|
69e70a4c9b | ||
|
|
b55f11ee74 | ||
|
|
94ee3c0203 | ||
|
|
25cf329a50 | ||
|
|
b02827cb3f | ||
|
|
2bcc0570ad | ||
|
|
a3ea0c65a1 | ||
|
|
5e526da11c | ||
|
|
5c79265f90 | ||
|
|
eb0134e716 | ||
|
|
afde1f4729 | ||
|
|
5c6dda0eac | ||
|
|
af453bc772 | ||
|
|
e13674e886 | ||
|
|
e3e856b826 | ||
|
|
9d1cd23a8f | ||
|
|
148f7c116f | ||
|
|
33e7134d6f | ||
|
|
fb799551aa | ||
|
|
7b95051fe1 | ||
|
|
efb6a6db4f | ||
|
|
478aa1aa12 | ||
|
|
751e55ed6c | ||
|
|
9dad53f763 | ||
|
|
2d57064a7a | ||
|
|
833d12cf66 | ||
|
|
7b56caa4cb | ||
|
|
5752644122 | ||
|
|
cadc0cb534 | ||
|
|
dcdab5ae1f | ||
|
|
d64e896288 | ||
|
|
500d8ede32 | ||
|
|
f4c5c7f6db | ||
|
|
43e1be4032 | ||
|
|
702def2a4d | ||
|
|
a34baf4154 | ||
|
|
4de0774f15 | ||
|
|
83d2dfc7d9 | ||
|
|
c93afd2d68 | ||
|
|
b7bacd11af | ||
|
|
f26835fae0 | ||
|
|
4edb7cb678 | ||
|
|
2ce9ba997f | ||
|
|
055077fa77 | ||
|
|
f342ccbc6a | ||
|
|
37ffd0a1ac | ||
|
|
a1f705381e | ||
|
|
c0970ad4fa | ||
|
|
3818d0c6d1 | ||
|
|
95411c79cb | ||
|
|
eeccbbacfc | ||
|
|
f6c4180502 | ||
|
|
f4d3d6c0b1 | ||
|
|
e9d2d11297 | ||
|
|
96204b29e8 | ||
|
|
47842c1243 | ||
|
|
9b494106bc | ||
|
|
d51e730a7f | ||
|
|
363909c0c4 | ||
|
|
82273f68fe | ||
|
|
12fa38b446 | ||
|
|
c26af593ff | ||
|
|
8e9a53c494 | ||
|
|
5559ce5fbb | ||
|
|
faa529a55b | ||
|
|
4ccfe20c14 | ||
|
|
3acb651650 | ||
|
|
2de57b334b | ||
|
|
0498f5bb1b | ||
|
|
929485a8f9 | ||
|
|
28cb62f373 |
@@ -25,12 +25,12 @@ exclude: |
|
||||
repos:
|
||||
# Code Upgrades
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.19.1
|
||||
rev: v3.20.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.22.2
|
||||
rev: 1.25.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version=4.2]
|
||||
@@ -66,11 +66,11 @@ repos:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: end-of-file-fixer
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 3.2.0
|
||||
rev: 3.2.1
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.44.0
|
||||
rev: v0.45.0
|
||||
hooks:
|
||||
- id: markdownlint
|
||||
language: node
|
||||
@@ -78,7 +78,7 @@ repos:
|
||||
- --disable=MD013
|
||||
# Infrastructure
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: v2.5.0
|
||||
rev: v2.6.0
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
name: pyproject.toml formatter
|
||||
@@ -88,7 +88,7 @@ repos:
|
||||
additional_dependencies:
|
||||
- tox==4.24.1 # https://github.com/tox-dev/tox/releases/latest
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.23
|
||||
rev: v0.24.1
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
name: Validate pyproject.toml
|
||||
|
||||
32
README.md
32
README.md
@@ -1,15 +1,15 @@
|
||||
# Alliance Auth
|
||||
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](https://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](https://discord.gg/fjnHAmk)
|
||||
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
A flexible authentication platform for EVE Online to help in-game organizations manage access to applications and services. AA provides both, a stable core, and a robust framework for community development and custom applications.
|
||||
|
||||
## Content
|
||||
|
||||
@@ -22,17 +22,17 @@ An auth system for EVE Online to help in-game organizations manage online servic
|
||||
|
||||
## Overview
|
||||
|
||||
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
|
||||
Alliance Auth (AA) is a platform that helps Eve Online organizations efficiently manage access to applications and services.
|
||||
|
||||
Main features:
|
||||
|
||||
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
|
||||
- Automatically grants or revokes user access to external services (e.g.: Discord, Mumble) based on the user's current membership to [a variety of EVE Online affiliation](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
|
||||
|
||||
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
|
||||
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
- Includes a set of connectors (called ["Services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
|
||||
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
|
||||
- Includes a set of web [Apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
|
||||
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
@@ -42,9 +42,15 @@ For further details about AA - including an installation guide and a full list o
|
||||
|
||||
## Screenshot
|
||||
|
||||
Here is an example of the Alliance Auth web site with some plug-ins apps and services enabled:
|
||||
Here is an example of the Alliance Auth web site with a mixture of Services, Apps and Community Creations enabled:
|
||||
|
||||

|
||||
### Flatly Theme
|
||||
|
||||

|
||||
|
||||
### Darkly Theme
|
||||
|
||||

|
||||
|
||||
## Support
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ manage online service access.
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '4.6.2a'
|
||||
__version__ = '4.9.0'
|
||||
__title__ = 'Alliance Auth'
|
||||
__title_useragent__ = 'AllianceAuth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class AnalyticsConfig(AppConfig):
|
||||
name = 'allianceauth.analytics'
|
||||
label = 'analytics'
|
||||
verbose_name = _('Analytics')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.apps import apps
|
||||
from allianceauth.authentication.models import User
|
||||
from allianceauth.analytics.utils import install_stat_users, install_stat_addons
|
||||
from esi.models import Token
|
||||
from allianceauth.analytics.utils import install_stat_users, install_stat_tokens, install_stat_addons
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from django.apps import AppConfig
|
||||
from django.core.checks import register, Tags
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class AuthenticationConfig(AppConfig):
|
||||
name = "allianceauth.authentication"
|
||||
label = "authentication"
|
||||
verbose_name = _("Authentication")
|
||||
|
||||
def ready(self):
|
||||
from allianceauth.authentication import checks, signals # noqa: F401
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import logging
|
||||
from typing import ClassVar
|
||||
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.db import models, transaction
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
|
||||
from allianceauth.notifications import notify
|
||||
from django.conf import settings
|
||||
|
||||
from .managers import CharacterOwnershipManager, StateManager
|
||||
|
||||
@@ -26,7 +28,7 @@ class State(models.Model):
|
||||
help_text="Factions to whose members this state is available.")
|
||||
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
|
||||
|
||||
objects = StateManager()
|
||||
objects: ClassVar[StateManager] = StateManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['-priority']
|
||||
@@ -136,8 +138,10 @@ class UserProfile(models.Model):
|
||||
sender=self.__class__, user=self.user, state=self.state
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return str(self.user)
|
||||
|
||||
|
||||
class CharacterOwnership(models.Model):
|
||||
class Meta:
|
||||
default_permissions = ('change', 'delete')
|
||||
@@ -147,7 +151,7 @@ class CharacterOwnership(models.Model):
|
||||
owner_hash = models.CharField(max_length=28, unique=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships')
|
||||
|
||||
objects = CharacterOwnershipManager()
|
||||
objects: ClassVar[CharacterOwnershipManager] = CharacterOwnershipManager()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user}: {self.character}"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<tr>
|
||||
<td style="white-space:initial;">
|
||||
{% for s in t.scopes.all %}
|
||||
<span class="badge bg-secondary">{{ s.name }}</span>
|
||||
<span class="badge text-bg-secondary">{{ s.name }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{% load theme_tags %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" {% theme_html_tags %}>
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<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 override -->
|
||||
<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.">
|
||||
<!-- End Required meta tags -->
|
||||
|
||||
<!-- Meta tags -->
|
||||
{% include 'allianceauth/opengraph.html' %}
|
||||
{% include 'allianceauth/icons.html' %}
|
||||
<!-- Meta tags -->
|
||||
|
||||
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
|
||||
|
||||
{% theme_css %}
|
||||
|
||||
{% include 'bundles/fontawesome.html' %}
|
||||
{% include 'bundles/auth-framework-css.html' %}
|
||||
|
||||
{% block extra_include %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="dropdown">
|
||||
<form action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<form class="dropdown-item" action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<select class="form-select" onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
<select class="form-select" onchange="this.form.submit()" id="lang-select" name="language">
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
|
||||
{% for lang_code, lang_name in LANGUAGES %}
|
||||
<option lang="{{ lang_code }}" value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
|
||||
{{ lang_code|language_name_local|capfirst }} ({{ lang_code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
{% for lang_code, lang_name in LANGUAGES %}
|
||||
<option lang="{{ lang_code }}" value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
|
||||
{{ lang_code|language_name_local|capfirst }} ({{ lang_code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
from django.db.models.signals import (
|
||||
m2m_changed,
|
||||
post_save,
|
||||
pre_delete,
|
||||
pre_save
|
||||
)
|
||||
from django.urls import reverse
|
||||
from unittest import mock
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from unittest import mock
|
||||
from allianceauth.authentication.middleware import UserSettingsMiddleware
|
||||
from unittest.mock import Mock
|
||||
from django.http import HttpResponse
|
||||
|
||||
@@ -4,10 +4,16 @@ from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo,
|
||||
EveAllianceInfo
|
||||
)
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.models.signals import (
|
||||
pre_save,
|
||||
post_save,
|
||||
pre_delete,
|
||||
m2m_changed
|
||||
)
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
from unittest.mock import Mock
|
||||
from . import patch
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import signing
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
"""
|
||||
Django system checks for Alliance Auth
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from django import db
|
||||
from django.core.checks import CheckMessage, Error, register, Warning
|
||||
@@ -16,48 +20,172 @@ B = Configuration
|
||||
|
||||
@register()
|
||||
def django_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
errors: List[CheckMessage] = []
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL[-1] == "/":
|
||||
errors.append(Warning("'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth.", hint="", id="allianceauth.checks.B005"))
|
||||
else:
|
||||
errors.append(Error("No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.", hint="", id="allianceauth.checks.B006"))
|
||||
"""
|
||||
Check that Django settings are correctly configured
|
||||
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
errors.append(Warning("'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.", hint="", id="allianceauth.checks.B007"))
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
# Check for SITE_URL
|
||||
if hasattr(settings, "SITE_URL"):
|
||||
# Check if SITE_URL is empty
|
||||
if settings.SITE_URL == "":
|
||||
errors.append(
|
||||
Error(
|
||||
msg="'SITE_URL' is empty.",
|
||||
hint="Make sure to set 'SITE_URL' to the URL of your Auth instance. (Without trailing slash)",
|
||||
id="allianceauth.checks.B011",
|
||||
)
|
||||
)
|
||||
# Check if SITE_URL has a trailing slash
|
||||
elif settings.SITE_URL[-1] == "/":
|
||||
errors.append(
|
||||
Warning(
|
||||
msg="'SITE_URL' has a trailing slash. This may lead to incorrect links being generated by Auth.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B005",
|
||||
)
|
||||
)
|
||||
# SITE_URL not found
|
||||
else:
|
||||
errors.append(Error("No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified", hint="", id="allianceauth.checks.B008"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg="No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B006",
|
||||
)
|
||||
)
|
||||
|
||||
# Check for CSRF_TRUSTED_ORIGINS
|
||||
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
|
||||
# Check if SITE_URL is not in CSRF_TRUSTED_ORIGINS
|
||||
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||
errors.append(
|
||||
Warning(
|
||||
msg="'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B007",
|
||||
)
|
||||
)
|
||||
# CSRF_TRUSTED_ORIGINS not found
|
||||
else:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified",
|
||||
hint="",
|
||||
id="allianceauth.checks.B008",
|
||||
)
|
||||
)
|
||||
|
||||
# Check for ESI_USER_CONTACT_EMAIL
|
||||
if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
|
||||
# Check if ESI_USER_CONTACT_EMAIL is empty
|
||||
if settings.ESI_USER_CONTACT_EMAIL == "":
|
||||
errors.append(
|
||||
Error(
|
||||
msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B009",
|
||||
)
|
||||
)
|
||||
# ESI_USER_CONTACT_EMAIL not found
|
||||
else:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
|
||||
hint="",
|
||||
id="allianceauth.checks.B010",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that Redis is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools"
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
try:
|
||||
redis_version = Pep440Version(get_redis_client().info()['redis_version'])
|
||||
redis_version = Pep440Version(get_redis_client().info()["redis_version"])
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm Redis Version"))
|
||||
|
||||
return errors
|
||||
|
||||
if redis_version.major == 7 and redis_version.minor == 2 and timezone.now() > timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint=allianceauth_redis_install_link, id="allianceauth.checks.A001"))
|
||||
if (
|
||||
redis_version.major == 7
|
||||
and redis_version.minor == 2
|
||||
and timezone.now()
|
||||
> timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc)
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A001",
|
||||
)
|
||||
)
|
||||
elif redis_version.major == 7 and redis_version.minor == 0:
|
||||
errors.append(Warning(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint=allianceauth_redis_install_link, id="allianceauth.checks.A002"))
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A002",
|
||||
)
|
||||
)
|
||||
elif redis_version.major == 6 and redis_version.minor == 2:
|
||||
errors.append(Warning(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint=allianceauth_redis_install_link, id="allianceauth.checks.A018"))
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A018",
|
||||
)
|
||||
)
|
||||
elif redis_version.major in [6, 5]:
|
||||
errors.append(Error(f"Redis {redis_version.public} EOL", hint=allianceauth_redis_install_link, id="allianceauth.checks.A003"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"Redis {redis_version.public} EOL",
|
||||
hint=allianceauth_redis_install_link,
|
||||
id="allianceauth.checks.A003",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that MySQL is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
mysql_quick_guide_link = "https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/"
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
@@ -65,108 +193,302 @@ def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
mysql_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
|
||||
mysql_version = Pep440Version(
|
||||
".".join(str(i) for i in connection.mysql_version)
|
||||
)
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm MySQL Version"))
|
||||
|
||||
return errors
|
||||
|
||||
# MySQL 8
|
||||
if mysql_version.major == 8:
|
||||
if mysql_version.minor == 4 and timezone.now() > timezone.datetime(year=2032, month=4, day=30, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MySQL {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A004"))
|
||||
if mysql_version.minor == 4 and timezone.now() > timezone.datetime(
|
||||
year=2032, month=4, day=30, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A004",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 3:
|
||||
errors.append(Warning(f"MySQL {mysql_version.public} Non LTS", hint=mysql_quick_guide_link, id="allianceauth.checks.A005"))
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MySQL {mysql_version.public} Non LTS",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A005",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 2:
|
||||
errors.append(Warning(f"MySQL {mysql_version.public} Non LTS", hint=mysql_quick_guide_link, id="allianceauth.checks.A006"))
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MySQL {mysql_version.public} Non LTS",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A006",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 1:
|
||||
errors.append(Error(f"MySQL {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A007"))
|
||||
elif mysql_version.minor == 0 and timezone.now() > timezone.datetime(year=2026, month=4, day=30, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MySQL {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A008"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A007",
|
||||
)
|
||||
)
|
||||
elif mysql_version.minor == 0 and timezone.now() > timezone.datetime(
|
||||
year=2026, month=4, day=30, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A008",
|
||||
)
|
||||
)
|
||||
|
||||
# MySQL below 8
|
||||
# This will also catch Mariadb 5.x
|
||||
elif mysql_version.major < 8:
|
||||
errors.append(Error(f"MySQL or MariaDB {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A009"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MySQL or MariaDB {mysql_version.public} EOL",
|
||||
hint=mysql_quick_guide_link,
|
||||
id="allianceauth.checks.A009",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that MariaDB is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
mariadb_download_link = "https://mariadb.org/download/?t=repo-config"
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql": # Still to find a way to determine MySQL vs MariaDB
|
||||
# TODO: Find a way to determine MySQL vs. MariaDB
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
mariadb_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
|
||||
mariadb_version = Pep440Version(
|
||||
".".join(str(i) for i in connection.mysql_version)
|
||||
)
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm MariaDB Version"))
|
||||
|
||||
return errors
|
||||
|
||||
# MariaDB 11
|
||||
if mariadb_version.major == 11:
|
||||
if mariadb_version.minor == 4 and timezone.now() > timezone.datetime(year=2029, month=5, day=19, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A010"))
|
||||
if mariadb_version.minor == 4 and timezone.now() > timezone.datetime(
|
||||
year=2029, month=5, day=19, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A010",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 2:
|
||||
errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint=mariadb_download_link, id="allianceauth.checks.A018"))
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MariaDB {mariadb_version.public} Non LTS",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A018",
|
||||
)
|
||||
)
|
||||
|
||||
if timezone.now() > timezone.datetime(year=2024, month=11, day=21, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A011"))
|
||||
if timezone.now() > timezone.datetime(
|
||||
year=2024, month=11, day=21, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A011",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 1:
|
||||
errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint=mariadb_download_link, id="allianceauth.checks.A019"))
|
||||
errors.append(
|
||||
Warning(
|
||||
msg=f"MariaDB {mariadb_version.public} Non LTS",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A019",
|
||||
)
|
||||
)
|
||||
|
||||
if timezone.now() > timezone.datetime(year=2024, month=8, day=21, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A012"))
|
||||
elif mariadb_version.minor in [0, 3]: # Demote versions down here once EOL
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A013"))
|
||||
if timezone.now() > timezone.datetime(
|
||||
year=2024, month=8, day=21, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A012",
|
||||
)
|
||||
)
|
||||
# Demote versions down here once EOL
|
||||
elif mariadb_version.minor in [0, 3]:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A013",
|
||||
)
|
||||
)
|
||||
|
||||
# MariaDB 10
|
||||
elif mariadb_version.major == 10:
|
||||
if mariadb_version.minor == 11 and timezone.now() > timezone.datetime(year=2028, month=2, day=10, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A014"))
|
||||
elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime(year=2026, month=7, day=6, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A0015"))
|
||||
elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime(year=2025, month=6, day=24, tzinfo=timezone.utc):
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A016"))
|
||||
elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]: # Demote versions down here once EOL
|
||||
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A017"))
|
||||
if mariadb_version.minor == 11 and timezone.now() > timezone.datetime(
|
||||
year=2028, month=2, day=10, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A014",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime(
|
||||
year=2026, month=7, day=6, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A0015",
|
||||
)
|
||||
)
|
||||
elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime(
|
||||
year=2025, month=6, day=24, tzinfo=timezone.utc
|
||||
):
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A016",
|
||||
)
|
||||
)
|
||||
# Demote versions down here once EOL
|
||||
elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]:
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"MariaDB {mariadb_version.public} EOL",
|
||||
hint=mariadb_download_link,
|
||||
id="allianceauth.checks.A017",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that SQLite is a supported version
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "sqlite":
|
||||
try:
|
||||
sqlite_version = Pep440Version(".".join(str(i) for i in sqlite_version_info))
|
||||
sqlite_version = Pep440Version(
|
||||
".".join(str(i) for i in sqlite_version_info)
|
||||
)
|
||||
except InvalidVersion:
|
||||
errors.append(Warning("Unable to confirm SQLite Version"))
|
||||
|
||||
return errors
|
||||
if sqlite_version.major == 3 and sqlite_version.minor < 27:
|
||||
errors.append(Error(f"SQLite {sqlite_version.public} Unsupported by Django", hint="https://pkgs.org/download/sqlite3", id="allianceauth.checks.A020"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"SQLite {sqlite_version.public} Unsupported by Django",
|
||||
hint="https://pkgs.org/download/sqlite3",
|
||||
id="allianceauth.checks.A020",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@register()
|
||||
def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that SQL settings are correctly configured
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
for connection in db.connections.all():
|
||||
if connection.vendor == "mysql":
|
||||
try:
|
||||
if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
|
||||
errors.append(Error(f"SQL Charset is not set to utf8mb4 DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
id="allianceauth.checks.B001",
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
errors.append(Error(f"SQL Charset is not set to utf8mb4 DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
id="allianceauth.checks.B001",
|
||||
)
|
||||
)
|
||||
|
||||
# This hasn't actually been set on AA yet
|
||||
# try:
|
||||
# if connection.settings_dict["OPTIONS"]["collation"] != "utf8mb4_unicode_ci":
|
||||
# errors.append(Error(f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
|
||||
# if (
|
||||
# connection.settings_dict["OPTIONS"]["collation"]
|
||||
# != "utf8mb4_unicode_ci"
|
||||
# ):
|
||||
# errors.append(
|
||||
# Error(
|
||||
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
|
||||
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
# id="allianceauth.checks.B001",
|
||||
# )
|
||||
# )
|
||||
# except KeyError:
|
||||
# errors.append(Error(f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
|
||||
# errors.append(
|
||||
# Error(
|
||||
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
|
||||
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
|
||||
# id="allianceauth.checks.B001",
|
||||
# )
|
||||
# )
|
||||
|
||||
# if connection.vendor == "sqlite":
|
||||
|
||||
@@ -175,19 +497,57 @@ def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
|
||||
@register()
|
||||
def celery_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||
"""
|
||||
Check that Celery settings are correctly configured
|
||||
|
||||
:param app_configs:
|
||||
:type app_configs:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
errors: List[CheckMessage] = []
|
||||
|
||||
try:
|
||||
if current_app.conf.broker_transport_options != {'priority_steps': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'queue_order_strategy': 'priority'}:
|
||||
errors.append(Error("Celery Priorities are not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
|
||||
if current_app.conf.broker_transport_options != {
|
||||
"priority_steps": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
"queue_order_strategy": "priority",
|
||||
}:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery Priorities are not set correctly",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
|
||||
id="allianceauth.checks.B003",
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
errors.append(Error("Celery Priorities are not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery Priorities are not set",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
|
||||
id="allianceauth.checks.B003",
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
if current_app.conf.broker_connection_retry_on_startup != True:
|
||||
errors.append(Error("Celery broker_connection_retry_on_startup not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
|
||||
if not current_app.conf.broker_connection_retry_on_startup:
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery broker_connection_retry_on_startup not set correctly",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
|
||||
id="allianceauth.checks.B004",
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
errors.append(Error("Celery broker_connection_retry_on_startup not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
|
||||
errors.append(
|
||||
Error(
|
||||
msg="Celery broker_connection_retry_on_startup not set",
|
||||
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
|
||||
id="allianceauth.checks.B004",
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CorpUtilsConfig(AppConfig):
|
||||
name = 'allianceauth.corputils'
|
||||
label = 'corputils'
|
||||
verbose_name = _('Corporation Stats')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import ClassVar
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership, UserProfile
|
||||
from bravado.exception import HTTPForbidden
|
||||
@@ -40,9 +41,9 @@ class CorpStats(models.Model):
|
||||
verbose_name = "corp stats"
|
||||
verbose_name_plural = "corp stats"
|
||||
|
||||
objects = CorpStatsManager()
|
||||
objects: ClassVar[CorpStatsManager] = CorpStatsManager()
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__} for {self.corp}"
|
||||
|
||||
def update(self):
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
<td style="width: 30%;">{{ alt.corporation_name }}</td>
|
||||
<td style="width: 30%;">{{ alt.alliance_name|default_if_none:"" }}</td>
|
||||
<td style="width: 5%;">
|
||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
|
||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge text-bg-danger" target="_blank">
|
||||
{% translate "Killboard" %}
|
||||
</a>
|
||||
</td>
|
||||
@@ -175,7 +175,7 @@
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
|
||||
<td>{{ member }}</td>
|
||||
<td>
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
</td>
|
||||
<td>{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
||||
<td>{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
||||
@@ -188,7 +188,7 @@
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td>{{ member.character_name }}</td>
|
||||
<td>
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
@@ -219,7 +219,7 @@
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td>{{ member.character_name }}</td>
|
||||
<td>
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">
|
||||
{% translate "Killboard" %}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<td><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
|
||||
<td>{{ result.1.character_name }}</td>
|
||||
<td >{{ result.0.corp.corporation_name }}</td>
|
||||
<td><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
|
||||
<td><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
|
||||
<td>{{ result.1.main_character.character_name }}</td>
|
||||
<td>{{ result.1.main_character.corporation_name }}</td>
|
||||
<td>{{ result.1.main_character.alliance_name }}</td>
|
||||
|
||||
@@ -3,6 +3,7 @@ Crontab App Config
|
||||
"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class CrontabConfig(AppConfig):
|
||||
@@ -12,3 +13,4 @@ class CrontabConfig(AppConfig):
|
||||
|
||||
name = "allianceauth.crontab"
|
||||
label = "crontab"
|
||||
verbose_name = _("Crontab")
|
||||
|
||||
@@ -40,10 +40,11 @@ class OffsetDatabaseScheduler(DatabaseScheduler):
|
||||
|
||||
for name, entry_fields in mapping.items():
|
||||
try:
|
||||
apply_offset = entry_fields.pop("apply_offset", False) # Ensure this pops before django tries to save to ORM
|
||||
apply_offset = entry_fields.pop("apply_offset", False) # Ensure this pops before django tries to save to ORM
|
||||
entry = self.Entry.from_entry(name, app=self.app, **entry_fields)
|
||||
|
||||
if apply_offset:
|
||||
entry_fields.update({"apply_offset": apply_offset}) # Reapply this as its gets pulled from config inconsistently.
|
||||
schedule_obj = entry.schedule
|
||||
if isinstance(schedule_obj, schedules.crontab):
|
||||
offset_cs = CrontabSchedule.from_schedule(offset_cron(schedule_obj))
|
||||
@@ -55,6 +56,7 @@ class OffsetDatabaseScheduler(DatabaseScheduler):
|
||||
day_of_week=offset_cs.day_of_week,
|
||||
timezone=offset_cs.timezone,
|
||||
)
|
||||
entry.schedule = offset_cron(schedule_obj) # This gets passed into Celery Beats Memory, important to keep it in sync with the model/DB
|
||||
entry.model.crontab = offset_cs
|
||||
entry.model.save()
|
||||
logger.debug(f"Offset applied for '{name}' due to 'apply_offset' = True.")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# myapp/tests/test_tasks.py
|
||||
|
||||
import logging
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
|
||||
@@ -8,7 +8,6 @@ from allianceauth.custom_css.models import CustomCSS
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.template.defaulttags import register
|
||||
from django.templatetags.static import static
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from pathlib import Path
|
||||
@@ -20,7 +19,7 @@ def custom_css_static(path: str) -> str:
|
||||
Versioned static URL
|
||||
This is to make sure to break the browser cache on CSS updates.
|
||||
|
||||
Example: /static/allianceauth/custom-styles.css?v=1234567890
|
||||
Example: /static/allianceauth/custom-styles.css?v=1752004819.555084
|
||||
|
||||
:param path:
|
||||
:type path:
|
||||
@@ -42,7 +41,6 @@ def custom_css_static(path: str) -> str:
|
||||
custom_css_version = (
|
||||
str(custom_css_changed).replace(" ", "").replace(":", "").replace("-", "")
|
||||
) # remove spaces, colons, and dashes
|
||||
static_url = static(path)
|
||||
versioned_url = static_url + "?v=" + custom_css_version
|
||||
versioned_url = f"{settings.STATIC_URL}{path}?v={custom_css_version}"
|
||||
|
||||
return mark_safe(f'<link rel="stylesheet" href="{versioned_url}">')
|
||||
|
||||
@@ -5,6 +5,9 @@ Form widgets for custom_css app
|
||||
# Django
|
||||
from django import forms
|
||||
|
||||
# Alliance Auth
|
||||
from allianceauth.custom_css.models import CustomCSS
|
||||
|
||||
|
||||
class CssEditorWidget(forms.Textarea):
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class EveonlineConfig(AppConfig):
|
||||
name = 'allianceauth.eveonline'
|
||||
label = 'eveonline'
|
||||
verbose_name = _('EVE Online')
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class EveAutogroupsConfig(AppConfig):
|
||||
name = 'allianceauth.eveonline.autogroups'
|
||||
label = 'eve_autogroups'
|
||||
verbose_name = _('EVE Online Autogroups')
|
||||
|
||||
def ready(self):
|
||||
import allianceauth.eveonline.autogroups.signals
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from typing import ClassVar
|
||||
from django.db import models, transaction
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
@@ -38,13 +39,13 @@ class AutogroupsConfigManager(models.Manager):
|
||||
"""
|
||||
if state is None:
|
||||
state = user.profile.state
|
||||
for config in self.filter(states=state):
|
||||
# grant user new groups for their state
|
||||
config.update_group_membership_for_user(user)
|
||||
for config in self.exclude(states=state):
|
||||
# ensure user does not have groups from previous state
|
||||
config.remove_user_from_alliance_groups(user)
|
||||
config.remove_user_from_corp_groups(user)
|
||||
for config in self.filter(states=state):
|
||||
# grant user new groups for their state
|
||||
config.update_group_membership_for_user(user)
|
||||
|
||||
|
||||
class AutogroupsConfig(models.Model):
|
||||
@@ -78,7 +79,7 @@ class AutogroupsConfig(models.Model):
|
||||
max_length=10, default='', blank=True,
|
||||
help_text='Any spaces in the group name will be replaced with this.')
|
||||
|
||||
objects = AutogroupsConfigManager()
|
||||
objects: ClassVar[AutogroupsConfigManager] = AutogroupsConfigManager()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from allianceauth.eveonline.models import EveCorporationInfo
|
||||
from django.test import TestCase
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
@@ -73,3 +74,51 @@ class AutogroupsConfigManagerTestCase(TestCase):
|
||||
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||
|
||||
self.assertTrue(update_groups.called)
|
||||
|
||||
def test_update_group_membership_corp_in_two_configs(self):
|
||||
# given
|
||||
member = AuthUtils.create_member('test member')
|
||||
AuthUtils.add_main_character_2(
|
||||
member,
|
||||
character_id='1234',
|
||||
name='test character',
|
||||
corp_id='2345',
|
||||
corp_name='corp name',
|
||||
corp_ticker='TIKK',
|
||||
|
||||
)
|
||||
|
||||
corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2345',
|
||||
corporation_name='corp name',
|
||||
corporation_ticker='TIKK',
|
||||
member_count=10,
|
||||
)
|
||||
|
||||
member_state = AuthUtils.get_member_state()
|
||||
member_config = AutogroupsConfig.objects.create(corp_groups=True)
|
||||
member_config.states.add(member_state)
|
||||
blue_state = AuthUtils.get_blue_state()
|
||||
blue_state.member_corporations.add(corp)
|
||||
blue_config = AutogroupsConfig.objects.create(corp_groups=True)
|
||||
blue_config.states.add(blue_state)
|
||||
|
||||
member.profile.state = blue_state
|
||||
member.profile.save()
|
||||
|
||||
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||
|
||||
# Checks before test that the role is correctly applied
|
||||
group = blue_config.get_corp_group(corp)
|
||||
self.assertIn(group, member.groups.all())
|
||||
|
||||
# when
|
||||
blue_state.member_corporations.remove(corp)
|
||||
member_state.member_corporations.add(corp)
|
||||
member.profile.state = member_state
|
||||
member.profile.save()
|
||||
|
||||
# then
|
||||
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||
group = member_config.get_corp_group(corp)
|
||||
self.assertIn(group, member.groups.all())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db import transaction
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from .. import dotlan, zkillboard, evewho, eveimageserver
|
||||
from ...templatetags import evelinks
|
||||
|
||||
|
||||
class TestEveWho(TestCase):
|
||||
|
||||
@@ -14,10 +14,20 @@ class EveCharacterProviderManager:
|
||||
class EveCharacterManager(models.Manager):
|
||||
provider = EveCharacterProviderManager()
|
||||
|
||||
def create_character(self, character_id):
|
||||
def exclude_biomassed(self):
|
||||
"""
|
||||
Get a queryset of EveCharacter objects, excluding the "Doomheim" corporation (1000001).
|
||||
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
return self.exclude(corporation_id=1000001)
|
||||
|
||||
def create_character(self, character_id) -> models.Model:
|
||||
return self.create_character_obj(self.provider.get_character(character_id))
|
||||
|
||||
def create_character_obj(self, character: providers.Character):
|
||||
def create_character_obj(self, character: providers.Character) -> models.Model:
|
||||
return self.create(
|
||||
character_id=character.id,
|
||||
character_name=character.name,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Union
|
||||
from typing import ClassVar, Union
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
@@ -75,8 +75,8 @@ class EveAllianceInfo(models.Model):
|
||||
alliance_ticker = models.CharField(max_length=254)
|
||||
executor_corp_id = models.PositiveIntegerField()
|
||||
|
||||
objects = EveAllianceManager()
|
||||
provider = EveAllianceProviderManager()
|
||||
objects: ClassVar[EveAllianceManager] = EveAllianceManager()
|
||||
provider: ClassVar[EveAllianceProviderManager] = EveAllianceProviderManager()
|
||||
|
||||
class Meta:
|
||||
indexes = [models.Index(fields=['executor_corp_id',])]
|
||||
@@ -147,7 +147,7 @@ class EveCorporationInfo(models.Model):
|
||||
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
objects = EveCorporationManager()
|
||||
objects: ClassVar[EveCorporationManager] = EveCorporationManager()
|
||||
provider = EveCorporationProviderManager()
|
||||
|
||||
class Meta:
|
||||
@@ -214,7 +214,7 @@ class EveCharacter(models.Model):
|
||||
faction_id = models.PositiveIntegerField(blank=True, null=True, default=None)
|
||||
faction_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
||||
|
||||
objects = EveCharacterManager()
|
||||
objects: ClassVar[EveCharacterManager] = EveCharacterManager()
|
||||
provider = EveCharacterProviderManager()
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -7,7 +7,7 @@ from jsonschema.exceptions import RefResolutionError
|
||||
from django.conf import settings
|
||||
from esi.clients import esi_client_factory
|
||||
|
||||
from allianceauth import __version__
|
||||
from allianceauth import __version__, __title_useragent__, __url__
|
||||
from allianceauth.utils.django import StartupCommand
|
||||
|
||||
|
||||
@@ -185,7 +185,9 @@ class EveSwaggerProvider(EveProvider):
|
||||
self._client = esi_client_factory(
|
||||
token=token,
|
||||
spec_file=SWAGGER_SPEC_PATH,
|
||||
app_info_text=f"allianceauth v{__version__}"
|
||||
ua_appname=__title_useragent__,
|
||||
ua_version=__version__,
|
||||
ua_url=__url__
|
||||
)
|
||||
except (HTTPError, RefResolutionError):
|
||||
logger.exception(
|
||||
@@ -202,7 +204,11 @@ class EveSwaggerProvider(EveProvider):
|
||||
def client(self):
|
||||
if self._client is None:
|
||||
self._client = esi_client_factory(
|
||||
token=self._token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
|
||||
token=self._token,
|
||||
spec_file=SWAGGER_SPEC_PATH,
|
||||
ua_appname=__title_useragent__,
|
||||
ua_version=__version__,
|
||||
ua_url=__url__
|
||||
)
|
||||
return self._client
|
||||
|
||||
|
||||
@@ -6,6 +6,12 @@ from jsonschema.exceptions import RefResolutionError
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth import __url__ as aa_url
|
||||
from allianceauth import __version__ as aa_version
|
||||
|
||||
from esi import __url__ as esi_url
|
||||
from esi import __version__ as esi_version
|
||||
|
||||
from . import set_logger
|
||||
from .esi_client_stub import EsiClientStub
|
||||
from ..providers import (
|
||||
@@ -717,11 +723,11 @@ class TestEveSwaggerProvider(TestCase):
|
||||
self.assertIsNotNone(my_provider._client)
|
||||
self.assertEqual(my_client, 'my_client')
|
||||
|
||||
@patch(MODULE_PATH + '.__version__', '1.0.0')
|
||||
def test_user_agent_header(self):
|
||||
my_provider = EveSwaggerProvider()
|
||||
my_client = my_provider.client
|
||||
operation = my_client.Universe.get_universe_factions()
|
||||
self.assertEqual(
|
||||
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0'
|
||||
operation.future.request.headers['User-Agent'],
|
||||
f'AllianceAuth/{aa_version} (dummy@example.net; +{aa_url}) Django-ESI/{esi_version} (+{esi_url})'
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class FatConfig(AppConfig):
|
||||
name = 'allianceauth.fleetactivitytracking'
|
||||
label = 'fleetactivitytracking'
|
||||
verbose_name = _('Fleet Activity Tracking')
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<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>
|
||||
<th class="text-center">{% translate "EVE time" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<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 "EVE time" %}</th>
|
||||
<th class="text-center">{% translate "Duration" %}</th>
|
||||
<th class="text-center">{% translate "Edit" %}</th>
|
||||
</tr>
|
||||
@@ -80,7 +80,7 @@
|
||||
{% for link in created_fats %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">
|
||||
<a href="{% url 'fatlink:click' link.hash %}" class="badge text-bg-primary">
|
||||
{{ link.fleet }}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<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>
|
||||
<th scope="col" class="text-center">{% translate "EVE time" %}</th>
|
||||
</tr>
|
||||
|
||||
{% for fat in fats %}
|
||||
@@ -89,7 +89,7 @@
|
||||
<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 "EVE time" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Duration" %}</th>
|
||||
<th scope="col" class="text-center">{% translate "Edit" %}</th>
|
||||
</tr>
|
||||
@@ -97,7 +97,7 @@
|
||||
{% for link in fatlinks %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a>
|
||||
<a href="{% url 'fatlink:click' link.hash %}" class="badge text-bg-primary">{{ link.fleet }}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.shortcuts import render, redirect, get_object_or_404, Http404
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from esi.decorators import token_required
|
||||
|
||||
@@ -10,24 +10,39 @@ from allianceauth.authentication.models import CharacterOwnership
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
|
||||
def get_all_characters_from_user(user: User) -> list:
|
||||
def get_all_characters_from_user(user: User, main_first: bool = False) -> list:
|
||||
"""
|
||||
Get all characters from a user or an empty list
|
||||
when no characters are found for the user or the user is None
|
||||
Get all characters from a user
|
||||
This function retrieves all characters associated with a given user, optionally ordering them
|
||||
with the main character first.
|
||||
If the user is None, an empty list is returned.
|
||||
|
||||
:param user:
|
||||
:type user:
|
||||
:return:
|
||||
:rtype:
|
||||
:param user: The user whose characters are to be retrieved
|
||||
:type user: User
|
||||
:param main_first: If True, the main character will be listed first
|
||||
:type main_first: bool
|
||||
:return: A list of EveCharacter objects associated with the user
|
||||
:rtype: list[EveCharacter]
|
||||
"""
|
||||
|
||||
if user is None:
|
||||
return []
|
||||
|
||||
try:
|
||||
characters = [
|
||||
char.character for char in CharacterOwnership.objects.filter(user=user)
|
||||
]
|
||||
if main_first:
|
||||
characters = [
|
||||
char.character
|
||||
for char in CharacterOwnership.objects.filter(user=user).order_by(
|
||||
"-character__userprofile", "character__character_name"
|
||||
)
|
||||
]
|
||||
else:
|
||||
characters = [
|
||||
char.character
|
||||
for char in CharacterOwnership.objects.filter(user=user).order_by(
|
||||
"character__character_name"
|
||||
)
|
||||
]
|
||||
except AttributeError:
|
||||
return []
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ Framework App Config
|
||||
"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class FrameworkConfig(AppConfig):
|
||||
@@ -12,3 +13,4 @@ class FrameworkConfig(AppConfig):
|
||||
|
||||
name = "allianceauth.framework"
|
||||
label = "framework"
|
||||
verbose_name = _("Framework")
|
||||
|
||||
@@ -5,6 +5,28 @@
|
||||
* to be used throughout Alliance Auth and its Community Apps
|
||||
*/
|
||||
|
||||
/* General
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
.navbar-toggler.collapsed {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child {
|
||||
display: list-item !important;
|
||||
}
|
||||
|
||||
form.is-submitting button[type="submit"] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 991px) {
|
||||
ul#nav-left:has(li) + ul#nav-right + ul#nav-right-character-control > li:first-child {
|
||||
display: list-item !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Bootstrap fixes
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
@@ -49,15 +71,12 @@
|
||||
}
|
||||
|
||||
/* Chevron icons */
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-down,
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-right {
|
||||
display: block;
|
||||
width: 16px;
|
||||
#sidebar-menu span[data-bs-toggle="collapse"] > i.fa-chevron-right {
|
||||
transition: 0.25s transform ease-in-out;
|
||||
}
|
||||
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-right,
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-down {
|
||||
display: none;
|
||||
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-right {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,47 +84,47 @@
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
.cursor-auto {
|
||||
cursor: auto;
|
||||
cursor: auto !important;
|
||||
}
|
||||
|
||||
.cursor-default {
|
||||
cursor: default;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.cursor-wait {
|
||||
cursor: wait;
|
||||
cursor: wait !important;
|
||||
}
|
||||
|
||||
.cursor-text {
|
||||
cursor: text;
|
||||
cursor: text !important;
|
||||
}
|
||||
|
||||
.cursor-move {
|
||||
cursor: move;
|
||||
cursor: move !important;
|
||||
}
|
||||
|
||||
.cursor-help {
|
||||
cursor: help;
|
||||
cursor: help !important;
|
||||
}
|
||||
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.cursor-inherit {
|
||||
cursor: inherit;
|
||||
cursor: inherit !important;
|
||||
}
|
||||
|
||||
.cursor-zoom-in {
|
||||
cursor: zoom-in;
|
||||
cursor: zoom-in !important;
|
||||
}
|
||||
|
||||
.cursor-zoom-out {
|
||||
cursor: zoom-out;
|
||||
cursor: zoom-out !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* Functions and utilities for the Alliance Auth framework.
|
||||
*/
|
||||
|
||||
/* jshint -W097 */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Checks if the given item is an array.
|
||||
*
|
||||
* @usage
|
||||
* ```javascript
|
||||
* if (isArray(someVariable)) {
|
||||
* console.log('This is an array');
|
||||
* } else {
|
||||
* console.log('This is not an array');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {*} item - The item to check.
|
||||
* @returns {boolean} True if the item is an array, false otherwise.
|
||||
*/
|
||||
const isArray = (item) => {
|
||||
return Array.isArray(item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given item is a plain object, excluding arrays and dates.
|
||||
*
|
||||
* @usage
|
||||
* ```javascript
|
||||
* if (isObject(someVariable)) {
|
||||
* console.log('This is a plain object');
|
||||
* } else {
|
||||
* console.log('This is not a plain object');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {*} item - The item to check.
|
||||
* @returns {boolean} True if the item is a plain object, false otherwise.
|
||||
*/
|
||||
const isObject = (item) => {
|
||||
return (
|
||||
item && typeof item === 'object' && !isArray(item) && !(item instanceof Date)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch data from an ajax URL
|
||||
*
|
||||
* Do not call this function directly, use `fetchGet` or `fetchPost` instead.
|
||||
*
|
||||
* @param {string} url The URL to fetch data from
|
||||
* @param {string} method The HTTP method to use for the request (default: 'get')
|
||||
* @param {string|null} csrfToken The CSRF token to include in the request headers (default: null)
|
||||
* @param {string|null} payload The payload (JSON|Object) to send with the request (default: null)
|
||||
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
|
||||
* @returns {Promise<string>} The fetched data
|
||||
* @throws {Error} Throws an error when:
|
||||
* - The method is not valid (only `get` and `post` are allowed).
|
||||
* - The CSRF token is required but not provided for POST requests.
|
||||
* - The payload is not an object when using POST method.
|
||||
* - The response status is not OK (HTTP 200-299).
|
||||
* - There is a network error or if the response cannot be parsed as JSON.
|
||||
*/
|
||||
const _fetchAjaxData = async ({
|
||||
url,
|
||||
method = 'get',
|
||||
csrfToken = null,
|
||||
payload = null,
|
||||
responseIsJson = true
|
||||
}) => {
|
||||
const normalizedMethod = method.toLowerCase();
|
||||
|
||||
// Validate the method
|
||||
const validMethods = ['get', 'post'];
|
||||
|
||||
if (!validMethods.includes(normalizedMethod)) {
|
||||
throw new Error(`Invalid method: ${method}. Valid methods are: get, post`);
|
||||
}
|
||||
|
||||
const headers = {};
|
||||
|
||||
// Set headers based on response type
|
||||
if (responseIsJson) {
|
||||
headers['Accept'] = 'application/json'; // jshint ignore:line
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
let requestUrl = url;
|
||||
let body = null;
|
||||
|
||||
if (normalizedMethod === 'post') {
|
||||
if (!csrfToken) {
|
||||
throw new Error('CSRF token is required for POST requests');
|
||||
}
|
||||
|
||||
headers['X-CSRFToken'] = csrfToken;
|
||||
|
||||
if (payload !== null && !isObject(payload)) {
|
||||
throw new Error('Payload must be an object when using POST method');
|
||||
}
|
||||
|
||||
body = payload ? JSON.stringify(payload) : null;
|
||||
} else if (normalizedMethod === 'get' && payload) {
|
||||
const queryParams = new URLSearchParams(payload).toString(); // jshint ignore:line
|
||||
|
||||
requestUrl += (url.includes('?') ? '&' : '?') + queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error with a formatted message.
|
||||
*
|
||||
* @param {Response} response The error object containing the message to throw.
|
||||
*/
|
||||
const throwHTTPStatusError = (response) => {
|
||||
throw new Error(`Error: ${response.status} - ${response.statusText}`);
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(requestUrl, {
|
||||
method: method.toUpperCase(),
|
||||
headers: headers,
|
||||
body: body
|
||||
});
|
||||
|
||||
/**
|
||||
* Throws an error if the response status is not OK (HTTP 200-299).
|
||||
* This is used to handle HTTP errors gracefully.
|
||||
*/
|
||||
if (!response.ok) {
|
||||
throwHTTPStatusError(response);
|
||||
}
|
||||
|
||||
return responseIsJson ? await response.json() : await response.text();
|
||||
} catch (error) {
|
||||
// Log the error message to the console
|
||||
console.log(`Error: ${error.message}`);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch data from an ajax URL using the GET method.
|
||||
* This function is a wrapper around _fetchAjaxData to simplify GET requests.
|
||||
*
|
||||
* @usage
|
||||
* ```javascript
|
||||
* fetchGet({
|
||||
* url: url,
|
||||
* responseIsJson: false
|
||||
* }).then((data) => {
|
||||
* // Process the fetched data
|
||||
* }).catch((error) => {
|
||||
* console.error(`Error: ${error.message}`);
|
||||
*
|
||||
* // Handle the error appropriately
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {string} url The URL to fetch data from
|
||||
* @param {string|null} payload The payload (JSON) to send with the request (default: null)
|
||||
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
|
||||
* @return {Promise<string>} The fetched data
|
||||
*/
|
||||
const fetchGet = async ({
|
||||
url,
|
||||
payload = null,
|
||||
responseIsJson = true
|
||||
}) => {
|
||||
return await _fetchAjaxData({
|
||||
url: url,
|
||||
method: 'get',
|
||||
payload: payload,
|
||||
responseIsJson: responseIsJson
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch data from an ajax URL using the POST method.
|
||||
* This function is a wrapper around _fetchAjaxData to simplify POST requests.
|
||||
* It requires a CSRF token for security purposes.
|
||||
*
|
||||
* @usage
|
||||
* ```javascript
|
||||
* fetchPost({
|
||||
* url: url,
|
||||
* csrfToken: csrfToken,
|
||||
* payload: {
|
||||
* key: 'value',
|
||||
* anotherKey: 'anotherValue'
|
||||
* },
|
||||
* responseIsJson: true
|
||||
* }).then((data) => {
|
||||
* // Process the fetched data
|
||||
* }).catch((error) => {
|
||||
* console.error(`Error: ${error.message}`);
|
||||
*
|
||||
* // Handle the error appropriately
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {string} url The URL to fetch data from
|
||||
* @param {string|null} csrfToken The CSRF token to include in the request headers (default: null)
|
||||
* @param {string|null} payload The payload (JSON) to send with the request (default: null)
|
||||
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
|
||||
* @return {Promise<string>} The fetched data
|
||||
*/
|
||||
const fetchPost = async ({
|
||||
url,
|
||||
csrfToken,
|
||||
payload = null,
|
||||
responseIsJson = true
|
||||
}) => {
|
||||
return await _fetchAjaxData({
|
||||
url: url,
|
||||
method: 'post',
|
||||
csrfToken: csrfToken,
|
||||
payload: payload,
|
||||
responseIsJson: responseIsJson
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively merges properties from source objects into a target object. If a property at the current level is an object,
|
||||
* and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.
|
||||
* This function does not modify the source objects and prevents prototype pollution by not allowing __proto__, constructor,
|
||||
* and prototype property names.
|
||||
*
|
||||
* @usage
|
||||
* ```javascript
|
||||
* const target = {a: 1, b: {c: 2}};
|
||||
* const source1 = {b: {d: 3}, e: 4 };
|
||||
* const source2 = {a: 5, b: {c: 6}};
|
||||
*
|
||||
* const merged = objectDeepMerge(target, source1, source2);
|
||||
*
|
||||
* console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
|
||||
* ```
|
||||
*
|
||||
* @param {Object} target The target object to merge properties into.
|
||||
* @param {...Object} sources One or more source objects from which to merge properties.
|
||||
* @returns {Object} The target object after merging properties from sources.
|
||||
*/
|
||||
function objectDeepMerge (target, ...sources) {
|
||||
if (!sources.length) {
|
||||
return target;
|
||||
}
|
||||
|
||||
// Iterate through each source object without modifying the `sources` array.
|
||||
sources.forEach(source => {
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
||||
continue; // Skip potentially dangerous keys to prevent prototype pollution.
|
||||
}
|
||||
|
||||
if (!target[key] || !isObject(target[key])) {
|
||||
target[key] = {};
|
||||
}
|
||||
|
||||
objectDeepMerge(target[key], source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the document is ready …
|
||||
*/
|
||||
$(document).ready(() => {
|
||||
/**
|
||||
* Prevent double form submits by adding a class to the form
|
||||
* when it is submitted.
|
||||
*
|
||||
* This class can be used to show a visual indicator that the form is being
|
||||
* submitted, such as a spinner.
|
||||
*
|
||||
* This is useful to prevent users from double-clicking the submit button
|
||||
* and submitting the form multiple times.
|
||||
*/
|
||||
document.querySelectorAll('form').forEach((form) => {
|
||||
form.addEventListener('submit', (e) => {
|
||||
// Prevent if already submitting
|
||||
if (form.classList.contains('is-submitting')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Add class to hook our visual indicator on
|
||||
form.classList.add('is-submitting');
|
||||
});
|
||||
});
|
||||
});
|
||||
105
allianceauth/framework/staticfiles/storage.py
Normal file
105
allianceauth/framework/staticfiles/storage.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Custom static files storage for Alliance Auth.
|
||||
|
||||
This module defines a custom static files storage class for
|
||||
Alliance Auth, named `AaManifestStaticFilesStorage`.
|
||||
|
||||
Using `ManifestStaticFilesStorage` will give us a hashed name for
|
||||
our static files, which is useful for cache busting.
|
||||
|
||||
This storage class extends Django's `ManifestStaticFilesStorage` to ignore missing files,
|
||||
which the original class does not handle, and log them in debug mode.
|
||||
It is useful for handling cases where static files may not exist, such as when a
|
||||
CSS file references a background image that is not present in the static files directory.
|
||||
|
||||
With debug mode enabled, it will print a message for each missing file when running `collectstatic`,
|
||||
which can help identify issues with static file references during development.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
|
||||
|
||||
|
||||
class AaManifestStaticFilesStorage(ManifestStaticFilesStorage):
|
||||
"""
|
||||
Custom static files storage that ignores missing files.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _cleanup_name(cls, name: str) -> str:
|
||||
"""
|
||||
Clean up the name by removing quotes.
|
||||
This method is used to ensure that the name does not contain any quotes,
|
||||
which can cause issues with file paths.
|
||||
|
||||
:param name: The name of the static file.
|
||||
:type name: str
|
||||
:return: The cleaned-up name without quotes.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
# Remove quotes from the name
|
||||
return name.replace('"', "").replace("'", "")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the static files storage, ignoring missing files.
|
||||
|
||||
:param args:
|
||||
:type args:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
"""
|
||||
|
||||
self.missing_files = []
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def hashed_name(self, name, content=None, filename=None):
|
||||
"""
|
||||
Generate a hashed name for the given static file, ignoring missing files.
|
||||
|
||||
Ignore missing files, e.g. non-existent background image referenced from css.
|
||||
Returns the original filename if the referenced file doesn't exist.
|
||||
|
||||
:param name: The name of the static file to hash.
|
||||
:type name: str
|
||||
:param content: The content of the static file, if available.
|
||||
:type content: bytes | None
|
||||
:param filename: The original filename of the static file, if available.
|
||||
:type filename: str | None
|
||||
:return: The hashed name of the static file, or the original name if the file is missing.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
try:
|
||||
clean_name = self._cleanup_name(name)
|
||||
|
||||
return super().hashed_name(clean_name, content, filename)
|
||||
except ValueError as e:
|
||||
if settings.DEBUG:
|
||||
# In debug mode, we log the missing file message
|
||||
message = e.args[0].split(" with ")[0]
|
||||
self.missing_files.append(message)
|
||||
# print(f'\x1b[0;30;41m{message}\x1b[0m')
|
||||
|
||||
return name
|
||||
|
||||
def post_process(self, *args, **kwargs):
|
||||
"""
|
||||
Post-process the static files, printing any missing files in debug mode.
|
||||
|
||||
:param args:
|
||||
:type args:
|
||||
:param kwargs:
|
||||
:type kwargs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
yield from super().post_process(*args, **kwargs)
|
||||
|
||||
if settings.DEBUG:
|
||||
# In debug mode, print the missing files
|
||||
for message in sorted(set(self.missing_files)):
|
||||
print(f"\x1b[0;30;41m{message}\x1b[0m")
|
||||
@@ -2,12 +2,14 @@
|
||||
{# {% include "framework/header/page-header.html" with title="Foobar" subtitle="Barfoo" %}#}
|
||||
|
||||
{% if title %}
|
||||
<h1 class="page-header text-center mb-3">
|
||||
{{ title }}
|
||||
<header class="aa-page-header mb-3">
|
||||
<h1 class="page-header text-center">
|
||||
{{ title }}
|
||||
|
||||
{% if subtitle %}
|
||||
<br>
|
||||
<small class="text-muted">{{ subtitle }}</small>
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% if subtitle %}
|
||||
<br>
|
||||
<small class="text-muted">{{ subtitle }}</small>
|
||||
{% endif %}
|
||||
</h1>
|
||||
</header>
|
||||
{% endif %}
|
||||
|
||||
23
allianceauth/framework/templates/framework/svg/sprite.svg
Normal file
23
allianceauth/framework/templates/framework/svg/sprite.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg id="alliance-auth-svg-sprite" width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<!-- Alliance Auth Logo -->
|
||||
<symbol id="aa-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<g transform="translate(41.953499,36.607802)">
|
||||
<path style="display:inline;fill:#e14852;stroke-width:0.32" d="M 131.07236,159.67687 C 109.26615,147.02458 91.302022,136.55002 91.152067,136.40007 l -0.272649,-0.27265 23.786292,-13.82371 c 13.08247,-7.60304 23.9186,-13.82025 24.08029,-13.81602 l 0.294,0.008 15.93273,36.83413 c 8.763,20.25877 15.891,36.95054 15.84,37.09283 l -0.0927,0.25869 z" />
|
||||
<path style="display:inline;fill:#436195;stroke-width:0.32" d="m 1.28,182.46369 c 0,-0.16969 17.354495,-40.46543 38.565546,-89.546103 L 78.411088,3.68 C 79.919052,1.4903841 82.294641,0.02199886 86.08,0.01224344 89.865359,0.00248802 92.288,1.4677954 93.674477,3.5158445 l 21.668143,50.1206965 21.66814,50.120699 -0.26538,0.23285 C 136.59942,104.11816 106.528,121.61441 69.92,142.87065 33.312,164.12688 2.892,181.80046 2.32,182.14527 l -1.04,0.62693 z" />
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<!-- Loading Spinner -->
|
||||
<symbol id="aa-loading-spinner" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="10" fill="none" stroke-width="1" stroke-linecap="round">
|
||||
<animate attributeName="stroke-dasharray" dur="1.5s" calcMode="spline" values="0 150;42 150;42 150;42 150" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
|
||||
<animate attributeName="stroke-dashoffset" dur="1.5s" calcMode="spline" values="0;-16;-59;-59" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<animateTransform attributeName="transform" type="rotate" dur="2s" values="0 12 12;360 12 12" repeatCount="indefinite" />
|
||||
</g>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,10 +1,11 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class GroupManagementConfig(AppConfig):
|
||||
name = 'allianceauth.groupmanagement'
|
||||
label = 'groupmanagement'
|
||||
verbose_name = 'Group Management'
|
||||
verbose_name = _('Group Management')
|
||||
|
||||
def ready(self):
|
||||
from . import signals # noqa: F401
|
||||
|
||||
@@ -39,12 +39,12 @@
|
||||
|
||||
<td>
|
||||
{% if group.authgroup.hidden %}
|
||||
<span class="badge bg-info">{% translate "Hidden" %}</span>
|
||||
<span class="badge text-bg-info">{% translate "Hidden" %}</span>
|
||||
{% endif %}
|
||||
{% if group.authgroup.open %}
|
||||
<span class="badge bg-success">{% translate "Open" %}</span>
|
||||
<span class="badge text-bg-success">{% translate "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{% translate "Requestable" %}</span>
|
||||
<span class="badge text-bg-secondary">{% translate "Requestable" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<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>
|
||||
<span class="badge text-bg-secondary">{{ req_count }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
@@ -33,8 +33,8 @@
|
||||
<th>{% translate "Description" %}</th>
|
||||
<th>
|
||||
{% translate "Leaders" %}<br>
|
||||
<span class="my-1 me-1 fw-lighter badge bg-primary">{% translate "User" %}</span>
|
||||
<span class="my-1 me-1 fw-lighter badge bg-secondary">{% translate "Group" %}</span>
|
||||
<span class="my-1 me-1 fw-lighter badge text-bg-primary">{% translate "User" %}</span>
|
||||
<span class="my-1 me-1 fw-lighter badge text-bg-secondary">{% translate "Group" %}</span>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -53,13 +53,13 @@
|
||||
{% if g.group.authgroup.group_leaders.all.count %}
|
||||
{% for leader in g.group.authgroup.group_leaders.all %}
|
||||
{% if leader.profile.main_character %}
|
||||
<span class="my-1 me-1 badge bg-primary">{{leader.profile.main_character}}</span>
|
||||
<span class="my-1 me-1 badge text-bg-primary">{{leader.profile.main_character}}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if g.group.authgroup.group_leader_groups.all.count %}
|
||||
{% for group in g.group.authgroup.group_leader_groups.all %}
|
||||
<span class="my-1 me-1 badge bg-secondary">{{group.name}}</span>
|
||||
<span class="my-1 me-1 badge text-bg-secondary">{{group.name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -70,8 +70,8 @@
|
||||
{% translate "Leave" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
<button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
|
||||
<i class="fa-regular fa-hourglass-half"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif not g.request %}
|
||||
@@ -85,9 +85,13 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
<button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
|
||||
<i class="fa-regular fa-hourglass-half"></i>
|
||||
</button>
|
||||
|
||||
<a href="{% url 'groupmanagement:request_retract' g.group.id %}" class="btn btn-danger">
|
||||
{% translate "Retract" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{% translate "Join Requests" %}
|
||||
|
||||
{% if acceptrequests %}
|
||||
<span class="badge bg-secondary">{{ acceptrequests|length }}</span>
|
||||
<span class="badge text-bg-secondary">{{ acceptrequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
@@ -30,7 +30,7 @@
|
||||
{% translate "Leave Requests" %}
|
||||
|
||||
{% if leaverequests %}
|
||||
<span class="badge bg-secondary">{{ leaverequests|length }}</span>
|
||||
<span class="badge text-bg-secondary">{{ leaverequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -10,6 +10,11 @@ urlpatterns = [
|
||||
path(
|
||||
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"
|
||||
),
|
||||
path(
|
||||
"group/request/retract/<int:group_id>/",
|
||||
views.group_request_retract,
|
||||
name="request_retract"
|
||||
),
|
||||
# group management
|
||||
path("groupmanagement/requests/", views.group_management, name="management"),
|
||||
path("groupmanagement/membership/", views.group_membership, name="membership"),
|
||||
|
||||
@@ -420,3 +420,42 @@ def group_request_leave(request, group_id):
|
||||
grouprequest.notify_leaders()
|
||||
messages.success(request, _('Applied to leave group %(group)s.') % {"group": group})
|
||||
return redirect("groupmanagement:groups")
|
||||
|
||||
@login_required
|
||||
def group_request_retract(request, group_id):
|
||||
logger.debug(
|
||||
f"group_request_retract called by user {request.user} for group id {group_id}"
|
||||
)
|
||||
group = get_object_or_404(Group, id=group_id)
|
||||
|
||||
if not GroupManager.check_internal_group(group):
|
||||
logger.warning(
|
||||
f"User {request.user} attempted to retract group request for "
|
||||
f"group id {group_id} but it is not a joinable group"
|
||||
)
|
||||
messages.warning(
|
||||
request,
|
||||
_("You cannot retract that request"),
|
||||
)
|
||||
return redirect('groupmanagement:groups')
|
||||
|
||||
try:
|
||||
group_request = GroupRequest.objects.get(
|
||||
user=request.user, group=group, leave_request=False
|
||||
)
|
||||
group_request.delete()
|
||||
|
||||
logger.info(f"Deleted group request for user {request.user} to group {group}")
|
||||
messages.success(
|
||||
request, _('Retracted application to group %(group)s.') % {"group": group}
|
||||
)
|
||||
except GroupRequest.DoesNotExist:
|
||||
logger.info(
|
||||
f"{request.user} attempted to retract group request for "
|
||||
f"group id {group_id} but has no open request"
|
||||
)
|
||||
messages.warning(
|
||||
request, _("You have no open request for that group.")
|
||||
)
|
||||
|
||||
return redirect("groupmanagement:groups")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class HRApplicationsConfig(AppConfig):
|
||||
name = 'allianceauth.hrapplications'
|
||||
label = 'hrapplications'
|
||||
verbose_name = _('HR Applications')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import ClassVar
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
@@ -40,7 +41,7 @@ class Application(models.Model):
|
||||
reviewer_character = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, blank=True, null=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = ApplicationManager()
|
||||
objects: ClassVar[ApplicationManager] = ApplicationManager()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user) + " Application To " + str(self.form)
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
<td class="text-center">{{ personal_app.form.corp.corporation_name }}</td>
|
||||
<td class="text-center">
|
||||
{% if personal_app.approved == None %}
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
|
||||
{% elif personal_app.approved == True %}
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
<div class="badge text-bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@@ -133,14 +133,14 @@
|
||||
<td class="text-center">
|
||||
{% if app.approved == None %}
|
||||
{% if app.reviewer_str %}
|
||||
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
<div class="badge text-bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
{% else %}
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
|
||||
{% endif %}
|
||||
{% elif app.approved == True %}
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
<div class="badge text-bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@@ -177,14 +177,14 @@
|
||||
<td class="text-center">
|
||||
{% if app.approved == None %}
|
||||
{% if app.reviewer_str %}
|
||||
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
<div class="badge text-bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||
{% else %}
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
|
||||
{% endif %}
|
||||
{% elif app.approved == True %}
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
<div class="badge text-bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
<td>{{ app.form.corp }}</td>
|
||||
<td class="text-center">
|
||||
{% if app.approved == None %}
|
||||
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
|
||||
{% elif app.approved == True %}
|
||||
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||
<div class="badge text-bg-success">{% translate "Approved" %}</div>
|
||||
{% else %}
|
||||
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-info">
|
||||
<div class="card-header text-bg-info">
|
||||
<div class="card-title mb-0">{% translate "Applicant" %}</div>
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-info">
|
||||
<div class="card-header text-bg-info">
|
||||
<div class="card-title mb-0">{% translate "Characters" %}</div>
|
||||
</div>
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-25 19:17+1000\n"
|
||||
"POT-Creation-Date: 2025-07-03 09:07+1000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,6 +18,10 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: allianceauth/analytics/apps.py:8
|
||||
msgid "Analytics"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/analytics/models.py:22
|
||||
msgid "Google Analytics Universal"
|
||||
msgstr ""
|
||||
@@ -26,6 +30,10 @@ msgstr ""
|
||||
msgid "Google Analytics V4"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/apps.py:9
|
||||
msgid "Authentication"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/constants.py:6
|
||||
msgid ""
|
||||
"This software has exceeded the error limit for ESI. If you are a user, "
|
||||
@@ -35,7 +43,7 @@ msgid ""
|
||||
"esi on the EVE Online Discord. https://www.eveonline.com/discord"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/decorators.py:49
|
||||
#: allianceauth/authentication/decorators.py:52
|
||||
msgid "A main character is required to perform that action. Add one below."
|
||||
msgstr ""
|
||||
|
||||
@@ -48,91 +56,91 @@ msgstr ""
|
||||
msgid "You are not allowed to add or remove these restricted groups: %s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:70
|
||||
#: allianceauth/project_template/project_name/settings/base.py:105
|
||||
#: allianceauth/authentication/models.py:72
|
||||
#: allianceauth/project_template/project_name/settings/base.py:104
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:71
|
||||
#: allianceauth/authentication/models.py:73
|
||||
msgid "Czech"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:72
|
||||
#: allianceauth/project_template/project_name/settings/base.py:107
|
||||
#: allianceauth/authentication/models.py:74
|
||||
#: allianceauth/project_template/project_name/settings/base.py:106
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:73
|
||||
#: allianceauth/project_template/project_name/settings/base.py:108
|
||||
#: allianceauth/authentication/models.py:75
|
||||
#: allianceauth/project_template/project_name/settings/base.py:107
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:74
|
||||
#: allianceauth/project_template/project_name/settings/base.py:109
|
||||
#: allianceauth/authentication/models.py:76
|
||||
#: allianceauth/project_template/project_name/settings/base.py:108
|
||||
msgid "Italian"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:75
|
||||
#: allianceauth/project_template/project_name/settings/base.py:110
|
||||
#: allianceauth/authentication/models.py:77
|
||||
#: allianceauth/project_template/project_name/settings/base.py:109
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:76
|
||||
#: allianceauth/project_template/project_name/settings/base.py:111
|
||||
#: allianceauth/authentication/models.py:78
|
||||
#: allianceauth/project_template/project_name/settings/base.py:110
|
||||
msgid "Korean"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:77
|
||||
#: allianceauth/project_template/project_name/settings/base.py:112
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:78
|
||||
#: allianceauth/project_template/project_name/settings/base.py:115
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:79
|
||||
#: allianceauth/project_template/project_name/settings/base.py:113
|
||||
msgid "Dutch"
|
||||
#: allianceauth/project_template/project_name/settings/base.py:111
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:80
|
||||
#: allianceauth/project_template/project_name/settings/base.py:114
|
||||
msgid "Polish"
|
||||
msgid "Russian"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:81
|
||||
#: allianceauth/project_template/project_name/settings/base.py:116
|
||||
msgid "Ukrainian"
|
||||
#: allianceauth/project_template/project_name/settings/base.py:112
|
||||
msgid "Dutch"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:82
|
||||
#: allianceauth/project_template/project_name/settings/base.py:117
|
||||
#: allianceauth/project_template/project_name/settings/base.py:113
|
||||
msgid "Polish"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:83
|
||||
#: allianceauth/project_template/project_name/settings/base.py:115
|
||||
msgid "Ukrainian"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:84
|
||||
#: allianceauth/project_template/project_name/settings/base.py:116
|
||||
msgid "Simplified Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:98
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:42
|
||||
#: allianceauth/authentication/models.py:100
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:67
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:103
|
||||
#: allianceauth/authentication/models.py:105
|
||||
#: allianceauth/templates/allianceauth/night-toggle.html:6
|
||||
msgid "Night Mode"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:107
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:46
|
||||
#: allianceauth/authentication/models.py:109
|
||||
#: allianceauth/theme/templates/theme/theme_select.html:4
|
||||
msgid "Theme"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:124
|
||||
#: allianceauth/authentication/models.py:126
|
||||
#, python-format
|
||||
msgid "State changed to: %s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/models.py:125
|
||||
#: allianceauth/authentication/models.py:127
|
||||
#, python-format
|
||||
msgid "Your user's state is now: %(state)s"
|
||||
msgstr ""
|
||||
@@ -194,6 +202,8 @@ msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/tokens.html:7
|
||||
#: allianceauth/authentication/templates/authentication/tokens.html:11
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:133
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:136
|
||||
#: allianceauth/templates/allianceauth/top-menu-user-dropdown.html:62
|
||||
msgid "Token Management"
|
||||
msgstr ""
|
||||
@@ -267,53 +277,53 @@ msgstr ""
|
||||
msgid "Invalid or expired activation link."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:157
|
||||
#: allianceauth/authentication/views.py:158
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot change main character to %(char)s: character owned by a different "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:164
|
||||
#: allianceauth/authentication/views.py:165
|
||||
#, python-format
|
||||
msgid "Changed main character to %s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:178
|
||||
#: allianceauth/authentication/views.py:179
|
||||
#, python-format
|
||||
msgid "Added %(name)s to your account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:180
|
||||
#: allianceauth/authentication/views.py:181
|
||||
#, python-format
|
||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:225
|
||||
#: allianceauth/authentication/views.py:226
|
||||
msgid ""
|
||||
"Unable to authenticate as the selected character. Please log in with the "
|
||||
"main character associated with this account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:292
|
||||
#: allianceauth/authentication/views.py:293
|
||||
msgid "Registration token has expired."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:353
|
||||
#: allianceauth/authentication/views.py:354
|
||||
msgid ""
|
||||
"Sent confirmation email. Please follow the link to confirm your email "
|
||||
"address."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:359
|
||||
#: allianceauth/authentication/views.py:360
|
||||
msgid "Confirmed your email address. Please login to continue."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:365
|
||||
#: allianceauth/authentication/views.py:366
|
||||
msgid "Registration of new accounts is not allowed at this time."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/corputils/auth_hooks.py:12
|
||||
#: allianceauth/corputils/apps.py:8 allianceauth/corputils/auth_hooks.py:12
|
||||
msgid "Corporation Stats"
|
||||
msgstr ""
|
||||
|
||||
@@ -419,6 +429,10 @@ msgstr ""
|
||||
msgid "Failed to gather corporation statistics with selected token."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/crontab/apps.py:16
|
||||
msgid "Crontab"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/crontab/models.py:13
|
||||
msgid "Minute Offset"
|
||||
msgstr ""
|
||||
@@ -452,6 +466,15 @@ msgstr ""
|
||||
msgid "This CSS will be added to the site after the default CSS."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/eveonline/apps.py:8
|
||||
msgid "EVE Online"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/eveonline/autogroups/apps.py:8
|
||||
msgid "EVE Online Autogroups"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/fleetactivitytracking/apps.py:8
|
||||
#: allianceauth/fleetactivitytracking/auth_hooks.py:10
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:10
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkcreate.html:11
|
||||
@@ -561,9 +584,11 @@ msgstr ""
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html:75
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:44
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:92
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
|
||||
#: allianceauth/templates/allianceauth/top-menu.html:23
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:18
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:12
|
||||
msgid "Eve Time"
|
||||
msgid "EVE time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:49
|
||||
@@ -728,6 +753,11 @@ msgid ""
|
||||
"character needs to be online."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/framework/apps.py:16
|
||||
msgid "Framework"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/apps.py:8
|
||||
#: allianceauth/groupmanagement/auth_hooks.py:18
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:18
|
||||
msgid "Group Management"
|
||||
@@ -956,7 +986,7 @@ msgid "Hidden"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:16
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:15
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
@@ -1009,7 +1039,7 @@ msgstr ""
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:182
|
||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:46
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:25
|
||||
#: allianceauth/srp/templates/srp/data.html:117
|
||||
#: allianceauth/srp/templates/srp/data.html:120
|
||||
#: allianceauth/srp/templates/srp/management.html:87
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
@@ -1151,6 +1181,10 @@ msgstr ""
|
||||
msgid "Applied to leave group %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/hrapplications/apps.py:8
|
||||
msgid "HR Applications"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/hrapplications/auth_hooks.py:15
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
@@ -1222,7 +1256,7 @@ msgstr ""
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:185
|
||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:48
|
||||
#: allianceauth/hrapplications/templates/hrapplications/view.html:21
|
||||
#: allianceauth/srp/templates/srp/data.html:109
|
||||
#: allianceauth/srp/templates/srp/data.html:112
|
||||
msgid "Approved"
|
||||
msgstr ""
|
||||
|
||||
@@ -1230,7 +1264,7 @@ msgstr ""
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:143
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:187
|
||||
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:50
|
||||
#: allianceauth/srp/templates/srp/data.html:113
|
||||
#: allianceauth/srp/templates/srp/data.html:116
|
||||
msgid "Rejected"
|
||||
msgstr ""
|
||||
|
||||
@@ -1345,7 +1379,7 @@ msgstr ""
|
||||
msgid "children"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/admin.py:90 allianceauth/menu/models.py:21
|
||||
#: allianceauth/menu/admin.py:90 allianceauth/menu/models.py:22
|
||||
msgid "text"
|
||||
msgstr ""
|
||||
|
||||
@@ -1357,11 +1391,15 @@ msgstr ""
|
||||
msgid "visible"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/apps.py:16
|
||||
msgid "Menu"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/constants.py:16
|
||||
msgid "app"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/constants.py:17 allianceauth/menu/models.py:37
|
||||
#: allianceauth/menu/constants.py:17 allianceauth/menu/models.py:38
|
||||
msgid "folder"
|
||||
msgstr ""
|
||||
|
||||
@@ -1373,47 +1411,47 @@ msgstr ""
|
||||
msgid "type"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:22
|
||||
#: allianceauth/menu/models.py:23
|
||||
msgid "Text to show on menu"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:27
|
||||
#: allianceauth/menu/models.py:28
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:28
|
||||
#: allianceauth/menu/models.py:29
|
||||
msgid "Order of the menu. Lowest First"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:38
|
||||
#: allianceauth/menu/models.py:39
|
||||
msgid "Folder this item is in (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:42
|
||||
#: allianceauth/menu/models.py:43
|
||||
msgid "is hidden"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:44
|
||||
#: allianceauth/menu/models.py:45
|
||||
msgid ""
|
||||
"Hide this menu item.If this item is a folder all items under it will be "
|
||||
"hidden too"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:59
|
||||
#: allianceauth/menu/models.py:60
|
||||
msgid "icon classes"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:61
|
||||
#: allianceauth/menu/models.py:62
|
||||
msgid ""
|
||||
"Font Awesome classes to show as icon on menu, e.g. <code>fa-solid fa-house</"
|
||||
"code>"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:67
|
||||
#: allianceauth/menu/models.py:68
|
||||
msgid "url"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/models.py:68
|
||||
#: allianceauth/menu/models.py:69
|
||||
msgid "External URL this menu items will link to"
|
||||
msgstr ""
|
||||
|
||||
@@ -1422,44 +1460,63 @@ msgid "Add folder"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-notification-block.html:12
|
||||
#: allianceauth/notifications/apps.py:8
|
||||
#: allianceauth/notifications/templates/notifications/list.html:7
|
||||
#: allianceauth/notifications/templates/notifications/list.html:11
|
||||
#: allianceauth/templates/allianceauth/notifications_menu_item.html:6
|
||||
msgid "Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:56
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:77
|
||||
msgid "Super User"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:70
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:83
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:86
|
||||
msgid "Alliance Auth Documentation"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:94
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:97
|
||||
msgid "Alliance Auth Discord"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:105
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:108
|
||||
msgid "Alliance Auth Git"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:118
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:121
|
||||
#: allianceauth/templates/allianceauth/top-menu-admin.html:9
|
||||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:82
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:144
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:147
|
||||
msgid "Sign Out"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:86
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:155
|
||||
#: allianceauth/menu/templates/menu/menu-user.html:158
|
||||
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:17
|
||||
#: allianceauth/templates/allianceauth/top-menu-rh-default.html:18
|
||||
msgid "Sign In"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/notifications/models.py:21
|
||||
#: allianceauth/notifications/models.py:22
|
||||
msgid "danger"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/notifications/models.py:22
|
||||
#: allianceauth/notifications/models.py:23
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/notifications/models.py:23
|
||||
#: allianceauth/notifications/models.py:24
|
||||
msgid "info"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/notifications/models.py:24
|
||||
#: allianceauth/notifications/models.py:25
|
||||
msgid "success"
|
||||
msgstr ""
|
||||
|
||||
@@ -1516,7 +1573,7 @@ msgstr ""
|
||||
msgid "Deleted all read notifications."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/auth_hooks.py:12
|
||||
#: allianceauth/optimer/apps.py:8 allianceauth/optimer/auth_hooks.py:12
|
||||
msgid "Fleet Operations"
|
||||
msgstr ""
|
||||
|
||||
@@ -1590,11 +1647,6 @@ msgstr ""
|
||||
msgid "Form Up System"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/dashboard.ops.html:17
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:18
|
||||
msgid "EVE Time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/fleetoptable.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:13
|
||||
msgid "Local Time"
|
||||
@@ -1610,7 +1662,7 @@ msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/management.html:28
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:32
|
||||
msgid "Current Eve Time:"
|
||||
msgid "Current EVE time:"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/optimer/templates/optimer/management.html:36
|
||||
@@ -1655,6 +1707,7 @@ msgstr ""
|
||||
msgid "Saved changes to operation timer for %(opname)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/permissions_tool/apps.py:8
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:7
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:11
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:17
|
||||
@@ -1711,7 +1764,7 @@ msgstr ""
|
||||
msgid "Successfully set your {self.service_name} password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/auth_hooks.py:12
|
||||
#: allianceauth/services/apps.py:8 allianceauth/services/auth_hooks.py:12
|
||||
msgid "Services"
|
||||
msgstr ""
|
||||
|
||||
@@ -1775,6 +1828,10 @@ msgstr ""
|
||||
msgid "Password must be at least 8 characters long."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/apps.py:8
|
||||
msgid "Discord Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/models.py:187
|
||||
msgid "Discord Account Disabled"
|
||||
msgstr ""
|
||||
@@ -1823,6 +1880,10 @@ msgid ""
|
||||
"again."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/apps.py:8
|
||||
msgid "Discourse Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/templates/services/discourse/discourse_service_ctrl.html:5
|
||||
msgid "Discourse"
|
||||
msgstr ""
|
||||
@@ -1853,6 +1914,14 @@ msgstr ""
|
||||
msgid "Invalid payload. Please contact support if this problem persists."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/example/apps.py:8
|
||||
msgid "Example Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/apps.py:8
|
||||
msgid "IPS4 Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:31
|
||||
msgid "Activated IPSuite4 account."
|
||||
msgstr ""
|
||||
@@ -1876,6 +1945,10 @@ msgstr ""
|
||||
msgid "Deactivated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/mumble/apps.py:8
|
||||
msgid "Mumble Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/mumble/templates/services/mumble/mumble_connection_history.html:7
|
||||
msgid "Mumble"
|
||||
msgstr ""
|
||||
@@ -1931,6 +2004,10 @@ msgstr ""
|
||||
msgid "Mumble Connection History"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/apps.py:8
|
||||
msgid "Openfire Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:27
|
||||
msgid "Jabber"
|
||||
msgstr ""
|
||||
@@ -1982,6 +2059,10 @@ msgstr ""
|
||||
msgid "Set jabber password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/apps.py:8
|
||||
msgid "phpBB3 Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||
msgid "Activated forum account."
|
||||
msgstr ""
|
||||
@@ -2005,6 +2086,10 @@ msgstr ""
|
||||
msgid "Set forum password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/apps.py:8
|
||||
msgid "SMF Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:52
|
||||
msgid "Activated SMF account."
|
||||
msgstr ""
|
||||
@@ -2028,6 +2113,10 @@ msgstr ""
|
||||
msgid "Set SMF password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/apps.py:8
|
||||
msgid "TeamSpeak 3 Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
||||
#, python-format
|
||||
msgid "Unable to locate user %s on server"
|
||||
@@ -2077,6 +2166,10 @@ msgstr ""
|
||||
msgid "Reset TeamSpeak3 permission key."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/apps.py:8
|
||||
msgid "Xenforo Service"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:30
|
||||
msgid "Activated XenForo account."
|
||||
msgstr ""
|
||||
@@ -2186,7 +2279,7 @@ msgid ""
|
||||
"Some services provide different options. Hover over the buttons to see more."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/srp/auth_hooks.py:14
|
||||
#: allianceauth/srp/apps.py:8 allianceauth/srp/auth_hooks.py:14
|
||||
msgid "Ship Replacement"
|
||||
msgstr ""
|
||||
|
||||
@@ -2261,18 +2354,18 @@ msgid "Mark Completed"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/srp/templates/srp/data.html:48
|
||||
#: allianceauth/srp/templates/srp/data.html:139
|
||||
#: allianceauth/srp/templates/srp/data.html:142
|
||||
msgid "Total Losses:"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/srp/templates/srp/data.html:49
|
||||
#: allianceauth/srp/templates/srp/data.html:140
|
||||
#: allianceauth/srp/templates/srp/data.html:143
|
||||
#: allianceauth/srp/templates/srp/management.html:36
|
||||
msgid "Total ISK Cost:"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/srp/templates/srp/data.html:60
|
||||
#: allianceauth/srp/templates/srp/data.html:151
|
||||
#: allianceauth/srp/templates/srp/data.html:154
|
||||
msgid "Are you sure you want to delete SRP requests?"
|
||||
msgstr ""
|
||||
|
||||
@@ -2304,12 +2397,12 @@ msgstr ""
|
||||
msgid "Post Time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/srp/templates/srp/data.html:99
|
||||
#: allianceauth/srp/templates/srp/data.html:102
|
||||
#: allianceauth/srp/templates/srp/management.html:70
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/srp/templates/srp/data.html:160
|
||||
#: allianceauth/srp/templates/srp/data.html:163
|
||||
msgid "No SRP requests for this fleet."
|
||||
msgstr ""
|
||||
|
||||
@@ -2493,52 +2586,48 @@ msgstr ""
|
||||
msgid "Alliance Auth Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:18
|
||||
msgid "Closed"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:24
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:20
|
||||
msgid "No notifications at this time"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:33
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:29
|
||||
msgid "Powered by GitLab"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:39
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:35
|
||||
msgid "Support Discord"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:49
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:53
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
|
||||
msgid "Software Version"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
|
||||
msgid "Current"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:67
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:63
|
||||
msgid "Latest Stable"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:72
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:68
|
||||
msgid "Update available"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:80
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:76
|
||||
msgid "Latest Pre-Release"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:85
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:81
|
||||
msgid "Pre-Release available"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:95
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:91
|
||||
msgid "Task Queue"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:100
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:96
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
@@ -2547,11 +2636,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:116
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:112
|
||||
msgid "running"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:117
|
||||
#: allianceauth/templates/allianceauth/admin-status/overview.html:113
|
||||
msgid "queued"
|
||||
msgstr ""
|
||||
|
||||
@@ -2576,10 +2665,17 @@ msgstr ""
|
||||
msgid "Toggle navigation"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/theme/templates/theme/theme_select.html:7
|
||||
#: allianceauth/theme/templates/theme/theme_select.html:11
|
||||
msgid "Select Theme"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/apps.py:8
|
||||
#: allianceauth/timerboard/templates/timerboard/form.html:10
|
||||
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:10
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:14
|
||||
msgid "Structure Timers"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/form.py:36
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:14
|
||||
#: allianceauth/timerboard/templates/timerboard/timertable.html:8
|
||||
@@ -2766,6 +2862,10 @@ msgstr ""
|
||||
msgid "Abandoned"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/models.py:60
|
||||
msgid "Theft"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/dashboard.timers.html:7
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:54
|
||||
msgid "Upcoming Timers"
|
||||
@@ -2775,12 +2875,6 @@ msgstr ""
|
||||
msgid "Timer"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/form.html:10
|
||||
#: allianceauth/timerboard/templates/timerboard/timer_confirm_delete.html:10
|
||||
#: allianceauth/timerboard/templates/timerboard/view.html:14
|
||||
msgid "Structure Timers"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/timerboard/templates/timerboard/form.html:25
|
||||
msgid "Structure Timer Details"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -12,6 +13,7 @@ logger = logging.getLogger(__name__)
|
||||
class MenuConfig(AppConfig):
|
||||
name = "allianceauth.menu"
|
||||
label = "menu"
|
||||
verbose_name = _("Menu")
|
||||
|
||||
def ready(self):
|
||||
from allianceauth.menu.core import smart_sync
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import ClassVar
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -68,7 +69,7 @@ class MenuItem(models.Model):
|
||||
help_text=_("External URL this menu items will link to"),
|
||||
)
|
||||
|
||||
objects = MenuItemManager()
|
||||
objects: ClassVar[MenuItemManager] = MenuItemManager()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.text
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</a>
|
||||
|
||||
{% if item.count >= 1 %}
|
||||
<span class="badge bg-primary m-2 align-self-center{% if not item.is_folder %} me-2{% endif %}">
|
||||
<span class="badge text-bg-primary m-2 align-self-center{% if not item.is_folder %} me-2{% endif %}">
|
||||
{{ item.count }}
|
||||
</span>
|
||||
{% elif item.url %}
|
||||
@@ -43,8 +43,7 @@
|
||||
aria-expanded="false"
|
||||
aria-controls=""
|
||||
>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</span>
|
||||
<ul
|
||||
class="collapse ps-1 w-100 border-start rounded-start border-light border-3"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{% load navactive %}
|
||||
{% load auth_notifications %}
|
||||
|
||||
<li class="nav-item {% navactive request 'notifications:' %}" id="menu_item_notifications">
|
||||
<a class="nav-link" href="{% url 'notifications:list' %}">
|
||||
<li class="nav-item" id="menu_item_notifications">
|
||||
<a class="nav-link {% navactive request 'notifications:' %}" href="{% url 'notifications:list' %}">
|
||||
{% with unread_count=request.user|user_unread_notification_count %}
|
||||
<i class="fa-solid fa-bell{% if unread_count %} text-danger{% endif %}"></i>
|
||||
{% endwith %}
|
||||
|
||||
@@ -2,28 +2,51 @@
|
||||
{% load evelinks %}
|
||||
{% load theme_tags %}
|
||||
|
||||
<div id="aa-user-info" class="w-100 d-flex flex-column justify-content-center align-items-center text-center py-1 border-top border-secondary {% if not user.is_authenticated %}position-absolute bottom-0{% endif %}">
|
||||
<div
|
||||
id="aa-user-info"
|
||||
class="w-100 d-flex flex-column justify-content-center align-items-center text-center py-1 border-top border-secondary {% if not user.is_authenticated %}position-absolute bottom-0{% endif %}"
|
||||
>
|
||||
<div class="d-flex mb-0 w-100">
|
||||
<div class="p-2 position-relative m-2">
|
||||
{% if user.is_authenticated %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<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 }}">
|
||||
<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 }}">
|
||||
<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 }}">
|
||||
<img
|
||||
class="rounded-circle position-absolute bottom-0 end-0"
|
||||
src="{{ main.faction_logo_url_32 }}"
|
||||
alt="{{ main.faction_name }}"
|
||||
>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
{% include "bundles/image-auth-logo.html" with logo_width="64px" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="align-self-center text-start">
|
||||
{% if user.is_authenticated %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<h5 class="m-0">{{ main.character_name }}</h5>
|
||||
<p class="m-0 small">{{ main.corporation_name }}</p>
|
||||
|
||||
{% if main.alliance_id %}
|
||||
<p class="m-0 small">{{ main.alliance_name }}</p>
|
||||
{% elif main.faction_id %}
|
||||
@@ -34,56 +57,106 @@
|
||||
<h5 class="m-0">{{ SITE_NAME }}</h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="ms-auto dropup">
|
||||
<button type="button" class="h-100 btn" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-gear fa-fw text-light"></i>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu" style="min-width: 200px;">
|
||||
<li><h6 class="dropdown-header">{% translate "Language" %}</h6></li>
|
||||
<li>
|
||||
<a class="dropdown-item">{% include "public/lang_select.html" %}</a>
|
||||
</li>
|
||||
<li><h6 class="dropdown-header">{% translate "Theme" %}</h6></li>
|
||||
|
||||
<li>
|
||||
<a class="dropdown-item">
|
||||
{% theme_select %}
|
||||
</a>
|
||||
{% include "public/lang_select.html" %}
|
||||
</li>
|
||||
|
||||
{% theme_select %}
|
||||
|
||||
{% if user.is_superuser %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">{% translate "Super User" %}</h6></li>
|
||||
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://allianceauth.readthedocs.io/" title="Alliance Auth Documentation"><i class="fa-solid fa-book fa-fw"></i> Alliance Auth Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://discord.gg/fjnHAmk" title="Alliance Auth Discord"><i class="fa-brands fa-discord fa-fw"></i> Alliance Auth Discord</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i> Alliance Auth Git</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}">
|
||||
<i class="fa-solid fa-gear fa-fw"></i> {% translate "Admin" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
{% if user.is_authenticated %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{% url 'authentication:token_management' %}">
|
||||
<i class="fa-solid fa-user-lock fa-fw"></i> Token Management
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="https://allianceauth.readthedocs.io/"
|
||||
title="{% translate 'Alliance Auth Documentation' %}"
|
||||
>
|
||||
<i class="fa-solid fa-book fa-fw"></i>
|
||||
{% translate "Alliance Auth Documentation" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="dropdown-item text-danger" href="{% url 'logout' %}" title="{% translate 'Sign Out' %}"><i class="fa-solid fa-right-from-bracket fa-fw "></i> {% translate 'Sign Out' %}</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="https://discord.gg/fjnHAmk"
|
||||
title="{% translate 'Alliance Auth Discord' %}"
|
||||
>
|
||||
<i class="fa-brands fa-discord fa-fw"></i>
|
||||
{% translate "Alliance Auth Discord" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="https://gitlab.com/allianceauth/allianceauth"
|
||||
title="{% translate 'Alliance Auth Git' %}"
|
||||
>
|
||||
<i class="fa-brands fa-gitlab fa-fw"></i>
|
||||
{% translate "Alliance Auth Git" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="{% url 'admin:index' %}"
|
||||
title="{% translate 'Admin' %}"
|
||||
>
|
||||
<i class="fa-solid fa-gear fa-fw"></i>
|
||||
{% translate "Admin" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="{% url 'authentication:token_management' %}"
|
||||
title="{% translate 'Token Management' %}"
|
||||
>
|
||||
<i class="fa-solid fa-user-lock fa-fw"></i>
|
||||
{% translate "Token Management" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
class="dropdown-item text-danger"
|
||||
href="{% url 'logout' %}"
|
||||
title="{% translate 'Sign Out' %}"
|
||||
>
|
||||
<i class="fa-solid fa-right-from-bracket fa-fw"></i>
|
||||
{% translate 'Sign Out' %}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<a class="dropdown-item text-success" href="{% url 'authentication:login' %}" title="{% translate 'Sign In' %}"> <i class="fa-solid fa-right-to-bracket fa-fw "></i> {% translate 'Sign In' %}</a>
|
||||
<a
|
||||
class="dropdown-item text-success"
|
||||
href="{% url 'authentication:login' %}"
|
||||
title="{% translate 'Sign In' %}"
|
||||
>
|
||||
<i class="fa-solid fa-right-to-bracket fa-fw"></i>
|
||||
{% translate 'Sign In' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="col-auto px-0">
|
||||
<div class="collapse collapse-horizontal" tabindex="-1" id="sidebar">
|
||||
<div>
|
||||
<div class="nav-padding navbar-dark bg-dark text-light px-0 d-flex flex-column overflow-hidden vh-100 {% if not user.is_authenticated %}position-relative{% endif %}">
|
||||
<div class="nav-padding navbar-dark text-bg-dark px-0 d-flex flex-column overflow-hidden vh-100 {% if not user.is_authenticated %}position-relative{% endif %}">
|
||||
{% if user.is_authenticated %}
|
||||
<ul id="sidebar-menu" class="navbar-nav flex-column mb-auto overflow-auto pt-2">
|
||||
<li class="d-flex flex-wrap m-2 p-2 pt-0 pb-0 mt-0 mb-0 me-0 pe-0">
|
||||
|
||||
@@ -186,7 +186,7 @@ class TestRenderDefaultMenu(TestCase):
|
||||
classes = "fa-solid fa-users-gear"
|
||||
url_name = "groupmanagement:management"
|
||||
|
||||
def render(self, request):
|
||||
def render(Self, request):
|
||||
# simulate no perms
|
||||
return ""
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class NotificationsConfig(AppConfig):
|
||||
name = 'allianceauth.notifications'
|
||||
label = 'notifications'
|
||||
verbose_name = _('Notifications')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from typing import ClassVar
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
@@ -56,7 +57,7 @@ class Notification(models.Model):
|
||||
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
viewed = models.BooleanField(default=False, db_index=True)
|
||||
|
||||
objects = NotificationManager()
|
||||
objects: ClassVar[NotificationManager] = NotificationManager()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.user}: {self.title}"
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
<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>
|
||||
<span class="badge text-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>
|
||||
<span class="badge text-bg-secondary">{{ read|length }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card border-{{ notif.level }}">
|
||||
<div class="card-header bg-{{ notif.level }}">
|
||||
<div class="card-header text-bg-{{ notif.level }}">
|
||||
{{ notif.timestamp }}
|
||||
{{ notif.title }}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class OptimerConfig(AppConfig):
|
||||
name = 'allianceauth.optimer'
|
||||
label = 'optimer'
|
||||
verbose_name = _('Fleet Operations')
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<th class="text-center">{% translate "Operation" %}</th>
|
||||
<th class="text-center">{% translate "Type" %}</th>
|
||||
<th class="text-center">{% translate "Form Up System" %}</th>
|
||||
<th class="text-center">{% translate "EVE Time" %}</th>
|
||||
<th class="text-center">{% translate "EVE time" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
{% block content %}
|
||||
<div>
|
||||
<div class="text-center mb-3">
|
||||
<div class="badge bg-primary text-start">
|
||||
<b>{% translate "Current Eve Time:" %}</b>
|
||||
<div class="badge text-bg-primary text-start">
|
||||
<b>{% translate "Current EVE time:" %}</b>
|
||||
<span id="current-time"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,8 +127,8 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current Eve time as a string
|
||||
* @returns {string} Eve time string
|
||||
* Get the current EVE time as a string
|
||||
* @returns {string} EVE time string
|
||||
*/
|
||||
const updateClock = () => {
|
||||
document.getElementById("current-time").innerHTML = getCurrentEveTimeString();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class PermissionsToolConfig(AppConfig):
|
||||
name = 'allianceauth.permissions_tool'
|
||||
label = 'permissions_tool'
|
||||
verbose_name = _('Permissions Audit')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user