mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-12-06 12:51:41 +01:00
Compare commits
21 Commits
b7bdff4a91
...
c2f69cd7e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2f69cd7e6 | ||
|
|
5d56f0a66c | ||
|
|
0fbdbf3a8d | ||
|
|
5291bf6896 | ||
|
|
bd6c0fede5 | ||
|
|
23fe1703c3 | ||
|
|
9139b0da56 | ||
|
|
80145b313f | ||
|
|
e96bdd12f9 | ||
|
|
513b7b88f4 | ||
|
|
5e3fc5c1cb | ||
|
|
d3069db046 | ||
|
|
24863eaf9e | ||
|
|
d138bd61c5 | ||
|
|
bff20ddd5d | ||
|
|
493e694410 | ||
|
|
8c41d9da58 | ||
|
|
9e0358a3ce | ||
|
|
19bb6856a2 | ||
|
|
9751315b97 | ||
|
|
0dd47e72bc |
@ -25,7 +25,7 @@ before_script:
|
||||
pre-commit-check:
|
||||
<<: *only-default
|
||||
stage: pre-commit
|
||||
image: python:3.11-bookworm
|
||||
image: python:3.11-trixie
|
||||
# variables:
|
||||
# PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||
# cache:
|
||||
@ -65,7 +65,7 @@ test-3.8-core:
|
||||
|
||||
test-3.9-core:
|
||||
<<: *only-default
|
||||
image: python:3.9-bookworm
|
||||
image: python:3.9-trixie
|
||||
script:
|
||||
- tox -e py39-core
|
||||
artifacts:
|
||||
@ -77,7 +77,7 @@ test-3.9-core:
|
||||
|
||||
test-3.10-core:
|
||||
<<: *only-default
|
||||
image: python:3.10-bookworm
|
||||
image: python:3.10-trixie
|
||||
script:
|
||||
- tox -e py310-core
|
||||
artifacts:
|
||||
@ -89,7 +89,7 @@ test-3.10-core:
|
||||
|
||||
test-3.11-core:
|
||||
<<: *only-default
|
||||
image: python:3.11-bookworm
|
||||
image: python:3.11-trixie
|
||||
script:
|
||||
- tox -e py311-core
|
||||
artifacts:
|
||||
@ -101,7 +101,7 @@ test-3.11-core:
|
||||
|
||||
test-3.12-core:
|
||||
<<: *only-default
|
||||
image: python:3.12-bookworm
|
||||
image: python:3.12-trixie
|
||||
script:
|
||||
- tox -e py312-core
|
||||
artifacts:
|
||||
@ -125,7 +125,7 @@ test-3.8-all:
|
||||
|
||||
test-3.9-all:
|
||||
<<: *only-default
|
||||
image: python:3.9-bookworm
|
||||
image: python:3.9-trixie
|
||||
script:
|
||||
- tox -e py39-all
|
||||
artifacts:
|
||||
@ -137,7 +137,7 @@ test-3.9-all:
|
||||
|
||||
test-3.10-all:
|
||||
<<: *only-default
|
||||
image: python:3.10-bookworm
|
||||
image: python:3.10-trixie
|
||||
script:
|
||||
- tox -e py310-all
|
||||
artifacts:
|
||||
@ -149,7 +149,7 @@ test-3.10-all:
|
||||
|
||||
test-3.11-all:
|
||||
<<: *only-default
|
||||
image: python:3.11-bookworm
|
||||
image: python:3.11-trixie
|
||||
script:
|
||||
- tox -e py311-all
|
||||
artifacts:
|
||||
@ -162,7 +162,7 @@ test-3.11-all:
|
||||
|
||||
test-3.12-all:
|
||||
<<: *only-default
|
||||
image: python:3.12-bookworm
|
||||
image: python:3.12-trixie
|
||||
script:
|
||||
- tox -e py312-all
|
||||
artifacts:
|
||||
@ -174,7 +174,7 @@ test-3.12-all:
|
||||
|
||||
build-test:
|
||||
stage: test
|
||||
image: python:3.11-bookworm
|
||||
image: python:3.11-trixie
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
@ -193,13 +193,13 @@ build-test:
|
||||
|
||||
test-docs:
|
||||
<<: *only-default
|
||||
image: python:3.11-bookworm
|
||||
image: python:3.11-trixie
|
||||
script:
|
||||
- tox -e docs
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.11-bookworm
|
||||
image: python:3.11-trixie
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
|
||||
@ -84,7 +84,7 @@ repos:
|
||||
args:
|
||||
- --indent=4
|
||||
additional_dependencies:
|
||||
- tox==4.31.0 # https://github.com/tox-dev/tox/releases/latest
|
||||
- tox==4.32.0 # https://github.com/tox-dev/tox/releases/latest
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.24.1
|
||||
hooks:
|
||||
|
||||
@ -320,8 +320,8 @@ def click_fatlink_view(request, token, fat_hash=None):
|
||||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
f"Cannot register the fleet participation for {character.character_name}. The character needs to be online."
|
||||
),
|
||||
"Cannot register the fleet participation for {character_name}. The character needs to be online."
|
||||
).format(character_name=character.character_name)
|
||||
)
|
||||
|
||||
return redirect('fatlink:view')
|
||||
|
||||
@ -47,6 +47,12 @@
|
||||
/* Side Navigation
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
.sidemenu-total-notifications-badge {
|
||||
position: absolute;
|
||||
left: 28px;
|
||||
font-size: 42.5% !important;
|
||||
}
|
||||
|
||||
#sidebar > div {
|
||||
width: 325px;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from __future__ import annotations
|
||||
from __future__ import annotations # Still needed for Python 3.8, replaced with better implementations in Py39+
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@ -26,7 +26,7 @@ app.conf.task_default_priority = 5 # anything called with the task.delay() will
|
||||
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
|
||||
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'backend': 'allianceauth.services.celery_once.backends.DjangoBackend',
|
||||
'settings': {}
|
||||
}
|
||||
|
||||
|
||||
@ -11,10 +11,10 @@ environment =
|
||||
[program:beat]
|
||||
command = %(ENV_AA_COMMAND_CELERY)s
|
||||
-A %(ENV_AA_PROJECT_NAME)s beat
|
||||
directory = %(ENV_AA_VENV_DIRECTORY)s
|
||||
directory = %(ENV_AA_PROJECT_DIRECTORY)s
|
||||
user = %(ENV_AA_USER)s
|
||||
stdout_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/%(program_name)s.log
|
||||
stderr_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/%(program_name)s.log
|
||||
stdout_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/%(program_name)s.log
|
||||
stderr_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/%(program_name)s.log
|
||||
autostart = true
|
||||
autorestart = true
|
||||
startsecs = 10
|
||||
@ -26,12 +26,12 @@ command = %(ENV_AA_COMMAND_CELERY)s
|
||||
--pool=threads
|
||||
--concurrency=5
|
||||
-n %(program_name)s_%(process_num)02d
|
||||
directory = %(ENV_AA_VENV_DIRECTORY)s
|
||||
directory = %(ENV_AA_PROJECT_DIRECTORY)s
|
||||
user = %(ENV_AA_USER)s
|
||||
numprocs = 1
|
||||
process_name = %(program_name)s_%(process_num)02d
|
||||
stdout_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/%(program_name)s.log
|
||||
stderr_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/%(program_name)s.log
|
||||
stdout_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/%(program_name)s.log
|
||||
stderr_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/%(program_name)s.log
|
||||
autostart = true
|
||||
autorestart = true
|
||||
startsecs = 10
|
||||
@ -42,12 +42,12 @@ priority = 998
|
||||
{% if gunicorn %}
|
||||
[program:gunicorn]
|
||||
user = %(ENV_AA_USER)s
|
||||
directory = %(ENV_AA_VENV_DIRECTORY)s
|
||||
directory = %(ENV_AA_PROJECT_DIRECTORY)s
|
||||
command = %(ENV_AA_COMMAND_GUNICORN)s %(ENV_AA_PROJECT_NAME)s.wsgi
|
||||
--workers=3
|
||||
--timeout 120
|
||||
stdout_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/%(program_name)s.log
|
||||
stderr_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/%(program_name)s.log
|
||||
stdout_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/%(program_name)s.log
|
||||
stderr_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/%(program_name)s.log
|
||||
autostart = true
|
||||
autorestart = true
|
||||
stopsignal = INT
|
||||
@ -57,10 +57,10 @@ stopsignal = INT
|
||||
command = %(ENV_AA_COMMAND_MEMMON)s
|
||||
-p worker_00=256MB
|
||||
-p gunicorn=256MB
|
||||
directory = %(ENV_AA_VENV_DIRECTORY)s
|
||||
directory = %(ENV_AA_PROJECT_DIRECTORY)s
|
||||
events = TICK_60
|
||||
stdout_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/memmon.log
|
||||
stderr_logfile = %(ENV_AA_VENV_DIRECTORY)s/log/memmon.log
|
||||
stdout_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/memmon.log
|
||||
stderr_logfile = %(ENV_AA_PROJECT_DIRECTORY)s/log/memmon.log
|
||||
|
||||
[group:{{ project_name }}]
|
||||
programs = beat,worker{% if gunicorn %},gunicorn{% endif %}
|
||||
|
||||
@ -100,7 +100,10 @@ class BaseSetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, Upda
|
||||
def post(self, request, *args, **kwargs):
|
||||
result = super().post(request, *args, **kwargs)
|
||||
if self.get_form().is_valid():
|
||||
messages.success(request, _(f"Successfully set your {self.service_name} password"))
|
||||
messages.success(
|
||||
request,
|
||||
_("Successfully set your {service_name} password").format(service_name=self.service_name)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
0
allianceauth/services/celery_once/__init__.py
Normal file
0
allianceauth/services/celery_once/__init__.py
Normal file
19
allianceauth/services/celery_once/backends.py
Normal file
19
allianceauth/services/celery_once/backends.py
Normal file
@ -0,0 +1,19 @@
|
||||
from celery_once import AlreadyQueued
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class DjangoBackend:
|
||||
"""Locking backend for celery once."""
|
||||
|
||||
def __init__(self, settings):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def raise_or_lock(key, timeout):
|
||||
acquired = cache.add(key=key, value="lock", timeout=timeout)
|
||||
if not acquired:
|
||||
raise AlreadyQueued(int(cache.ttl(key)))
|
||||
|
||||
@staticmethod
|
||||
def clear_lock(key):
|
||||
return cache.delete(key)
|
||||
8
allianceauth/services/celery_once/tasks.py
Normal file
8
allianceauth/services/celery_once/tasks.py
Normal file
@ -0,0 +1,8 @@
|
||||
from celery_once import QueueOnce as BaseTask
|
||||
|
||||
|
||||
class QueueOnce(BaseTask):
|
||||
"""QueueOnce class with custom defaults."""
|
||||
|
||||
once = BaseTask.once
|
||||
once["graceful"] = True
|
||||
47
allianceauth/services/celery_once/tests.py
Normal file
47
allianceauth/services/celery_once/tests.py
Normal file
@ -0,0 +1,47 @@
|
||||
from celery_once import AlreadyQueued
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.services.celery_once.backends import DjangoBackend
|
||||
|
||||
|
||||
class TestDjangoBackend(TestCase):
|
||||
TEST_KEY = "my-django-backend-test-key"
|
||||
TIMEOUT = 1800
|
||||
|
||||
def setUp(self) -> None:
|
||||
cache.delete(self.TEST_KEY)
|
||||
self.backend = DjangoBackend(dict())
|
||||
|
||||
def test_can_get_lock(self):
|
||||
"""
|
||||
when lock can be acquired
|
||||
then set it with timeout
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
self.assertAlmostEqual(cache.ttl(self.TEST_KEY), self.TIMEOUT, delta=2)
|
||||
|
||||
def test_when_cant_get_lock_raise_exception(self):
|
||||
"""
|
||||
when lock can bot be acquired
|
||||
then raise AlreadyQueued exception with countdown
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
try:
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
except Exception as ex:
|
||||
self.assertIsInstance(ex, AlreadyQueued)
|
||||
self.assertAlmostEqual(ex.countdown, self.TIMEOUT, delta=2)
|
||||
|
||||
def test_can_clear_lock(self):
|
||||
"""
|
||||
when a lock exists
|
||||
then can get a new lock after clearing it
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
self.backend.clear_lock(self.TEST_KEY)
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
@ -3,33 +3,11 @@ import logging
|
||||
from celery import shared_task
|
||||
from django.contrib.auth.models import User
|
||||
from .hooks import ServicesHook
|
||||
from celery_once import QueueOnce as BaseTask, AlreadyQueued
|
||||
from django.core.cache import cache
|
||||
|
||||
from .celery_once.tasks import QueueOnce # noqa: F401 - for backwards compatibility
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QueueOnce(BaseTask):
|
||||
once = BaseTask.once
|
||||
once['graceful'] = True
|
||||
|
||||
|
||||
class DjangoBackend:
|
||||
def __init__(self, settings):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def raise_or_lock(key, timeout):
|
||||
acquired = cache.add(key=key, value="lock", timeout=timeout)
|
||||
if not acquired:
|
||||
raise AlreadyQueued(int(cache.ttl(key)))
|
||||
|
||||
@staticmethod
|
||||
def clear_lock(key):
|
||||
return cache.delete(key)
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
def validate_services(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
@ -38,7 +16,7 @@ def validate_services(self, pk):
|
||||
for svc in ServicesHook.get_services():
|
||||
try:
|
||||
svc.validate_user(user)
|
||||
except:
|
||||
except Exception:
|
||||
logger.exception(f'Exception running validate_user for services module {svc} on user {user}')
|
||||
|
||||
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
from unittest import mock
|
||||
|
||||
from celery_once import AlreadyQueued
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.test import override_settings, TestCase
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.services.tasks import validate_services, update_groups_for_user
|
||||
|
||||
from ..tasks import DjangoBackend
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
class ServicesTasksTestCase(TestCase):
|
||||
@ -46,46 +41,3 @@ class ServicesTasksTestCase(TestCase):
|
||||
self.assertTrue(svc.update_groups.called)
|
||||
args, _ = svc.update_groups.call_args
|
||||
self.assertEqual(self.member, args[0]) # Assert correct user
|
||||
|
||||
|
||||
class TestDjangoBackend(TestCase):
|
||||
|
||||
TEST_KEY = "my-django-backend-test-key"
|
||||
TIMEOUT = 1800
|
||||
|
||||
def setUp(self) -> None:
|
||||
cache.delete(self.TEST_KEY)
|
||||
self.backend = DjangoBackend(dict())
|
||||
|
||||
def test_can_get_lock(self):
|
||||
"""
|
||||
when lock can be acquired
|
||||
then set it with timetout
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
self.assertAlmostEqual(cache.ttl(self.TEST_KEY), self.TIMEOUT, delta=2)
|
||||
|
||||
def test_when_cant_get_lock_raise_exception(self):
|
||||
"""
|
||||
when lock can bot be acquired
|
||||
then raise AlreadyQueued exception with countdown
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
try:
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
except Exception as ex:
|
||||
self.assertIsInstance(ex, AlreadyQueued)
|
||||
self.assertAlmostEqual(ex.countdown, self.TIMEOUT, delta=2)
|
||||
|
||||
def test_can_clear_lock(self):
|
||||
"""
|
||||
when a lock exists
|
||||
then can get a new lock after clearing it
|
||||
"""
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
|
||||
self.backend.clear_lock(self.TEST_KEY)
|
||||
self.backend.raise_or_lock(self.TEST_KEY, self.TIMEOUT)
|
||||
self.assertIsNotNone(cache.get(self.TEST_KEY))
|
||||
|
||||
@ -34,6 +34,9 @@ class SrpFleetUserRequestForm(forms.Form):
|
||||
_("Invalid Link. Please use zkillboard.com or kb.evetools.org")
|
||||
)
|
||||
|
||||
if re.match(r"^http[s]?:\/\/zkillboard\.com\/", data) and not data.endswith("/"):
|
||||
data += "/"
|
||||
|
||||
# Check if it's an actual kill mail
|
||||
if not any(
|
||||
re.match(regex, data)
|
||||
|
||||
27
allianceauth/srp/tests/test_form.py
Normal file
27
allianceauth/srp/tests/test_form.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.srp.form import SrpFleetUserRequestForm
|
||||
|
||||
|
||||
class TestForms(TestCase):
|
||||
|
||||
def test_allow_missing_trailing_slash_zkillboard(self):
|
||||
form = SrpFleetUserRequestForm(
|
||||
data = {
|
||||
"killboard_link": "https://zkillboard.com/kill/130429493",
|
||||
"additional_info": "Details",
|
||||
}
|
||||
)
|
||||
form.cleaned_data = {"killboard_link": "https://zkillboard.com/kill/130429493"}
|
||||
|
||||
self.assertEqual("https://zkillboard.com/kill/130429493/", form.clean_killboard_link())
|
||||
|
||||
def test_not_add_trailling_slash_kb_evetools(self):
|
||||
form = SrpFleetUserRequestForm(
|
||||
data = {
|
||||
"killboard_link": "https://kb.evetools.org/kill/130429493",
|
||||
}
|
||||
)
|
||||
form.cleaned_data = {"killboard_link": "https://kb.evetools.org/kill/130429493"}
|
||||
|
||||
self.assertEqual("https://kb.evetools.org/kill/130429493", form.clean_killboard_link())
|
||||
@ -1,13 +0,0 @@
|
||||
$(document).ready(() => {
|
||||
'use strict';
|
||||
|
||||
const activeChildMenuItem = document.querySelector('ul#sidebar-menu ul.collapse a.active');
|
||||
|
||||
if (activeChildMenuItem) {
|
||||
const activeChildMenuUl = activeChildMenuItem.closest('ul');
|
||||
activeChildMenuUl.classList.add('show');
|
||||
|
||||
document.querySelectorAll(`[data-bs-target^="#${activeChildMenuUl.id}"]`)
|
||||
.forEach(element => element.setAttribute('aria-expanded', true));
|
||||
}
|
||||
});
|
||||
57
allianceauth/static/allianceauth/js/sidebar.js
Normal file
57
allianceauth/static/allianceauth/js/sidebar.js
Normal file
@ -0,0 +1,57 @@
|
||||
$(document).ready(() => {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Collect all badges in the sidebar menu that are not part of a collapsible submenu, and calculate the total notification count.
|
||||
* Show a total notification badge in the navbar if there are any notifications.
|
||||
*/
|
||||
const totalNotificationsBadge = () => {
|
||||
const badges = [];
|
||||
let notificationCount = 0;
|
||||
|
||||
document.querySelectorAll('#sidebar-menu .badge').forEach(b => {
|
||||
const li = b.closest('li');
|
||||
|
||||
if (!li || !li.querySelector('ul.collapse')) {
|
||||
badges.push(b);
|
||||
notificationCount += parseInt(b.textContent);
|
||||
}
|
||||
});
|
||||
|
||||
if (badges.length > 0 && notificationCount > 0) {
|
||||
const notificationBadge = document.createElement('span');
|
||||
|
||||
notificationBadge.classList.add(
|
||||
'badge',
|
||||
'text-bg-danger',
|
||||
'align-self-center',
|
||||
'sidemenu-notification-badge',
|
||||
'sidemenu-total-notifications-badge'
|
||||
);
|
||||
notificationBadge.textContent = String(notificationCount);
|
||||
|
||||
document.querySelector('a.navbar-brand i.fa-solid').prepend(notificationBadge);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the active child menu item in the sidebar menu, if any, and ensure its parent submenu is expanded.
|
||||
*/
|
||||
const expandChildMenu = () => {
|
||||
const activeChildMenuItem = document.querySelector('ul#sidebar-menu ul.collapse a.active');
|
||||
|
||||
if (activeChildMenuItem) {
|
||||
const activeChildMenuUl = activeChildMenuItem.closest('ul');
|
||||
activeChildMenuUl.classList.add('show');
|
||||
|
||||
document.querySelectorAll(`[data-bs-target^="#${activeChildMenuUl.id}"]`)
|
||||
.forEach(element => element.setAttribute('aria-expanded', 'true'));
|
||||
}
|
||||
};
|
||||
|
||||
// Execute functions on document ready
|
||||
[
|
||||
totalNotificationsBadge,
|
||||
expandChildMenu
|
||||
].forEach(fn => fn());
|
||||
});
|
||||
@ -1,3 +1,3 @@
|
||||
{% load sri %}
|
||||
|
||||
{% sri_static 'allianceauth/js/sidebar-collapse.js' %}
|
||||
{% sri_static 'allianceauth/js/sidebar.js' %}
|
||||
|
||||
11
allianceauth/templates/django_bootstrap5/field_errors.html
Normal file
11
allianceauth/templates/django_bootstrap5/field_errors.html
Normal file
@ -0,0 +1,11 @@
|
||||
{# Template override to display the errors of django Forms when using `boostrap_form` form the `django-bootstrap5` #}
|
||||
{# library. #}
|
||||
{# See: https://github.com/zostera/django-bootstrap5/pull/767 #}
|
||||
|
||||
{% if field_errors %}
|
||||
<div id="{{ field.auto_id }}_error" class="invalid-feedback">
|
||||
{% for text in field_errors %}
|
||||
<div>{{ text }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -15,7 +15,7 @@ license = { file = "LICENSE" }
|
||||
authors = [
|
||||
{ name = "Alliance Auth", email = "adarnof@gmail.com" },
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
requires-python = ">=3.8,<3.13"
|
||||
classifiers = [
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Celery",
|
||||
@ -31,8 +31,6 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
]
|
||||
|
||||
@ -26,7 +26,7 @@ app.conf.task_default_priority = 5 # anything called with the task.delay() will
|
||||
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
|
||||
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'backend': 'allianceauth.services.celery_once.backends.DjangoBackend',
|
||||
'settings': {}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user