Compare commits

...

8 Commits

Author SHA1 Message Date
Erik Kalkoken
ce36a9dd4a Merge branch 'seperate-out-celery-once' into 'master'
Remove dependecies from celery_once customizations

See merge request allianceauth/allianceauth!1491
2026-01-04 23:52:03 +01:00
Ariel Rin
99c65d2a5d Merge branch 'helpful-comments' into 'master'
[ADD] Some helpful comments

See merge request allianceauth/allianceauth!1780
2026-01-02 02:43:08 +00:00
Ariel Rin
55125a8ff3 Merge branch 'missing-logo' into 'master'
[ADD] Missing logo to list of available SVG logos

See merge request allianceauth/allianceauth!1781
2026-01-02 02:35:23 +00:00
Ariel Rin
2fd0fcdbcb Merge branch 'notifications_fix' into 'master'
[Fix] wrong delete read url

See merge request allianceauth/allianceauth!1783
2026-01-02 02:35:11 +00:00
Swashman
2fe7bcf20e [Fix] wrong delete read url 2026-01-02 02:35:11 +00:00
Peter Pfeufer
453512db64 [ADD] Missing logo to list of available SVG logos 2025-11-23 14:02:30 +01:00
Peter Pfeufer
4047159fd1 [ADD] Some helpful comments
Since this is one of the most occurring issues when editing `local.py`
2025-11-22 22:29:24 +01:00
Erik Kalkoken
0dd47e72bc Move celery once config into own package 2023-02-28 15:16:51 +01:00
13 changed files with 85 additions and 84 deletions

View File

@@ -33,7 +33,7 @@
{% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-check-double" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
{% translate "Delete all read notifications" as nav_item_title %}
{% url "notifications:mark_all_read" as nav_item_link %}
{% url "notifications:delete_all_read" as nav_item_link %}
{% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-trash-can" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
{% endblock %}

View File

@@ -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': {}
}

View File

@@ -57,10 +57,10 @@ DATABASES['default'] = {
# CCP's developer portal
# Logging in to auth requires the publicData scope (can be overridden through the
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback" # Do NOT change this line!
ESI_SSO_CLIENT_ID = ''
ESI_SSO_CLIENT_SECRET = ''
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback"
ESI_USER_CONTACT_EMAIL = '' # A server maintainer that CCP can contact in case of issues.
ESI_USER_CONTACT_EMAIL = '' # A server maintainer that CCP can contact in case of issues.
# By default, emails are validated before new users can log in.
# It's recommended to use a free service like SparkPost or Elastic Email to send email.

View 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)

View 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

View 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))

View File

@@ -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}')

View File

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

View File

@@ -17,9 +17,7 @@ DATABASES["default"] = {
"PASSWORD": os.environ.get("AA_DB_PASSWORD"),
"HOST": os.environ.get("AA_DB_HOST"),
"PORT": os.environ.get("AA_DB_PORT", "3306"),
"OPTIONS": {
"charset": os.environ.get("AA_DB_CHARSET", "utf8mb4")
}
"OPTIONS": {"charset": os.environ.get("AA_DB_CHARSET", "utf8mb4")},
}
# Register an application at https://developers.eveonline.com for Authentication
@@ -27,10 +25,9 @@ DATABASES["default"] = {
# to https://example.com/sso/callback substituting your domain for example.com
# Logging in to auth requires the publicData scope (can be overridden through the
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback" # Do NOT change this line!
ESI_SSO_CLIENT_ID = os.environ.get("ESI_SSO_CLIENT_ID")
ESI_SSO_CLIENT_SECRET = os.environ.get("ESI_SSO_CLIENT_SECRET")
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback"
ESI_USER_CONTACT_EMAIL = os.environ.get(
"ESI_USER_CONTACT_EMAIL"
) # A server maintainer that CCP can contact in case of issues.
@@ -70,7 +67,6 @@ INSTALLED_APPS += [
# 'allianceauth.permissions_tool',
# 'allianceauth.srp',
# 'allianceauth.timerboard',
# https://allianceauth.readthedocs.io/en/latest/features/services/index.html
# 'allianceauth.services.modules.discord',
# 'allianceauth.services.modules.discourse',

View File

@@ -28,6 +28,7 @@ The following icons are available in the Alliance Auth SVG sprite:
- `aa-logo`: The Alliance Auth logo
- `aa-loading-spinner`: A loading spinner icon
- `aa-mumble-logo`: The Mumble logo
### Alliance Auth Logo

View File

@@ -20,8 +20,8 @@ Make the following changes in your auth project's settings file (`local.py`):
# Be sure to set the callback URLto https://example.com/discord/callback/
# substituting your domain for example.com in Discord's developer portal
# (Be sure to add the trailing slash)
DISCORD_CALLBACK_URL = f"{SITE_URL}/discord/callback/" # Do NOT change this line!
DISCORD_GUILD_ID = ''
DISCORD_CALLBACK_URL = f"{SITE_URL}/discord/callback/"
DISCORD_APP_ID = ''
DISCORD_APP_SECRET = ''
DISCORD_BOT_TOKEN = ''

View File

@@ -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': {}
}