mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-05 14:46:20 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36b3077caa | ||
|
|
1786f3a642 | ||
|
|
55927c6f15 | ||
|
|
8fbe0ba45d | ||
|
|
1563805ddb | ||
|
|
c58ed53369 | ||
|
|
32128ace1c | ||
|
|
7290eaad7e | ||
|
|
f23d4f4dd1 | ||
|
|
ab3f10e6f2 | ||
|
|
20187cc73e | ||
|
|
1f55fbfccc |
@@ -5,7 +5,7 @@ manage online service access.
|
|||||||
# This will make sure the app is always imported when
|
# This will make sure the app is always imported when
|
||||||
# Django starts so that shared_task will use this app.
|
# Django starts so that shared_task will use this app.
|
||||||
|
|
||||||
__version__ = '3.6.0'
|
__version__ = '3.6.1'
|
||||||
__title__ = 'Alliance Auth'
|
__title__ = 'Alliance Auth'
|
||||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||||
NAME = f'{__title__} v{__version__}'
|
NAME = f'{__title__} v{__version__}'
|
||||||
|
|||||||
@@ -5,59 +5,27 @@ import logging
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pytz import utc
|
from pytz import utc
|
||||||
from redis import Redis, RedisError
|
from redis import Redis
|
||||||
|
|
||||||
from allianceauth.utils.cache import get_redis_client
|
from .helpers import get_redis_client_or_stub
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class _RedisStub:
|
|
||||||
"""Stub of a Redis client.
|
|
||||||
|
|
||||||
It's purpose is to prevent EventSeries objects from trying to access Redis
|
|
||||||
when it is not available. e.g. when the Sphinx docs are rendered by readthedocs.org.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def incr(self, *args, **kwargs):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def zadd(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def zcount(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def zrangebyscore(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EventSeries:
|
class EventSeries:
|
||||||
"""API for recording and analyzing a series of events."""
|
"""API for recording and analyzing a series of events."""
|
||||||
|
|
||||||
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
|
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
|
||||||
|
|
||||||
def __init__(self, key_id: str, redis: Redis = None) -> None:
|
def __init__(self, key_id: str, redis: Optional[Redis] = None) -> None:
|
||||||
self._redis = get_redis_client() if not redis else redis
|
self._redis = get_redis_client_or_stub() if not redis else redis
|
||||||
try:
|
|
||||||
if not self._redis.ping():
|
|
||||||
raise RuntimeError()
|
|
||||||
except (AttributeError, RedisError, RuntimeError):
|
|
||||||
logger.exception(
|
|
||||||
"Failed to establish a connection with Redis. "
|
|
||||||
"This EventSeries object is disabled.",
|
|
||||||
)
|
|
||||||
self._redis = _RedisStub()
|
|
||||||
self._key_id = str(key_id)
|
self._key_id = str(key_id)
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_disabled(self):
|
def is_disabled(self):
|
||||||
"""True when this object is disabled, e.g. Redis was not available at startup."""
|
"""True when this object is disabled, e.g. Redis was not available at startup."""
|
||||||
return isinstance(self._redis, _RedisStub)
|
return hasattr(self._redis, "IS_STUB")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _key_counter(self):
|
def _key_counter(self):
|
||||||
@@ -97,7 +65,7 @@ class EventSeries:
|
|||||||
self._redis.delete(self._key_counter)
|
self._redis.delete(self._key_counter)
|
||||||
|
|
||||||
def count(self, earliest: dt.datetime = None, latest: dt.datetime = None) -> int:
|
def count(self, earliest: dt.datetime = None, latest: dt.datetime = None) -> int:
|
||||||
"""Count of events, can be restricted to given timeframe.
|
"""Count of events, can be restricted to given time frame.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- earliest: Date of first events to count(inclusive), or -infinite if not specified
|
- earliest: Date of first events to count(inclusive), or -infinite if not specified
|
||||||
|
|||||||
@@ -1,21 +1,63 @@
|
|||||||
"""Helpers for Task Statistics."""
|
"""Helpers for Task Statistics."""
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from redis import Redis, RedisError
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
from allianceauth.utils.cache import get_redis_client
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class _RedisStub:
|
||||||
|
"""Stub of a Redis client.
|
||||||
|
|
||||||
|
It's purpose is to prevent EventSeries objects from trying to access Redis
|
||||||
|
when it is not available. e.g. when the Sphinx docs are rendered by readthedocs.org.
|
||||||
|
"""
|
||||||
|
|
||||||
|
IS_STUB = True
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def incr(self, *args, **kwargs):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def zadd(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def zcount(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def zrangebyscore(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ItemCounter:
|
class ItemCounter:
|
||||||
"""A process safe item counter."""
|
"""A process safe item counter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- name: Unique name for the counter
|
||||||
|
- minimum: Counter can not go below the minimum, when set
|
||||||
|
- redis: A Redis client. Will use AA's cache client by default
|
||||||
|
"""
|
||||||
|
|
||||||
CACHE_KEY_BASE = "allianceauth-item-counter"
|
CACHE_KEY_BASE = "allianceauth-item-counter"
|
||||||
DEFAULT_CACHE_TIMEOUT = 24 * 3600
|
DEFAULT_CACHE_TIMEOUT = 24 * 3600
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
def __init__(
|
||||||
|
self, name: str, minimum: Optional[int] = None, redis: Optional[Redis] = None
|
||||||
|
) -> None:
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError("Must define a name")
|
raise ValueError("Must define a name")
|
||||||
|
|
||||||
self._name = str(name)
|
self._name = str(name)
|
||||||
|
self._minimum = minimum
|
||||||
|
self._redis = get_redis_client_or_stub() if not redis else redis
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _cache_key(self) -> str:
|
def _cache_key(self) -> str:
|
||||||
@@ -23,7 +65,11 @@ class ItemCounter:
|
|||||||
|
|
||||||
def reset(self, init_value: int = 0):
|
def reset(self, init_value: int = 0):
|
||||||
"""Reset counter to initial value."""
|
"""Reset counter to initial value."""
|
||||||
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
|
with self._redis.lock(f"{self.CACHE_KEY_BASE}-reset"):
|
||||||
|
if self._minimum is not None and init_value < self._minimum:
|
||||||
|
raise ValueError("Can not reset below minimum")
|
||||||
|
|
||||||
|
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
|
||||||
|
|
||||||
def incr(self, delta: int = 1):
|
def incr(self, delta: int = 1):
|
||||||
"""Increment counter by delta."""
|
"""Increment counter by delta."""
|
||||||
@@ -34,11 +80,29 @@ class ItemCounter:
|
|||||||
|
|
||||||
def decr(self, delta: int = 1):
|
def decr(self, delta: int = 1):
|
||||||
"""Decrement counter by delta."""
|
"""Decrement counter by delta."""
|
||||||
try:
|
with self._redis.lock(f"{self.CACHE_KEY_BASE}-decr"):
|
||||||
cache.decr(self._cache_key, delta)
|
if self._minimum is not None and self.value() == self._minimum:
|
||||||
except ValueError:
|
return
|
||||||
pass
|
try:
|
||||||
|
cache.decr(self._cache_key, delta)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
def value(self) -> Optional[int]:
|
def value(self) -> Optional[int]:
|
||||||
"""Return current value or None if not yet initialized."""
|
"""Return current value or None if not yet initialized."""
|
||||||
return cache.get(self._cache_key)
|
return cache.get(self._cache_key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_redis_client_or_stub() -> Redis:
|
||||||
|
"""Return AA's default cache client or a stub if Redis is not available."""
|
||||||
|
redis = get_redis_client()
|
||||||
|
try:
|
||||||
|
if not redis.ping():
|
||||||
|
raise RuntimeError()
|
||||||
|
except (AttributeError, RedisError, RuntimeError):
|
||||||
|
logger.exception(
|
||||||
|
"Failed to establish a connection with Redis. "
|
||||||
|
"This EventSeries object is disabled.",
|
||||||
|
)
|
||||||
|
return _RedisStub()
|
||||||
|
return redis
|
||||||
|
|||||||
@@ -1,48 +1,19 @@
|
|||||||
import datetime as dt
|
import datetime as dt
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from pytz import utc
|
from pytz import utc
|
||||||
from redis import RedisError
|
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from allianceauth.authentication.task_statistics.event_series import (
|
from allianceauth.authentication.task_statistics.event_series import (
|
||||||
EventSeries,
|
EventSeries,
|
||||||
_RedisStub,
|
|
||||||
)
|
)
|
||||||
|
from allianceauth.authentication.task_statistics.helpers import _RedisStub
|
||||||
|
|
||||||
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
|
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
|
||||||
|
|
||||||
|
|
||||||
class TestEventSeries(TestCase):
|
class TestEventSeries(TestCase):
|
||||||
def test_should_abort_without_redis_client(self):
|
|
||||||
# when
|
|
||||||
with patch(MODULE_PATH + ".get_redis_client") as mock:
|
|
||||||
mock.return_value = None
|
|
||||||
events = EventSeries("dummy")
|
|
||||||
# then
|
|
||||||
self.assertTrue(events._redis, _RedisStub)
|
|
||||||
self.assertTrue(events.is_disabled)
|
|
||||||
|
|
||||||
def test_should_disable_itself_if_redis_not_available_1(self):
|
|
||||||
# when
|
|
||||||
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
|
|
||||||
mock_get_master_client.return_value.ping.side_effect = RedisError
|
|
||||||
events = EventSeries("dummy")
|
|
||||||
# then
|
|
||||||
self.assertIsInstance(events._redis, _RedisStub)
|
|
||||||
self.assertTrue(events.is_disabled)
|
|
||||||
|
|
||||||
def test_should_disable_itself_if_redis_not_available_2(self):
|
|
||||||
# when
|
|
||||||
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
|
|
||||||
mock_get_master_client.return_value.ping.return_value = False
|
|
||||||
events = EventSeries("dummy")
|
|
||||||
# then
|
|
||||||
self.assertIsInstance(events._redis, _RedisStub)
|
|
||||||
self.assertTrue(events.is_disabled)
|
|
||||||
|
|
||||||
def test_should_add_event(self):
|
def test_should_add_event(self):
|
||||||
# given
|
# given
|
||||||
events = EventSeries("dummy")
|
events = EventSeries("dummy")
|
||||||
@@ -166,3 +137,15 @@ class TestEventSeries(TestCase):
|
|||||||
results = events.all()
|
results = events.all()
|
||||||
# then
|
# then
|
||||||
self.assertEqual(len(results), 2)
|
self.assertEqual(len(results), 2)
|
||||||
|
|
||||||
|
def test_should_not_report_as_disabled_when_initialized_normally(self):
|
||||||
|
# given
|
||||||
|
events = EventSeries("dummy")
|
||||||
|
# when/then
|
||||||
|
self.assertFalse(events.is_disabled)
|
||||||
|
|
||||||
|
def test_should_report_as_disabled_when_initialized_with_redis_stub(self):
|
||||||
|
# given
|
||||||
|
events = EventSeries("dummy", redis=_RedisStub())
|
||||||
|
# when/then
|
||||||
|
self.assertTrue(events.is_disabled)
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from redis import RedisError
|
||||||
|
|
||||||
|
from allianceauth.authentication.task_statistics.helpers import (
|
||||||
|
ItemCounter, _RedisStub, get_redis_client_or_stub,
|
||||||
|
)
|
||||||
|
|
||||||
|
MODULE_PATH = "allianceauth.authentication.task_statistics.helpers"
|
||||||
|
|
||||||
|
COUNTER_NAME = "test-counter"
|
||||||
|
|
||||||
|
|
||||||
|
class TestItemCounter(TestCase):
|
||||||
|
def test_can_create_counter(self):
|
||||||
|
# when
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
# then
|
||||||
|
self.assertIsInstance(counter, ItemCounter)
|
||||||
|
|
||||||
|
def test_can_reset_counter_to_default(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
# when
|
||||||
|
counter.reset()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 0)
|
||||||
|
|
||||||
|
def test_can_reset_counter_to_custom_value(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
# when
|
||||||
|
counter.reset(42)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 42)
|
||||||
|
|
||||||
|
def test_can_increment_counter_by_default(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.incr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 1)
|
||||||
|
|
||||||
|
def test_can_increment_counter_by_custom_value(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.incr(8)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 8)
|
||||||
|
|
||||||
|
def test_can_decrement_counter_by_default(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(9)
|
||||||
|
# when
|
||||||
|
counter.decr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 8)
|
||||||
|
|
||||||
|
def test_can_decrement_counter_by_custom_value(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(9)
|
||||||
|
# when
|
||||||
|
counter.decr(8)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 1)
|
||||||
|
|
||||||
|
def test_can_decrement_counter_below_zero(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.decr(1)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), -1)
|
||||||
|
|
||||||
|
def test_can_not_decrement_counter_below_minimum(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME, minimum=0)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
counter.decr(1)
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 0)
|
||||||
|
|
||||||
|
def test_can_not_reset_counter_below_minimum(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME, minimum=0)
|
||||||
|
# when/then
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
counter.reset(-1)
|
||||||
|
|
||||||
|
def test_can_not_init_without_name(self):
|
||||||
|
# when/then
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
ItemCounter(name="")
|
||||||
|
|
||||||
|
def test_can_ignore_invalid_values_when_incrementing(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(0)
|
||||||
|
# when
|
||||||
|
with patch(MODULE_PATH + ".cache.incr") as m:
|
||||||
|
m.side_effect = ValueError
|
||||||
|
counter.incr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 0)
|
||||||
|
|
||||||
|
def test_can_ignore_invalid_values_when_decrementing(self):
|
||||||
|
# given
|
||||||
|
counter = ItemCounter(COUNTER_NAME)
|
||||||
|
counter.reset(1)
|
||||||
|
# when
|
||||||
|
with patch(MODULE_PATH + ".cache.decr") as m:
|
||||||
|
m.side_effect = ValueError
|
||||||
|
counter.decr()
|
||||||
|
# then
|
||||||
|
self.assertEqual(counter.value(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetRedisClient(TestCase):
|
||||||
|
def test_should_return_mock_if_redis_not_available_1(self):
|
||||||
|
# when
|
||||||
|
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
|
||||||
|
mock_get_master_client.return_value.ping.side_effect = RedisError
|
||||||
|
result = get_redis_client_or_stub()
|
||||||
|
# then
|
||||||
|
self.assertIsInstance(result, _RedisStub)
|
||||||
|
|
||||||
|
def test_should_return_mock_if_redis_not_available_2(self):
|
||||||
|
# when
|
||||||
|
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
|
||||||
|
mock_get_master_client.return_value.ping.return_value = False
|
||||||
|
result = get_redis_client_or_stub()
|
||||||
|
# then
|
||||||
|
self.assertIsInstance(result, _RedisStub)
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from allianceauth.authentication.task_statistics.helpers import ItemCounter
|
|
||||||
|
|
||||||
COUNTER_NAME = "test-counter"
|
|
||||||
|
|
||||||
|
|
||||||
class TestItemCounter(TestCase):
|
|
||||||
def test_can_create_counter(self):
|
|
||||||
# when
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
# then
|
|
||||||
self.assertIsInstance(counter, ItemCounter)
|
|
||||||
|
|
||||||
def test_can_reset_counter_to_default(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
# when
|
|
||||||
counter.reset()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 0)
|
|
||||||
|
|
||||||
def test_can_reset_counter_to_custom_value(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
# when
|
|
||||||
counter.reset(42)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 42)
|
|
||||||
|
|
||||||
def test_can_increment_counter_by_default(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.incr()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 1)
|
|
||||||
|
|
||||||
def test_can_increment_counter_by_custom_value(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.incr(8)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 8)
|
|
||||||
|
|
||||||
def test_can_decrement_counter_by_default(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(9)
|
|
||||||
# when
|
|
||||||
counter.decr()
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 8)
|
|
||||||
|
|
||||||
def test_can_decrement_counter_by_custom_value(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(9)
|
|
||||||
# when
|
|
||||||
counter.decr(8)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), 1)
|
|
||||||
|
|
||||||
def test_can_decrement_counter_below_zero(self):
|
|
||||||
# given
|
|
||||||
counter = ItemCounter(COUNTER_NAME)
|
|
||||||
counter.reset(0)
|
|
||||||
# when
|
|
||||||
counter.decr(1)
|
|
||||||
# then
|
|
||||||
self.assertEqual(counter.value(), -1)
|
|
||||||
@@ -32,10 +32,13 @@ INSTALLED_APPS += [
|
|||||||
# To change the logging level for extensions, uncomment the following line.
|
# To change the logging level for extensions, uncomment the following line.
|
||||||
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
|
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
|
||||||
|
|
||||||
# By default apps are prevented from having public views for security reasons.
|
# By default, apps are prevented from having public views for security reasons.
|
||||||
# If you want to allow specific apps to have public views
|
# To allow specific apps to have public views, add them to APPS_WITH_PUBLIC_VIEWS
|
||||||
# you can put there names here (same name as in INSTALLED_APPS):
|
# » The format is the same as in INSTALLED_APPS
|
||||||
APPS_WITH_PUBLIC_VIEWS = []
|
# » The app developer must also explicitly allow public views for their app
|
||||||
|
APPS_WITH_PUBLIC_VIEWS = [
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
|||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav > li.top-user-menu.with-main-character a {
|
.navbar-nav > li.top-user-menu a {
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,33 +21,37 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="{% if NIGHT_MODE %}template-dark-mode{% else %}template-light-mode{% endif %}">
|
<body class="{% if NIGHT_MODE %}template-dark-mode{% else %}template-light-mode{% endif %}">
|
||||||
{% if user.is_authenticated %}
|
<div id="wrapper" class="container">
|
||||||
<div id="wrapper" class="container">
|
<!-- Navigation -->
|
||||||
<!-- Navigation -->
|
{% include 'allianceauth/top-menu.html' %}
|
||||||
{% include 'allianceauth/top-menu.html' %}
|
|
||||||
<div class="row" id="site-body-wrapper">
|
<div class="clearfix{% if user.is_authenticated %} row{% endif %}" id="site-body-wrapper">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
{% include 'allianceauth/side-menu.html' %}
|
{% include 'allianceauth/side-menu.html' %}
|
||||||
<div class="col-sm-10">
|
{% endif %}
|
||||||
{% include 'allianceauth/messages.html' %}
|
|
||||||
{% block content %}
|
<div class="{% if user.is_authenticated %}col-sm-10{% else %}col-sm-12{% endif %}">
|
||||||
{% endblock content %}
|
{% include 'allianceauth/messages.html' %}
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
{% block content %}
|
||||||
|
{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
|
|
||||||
{% include 'bundles/bootstrap-js.html' %}
|
{% include 'bundles/bootstrap-js.html' %}
|
||||||
{% include 'bundles/jquery-visibility-js.html' %}
|
{% include 'bundles/jquery-visibility-js.html' %}
|
||||||
|
|
||||||
<script>
|
{% if user.is_authenticated %}
|
||||||
let notificationUPdateSettings = {
|
<script>
|
||||||
notificationsListViewUrl: "{% url 'notifications:list' %}",
|
let notificationUPdateSettings = {
|
||||||
notificationsRefreshTime: "{% notifications_refresh_time %}",
|
notificationsListViewUrl: "{% url 'notifications:list' %}",
|
||||||
userNotificationsCountViewUrl: "{% url 'notifications:user_notifications_count' request.user.pk %}"
|
notificationsRefreshTime: "{% notifications_refresh_time %}",
|
||||||
};
|
userNotificationsCountViewUrl: "{% url 'notifications:user_notifications_count' request.user.pk %}"
|
||||||
</script>
|
};
|
||||||
{% include 'bundles/refresh-notifications-js.html' %}
|
</script>
|
||||||
|
{% include 'bundles/refresh-notifications-js.html' %}
|
||||||
|
{% endif %}
|
||||||
{% include 'bundles/evetime-js.html' %}
|
{% include 'bundles/evetime-js.html' %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
</span>
|
</span>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% translate "User Menu" %}
|
<img class="img-rounded ra-avatar" src="{{ 1|character_portrait_url:32 }}" alt="{{ main.character_name }}">
|
||||||
|
<span class="hidden-sm hidden-md hidden-lg">
|
||||||
|
{% translate "User Menu" %}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -22,9 +22,12 @@
|
|||||||
<li class="nav-item-eve-time">
|
<li class="nav-item-eve-time">
|
||||||
<div class="eve-time-wrapper">{% translate "Eve Time" %}: <span class="eve-time-clock"></span></div>
|
<div class="eve-time-wrapper">{% translate "Eve Time" %}: <span class="eve-time-clock"></span></div>
|
||||||
</li>
|
</li>
|
||||||
<li class="{% navactive request 'notifications:' %}" id="menu_item_notifications">
|
|
||||||
{% include 'allianceauth/notifications_menu_item.html' %}
|
{% if user.is_authenticated %}
|
||||||
</li>
|
<li class="{% navactive request 'notifications:' %}" id="menu_item_notifications">
|
||||||
|
{% include 'allianceauth/notifications_menu_item.html' %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% include 'allianceauth/top-menu-user-dropdown.html' %}
|
{% include 'allianceauth/top-menu-user-dropdown.html' %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
PROTOCOL=https://
|
PROTOCOL=https://
|
||||||
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
||||||
DOMAIN=%DOMAIN%
|
DOMAIN=%DOMAIN%
|
||||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.6.0
|
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.6.1
|
||||||
|
|
||||||
# Nginx Proxy Manager
|
# Nginx Proxy Manager
|
||||||
PROXY_HTTP_PORT=80
|
PROXY_HTTP_PORT=80
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM python:3.9-slim
|
FROM python:3.9-slim
|
||||||
ARG AUTH_VERSION=v3.6.0
|
ARG AUTH_VERSION=v3.6.1
|
||||||
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
||||||
ENV VIRTUAL_ENV=/opt/venv
|
ENV VIRTUAL_ENV=/opt/venv
|
||||||
ENV AUTH_USER=allianceauth
|
ENV AUTH_USER=allianceauth
|
||||||
|
|||||||
Reference in New Issue
Block a user