Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12383d79c8 | ||
|
|
56e2875650 | ||
|
|
d0118e6c0b | ||
|
|
7075ccdf7a | ||
|
|
b2d540c010 | ||
|
|
7cb7e2c77b | ||
|
|
5d6a4ab1a9 | ||
|
|
1122d617bd | ||
|
|
ef33501e45 | ||
|
|
08fd86db8f | ||
|
|
c4193c15fc | ||
|
|
903074080e | ||
|
|
3046a26a02 | ||
|
|
951c4135c2 | ||
|
|
b256a0c5e1 | ||
|
|
212b9b0f60 | ||
|
|
fc29d7e80d | ||
|
|
ec536c66a0 | ||
|
|
749ece45e2 | ||
|
|
b04c8873d0 | ||
|
|
9a77175bf3 | ||
|
|
5d4c7b9030 | ||
|
|
5f80259d57 | ||
|
|
dcd6bd1b36 | ||
|
|
1cae20fe5f |
@@ -1,6 +0,0 @@
|
||||
[settings]
|
||||
profile=django
|
||||
sections=FUTURE,STDLIB,THIRDPARTY,DJANGO,ESI,FIRSTPARTY,LOCALFOLDER
|
||||
known_esi=esi
|
||||
known_django=django
|
||||
skip_gitignore=true
|
||||
@@ -7,12 +7,33 @@ repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-json
|
||||
- id: check-xml
|
||||
# Identify invalid files
|
||||
- id: check-ast
|
||||
- id: check-yaml
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: check-xml
|
||||
|
||||
# git checks
|
||||
- id: check-merge-conflict
|
||||
- id: check-added-large-files
|
||||
args: [ --maxkb=1000 ]
|
||||
- id: detect-private-key
|
||||
- id: check-case-conflict
|
||||
|
||||
# Python checks
|
||||
# - id: check-docstring-first
|
||||
- id: debug-statements
|
||||
# - id: requirements-txt-fixer
|
||||
- id: fix-encoding-pragma
|
||||
args: [ --remove ]
|
||||
- id: fix-byte-order-marker
|
||||
|
||||
# General quality checks
|
||||
- id: mixed-line-ending
|
||||
args: [ --fix=lf ]
|
||||
- id: trailing-whitespace
|
||||
args: [ --markdown-linebreak-ext=md ]
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
@@ -21,6 +42,7 @@ repos:
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- id: check-executables-have-shebangs
|
||||
- id: end-of-file-fixer
|
||||
exclude: |
|
||||
(?x)(
|
||||
@@ -30,13 +52,9 @@ repos:
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- id: mixed-line-ending
|
||||
args: [ '--fix=lf' ]
|
||||
- id: fix-encoding-pragma
|
||||
args: [ '--remove' ]
|
||||
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 2.7.1
|
||||
rev: 2.7.2
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
exclude: |
|
||||
@@ -48,14 +66,14 @@ repos:
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.14.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [ --target-version=4.0 ]
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
rev: v3.10.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [ --py38-plus ]
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.2.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: [ --include-version-classifiers ]
|
||||
|
||||
0
.tx/config_20230406134150.bak
Executable file → Normal file
@@ -1,7 +0,0 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include MANIFEST.in
|
||||
graft allianceauth
|
||||
|
||||
global-exclude __pycache__
|
||||
global-exclude *.py[co]
|
||||
@@ -1,7 +1,11 @@
|
||||
"""An auth system for EVE Online to help in-game organizations
|
||||
manage online service access.
|
||||
"""
|
||||
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '3.5.1'
|
||||
__version__ = '3.6.0'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
from django.conf.urls import include
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from functools import wraps
|
||||
from django.shortcuts import redirect
|
||||
from typing import Callable, Iterable, Optional
|
||||
|
||||
from django.urls import include
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
|
||||
def user_has_main_character(user):
|
||||
return bool(user.profile.main_character)
|
||||
|
||||
|
||||
def decorate_url_patterns(urls, decorator):
|
||||
def decorate_url_patterns(
|
||||
urls, decorator: Callable, excluded_views: Optional[Iterable] = None
|
||||
):
|
||||
"""Decorate views given in url patterns except when they are explicitly excluded.
|
||||
|
||||
Args:
|
||||
- urls: Django URL patterns
|
||||
- decorator: Decorator to be added to each view
|
||||
- exclude_views: Optional iterable of view names to be excluded
|
||||
"""
|
||||
url_list, app_name, namespace = include(urls)
|
||||
|
||||
def process_patterns(url_patterns):
|
||||
@@ -22,6 +32,8 @@ def decorate_url_patterns(urls, decorator):
|
||||
process_patterns(pattern.url_patterns)
|
||||
else:
|
||||
# this is a pattern
|
||||
if excluded_views and pattern.lookup_str in excluded_views:
|
||||
return
|
||||
pattern.callback = decorator(pattern.callback)
|
||||
|
||||
process_patterns(url_list)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
from django.conf.urls import include
|
||||
|
||||
from allianceauth.authentication import views
|
||||
from django.urls import re_path
|
||||
from django.urls import path
|
||||
from django.urls import include, re_path, path
|
||||
|
||||
urlpatterns = [
|
||||
path('activate/complete/', views.activation_complete, name='registration_activation_complete'),
|
||||
|
||||
0
allianceauth/authentication/managers.py
Executable file → Normal file
0
allianceauth/authentication/models.py
Executable file → Normal file
@@ -1,35 +1,44 @@
|
||||
from collections import namedtuple
|
||||
"""Counters for Task Statistics."""
|
||||
|
||||
import datetime as dt
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
from .event_series import EventSeries
|
||||
from .helpers import ItemCounter
|
||||
|
||||
|
||||
"""Global series for counting task events."""
|
||||
# Global series for counting task events.
|
||||
succeeded_tasks = EventSeries("SUCCEEDED_TASKS")
|
||||
retried_tasks = EventSeries("RETRIED_TASKS")
|
||||
failed_tasks = EventSeries("FAILED_TASKS")
|
||||
running_tasks = ItemCounter("running_tasks")
|
||||
|
||||
|
||||
_TaskCounts = namedtuple(
|
||||
"_TaskCounts", ["succeeded", "retried", "failed", "total", "earliest_task", "hours"]
|
||||
)
|
||||
class _TaskCounts(NamedTuple):
|
||||
succeeded: int
|
||||
retried: int
|
||||
failed: int
|
||||
total: int
|
||||
earliest_task: Optional[dt.datetime]
|
||||
hours: int
|
||||
running: int
|
||||
|
||||
|
||||
def dashboard_results(hours: int) -> _TaskCounts:
|
||||
"""Counts of all task events within the given timeframe."""
|
||||
"""Counts of all task events within the given time frame."""
|
||||
|
||||
def earliest_if_exists(events: EventSeries, earliest: dt.datetime) -> list:
|
||||
my_earliest = events.first_event(earliest=earliest)
|
||||
return [my_earliest] if my_earliest else []
|
||||
|
||||
earliest = dt.datetime.utcnow() - dt.timedelta(hours=hours)
|
||||
earliest_events = list()
|
||||
earliest_events = []
|
||||
succeeded_count = succeeded_tasks.count(earliest=earliest)
|
||||
earliest_events += earliest_if_exists(succeeded_tasks, earliest)
|
||||
retried_count = retried_tasks.count(earliest=earliest)
|
||||
earliest_events += earliest_if_exists(retried_tasks, earliest)
|
||||
failed_count = failed_tasks.count(earliest=earliest)
|
||||
earliest_events += earliest_if_exists(failed_tasks, earliest)
|
||||
running_count = running_tasks.value()
|
||||
return _TaskCounts(
|
||||
succeeded=succeeded_count,
|
||||
retried=retried_count,
|
||||
@@ -37,4 +46,5 @@ def dashboard_results(hours: int) -> _TaskCounts:
|
||||
total=succeeded_count + retried_count + failed_count,
|
||||
earliest_task=min(earliest_events) if earliest_events else None,
|
||||
hours=hours,
|
||||
running=running_count,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Event series for Task Statistics."""
|
||||
|
||||
import datetime as dt
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
@@ -73,8 +75,8 @@ class EventSeries:
|
||||
"""
|
||||
if not event_time:
|
||||
event_time = dt.datetime.utcnow()
|
||||
id = self._redis.incr(self._key_counter)
|
||||
self._redis.zadd(self._key_sorted_set, {id: event_time.timestamp()})
|
||||
my_id = self._redis.incr(self._key_counter)
|
||||
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})
|
||||
|
||||
def all(self) -> List[dt.datetime]:
|
||||
"""List of all known events."""
|
||||
@@ -101,9 +103,9 @@ class EventSeries:
|
||||
- earliest: Date of first events to count(inclusive), or -infinite if not specified
|
||||
- latest: Date of last events to count(inclusive), or +infinite if not specified
|
||||
"""
|
||||
min = "-inf" if not earliest else earliest.timestamp()
|
||||
max = "+inf" if not latest else latest.timestamp()
|
||||
return self._redis.zcount(self._key_sorted_set, min=min, max=max)
|
||||
minimum = "-inf" if not earliest else earliest.timestamp()
|
||||
maximum = "+inf" if not latest else latest.timestamp()
|
||||
return self._redis.zcount(self._key_sorted_set, min=minimum, max=maximum)
|
||||
|
||||
def first_event(self, earliest: dt.datetime = None) -> Optional[dt.datetime]:
|
||||
"""Date/Time of first event. Returns `None` if series has no events.
|
||||
@@ -111,10 +113,10 @@ class EventSeries:
|
||||
Args:
|
||||
- earliest: Date of first events to count(inclusive), or any if not specified
|
||||
"""
|
||||
min = "-inf" if not earliest else earliest.timestamp()
|
||||
minimum = "-inf" if not earliest else earliest.timestamp()
|
||||
event = self._redis.zrangebyscore(
|
||||
self._key_sorted_set,
|
||||
min,
|
||||
minimum,
|
||||
"+inf",
|
||||
withscores=True,
|
||||
start=0,
|
||||
|
||||
44
allianceauth/authentication/task_statistics/helpers.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Helpers for Task Statistics."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class ItemCounter:
|
||||
"""A process safe item counter."""
|
||||
|
||||
CACHE_KEY_BASE = "allianceauth-item-counter"
|
||||
DEFAULT_CACHE_TIMEOUT = 24 * 3600
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
if not name:
|
||||
raise ValueError("Must define a name")
|
||||
|
||||
self._name = str(name)
|
||||
|
||||
@property
|
||||
def _cache_key(self) -> str:
|
||||
return f"{self.CACHE_KEY_BASE}-{self._name}"
|
||||
|
||||
def reset(self, init_value: int = 0):
|
||||
"""Reset counter to initial value."""
|
||||
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
|
||||
|
||||
def incr(self, delta: int = 1):
|
||||
"""Increment counter by delta."""
|
||||
try:
|
||||
cache.incr(self._cache_key, delta)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def decr(self, delta: int = 1):
|
||||
"""Decrement counter by delta."""
|
||||
try:
|
||||
cache.decr(self._cache_key, delta)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def value(self) -> Optional[int]:
|
||||
"""Return current value or None if not yet initialized."""
|
||||
return cache.get(self._cache_key)
|
||||
@@ -1,14 +1,15 @@
|
||||
"""Signals for Task Statistics."""
|
||||
|
||||
from celery.signals import (
|
||||
task_failure,
|
||||
task_internal_error,
|
||||
task_retry,
|
||||
task_success,
|
||||
worker_ready
|
||||
task_failure, task_internal_error, task_postrun, task_prerun, task_retry,
|
||||
task_success, worker_ready,
|
||||
)
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from .counters import failed_tasks, retried_tasks, succeeded_tasks
|
||||
from .counters import (
|
||||
failed_tasks, retried_tasks, running_tasks, succeeded_tasks,
|
||||
)
|
||||
|
||||
|
||||
def reset_counters():
|
||||
@@ -16,9 +17,11 @@ def reset_counters():
|
||||
succeeded_tasks.clear()
|
||||
failed_tasks.clear()
|
||||
retried_tasks.clear()
|
||||
running_tasks.reset()
|
||||
|
||||
|
||||
def is_enabled() -> bool:
|
||||
"""Return True if task statistics are enabled, else return False."""
|
||||
return not bool(
|
||||
getattr(settings, "ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED", False)
|
||||
)
|
||||
@@ -52,3 +55,15 @@ def record_task_failed(*args, **kwargs):
|
||||
def record_task_internal_error(*args, **kwargs):
|
||||
if is_enabled():
|
||||
failed_tasks.add()
|
||||
|
||||
|
||||
@task_prerun.connect
|
||||
def record_task_prerun(*args, **kwargs):
|
||||
if is_enabled():
|
||||
running_tasks.incr()
|
||||
|
||||
|
||||
@task_postrun.connect
|
||||
def record_task_postrun(*args, **kwargs):
|
||||
if is_enabled():
|
||||
running_tasks.decr()
|
||||
|
||||
@@ -8,25 +8,31 @@ from allianceauth.authentication.task_statistics.counters import (
|
||||
succeeded_tasks,
|
||||
retried_tasks,
|
||||
failed_tasks,
|
||||
running_tasks,
|
||||
)
|
||||
|
||||
|
||||
class TestDashboardResults(TestCase):
|
||||
def test_should_return_counts_for_given_timeframe_only(self):
|
||||
def test_should_return_counts_for_given_time_frame_only(self):
|
||||
# given
|
||||
earliest_task = now() - dt.timedelta(minutes=15)
|
||||
|
||||
succeeded_tasks.clear()
|
||||
succeeded_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
|
||||
succeeded_tasks.add(earliest_task)
|
||||
succeeded_tasks.add()
|
||||
succeeded_tasks.add()
|
||||
|
||||
retried_tasks.clear()
|
||||
retried_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
|
||||
retried_tasks.add(now() - dt.timedelta(seconds=30))
|
||||
retried_tasks.add()
|
||||
|
||||
failed_tasks.clear()
|
||||
failed_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
|
||||
failed_tasks.add()
|
||||
|
||||
running_tasks.reset(8)
|
||||
# when
|
||||
results = dashboard_results(hours=1)
|
||||
# then
|
||||
@@ -35,12 +41,14 @@ class TestDashboardResults(TestCase):
|
||||
self.assertEqual(results.failed, 1)
|
||||
self.assertEqual(results.total, 6)
|
||||
self.assertEqual(results.earliest_task, earliest_task)
|
||||
self.assertEqual(results.running, 8)
|
||||
|
||||
def test_should_work_with_no_data(self):
|
||||
# given
|
||||
succeeded_tasks.clear()
|
||||
retried_tasks.clear()
|
||||
failed_tasks.clear()
|
||||
running_tasks.reset()
|
||||
# when
|
||||
results = dashboard_results(hours=1)
|
||||
# then
|
||||
@@ -49,3 +57,4 @@ class TestDashboardResults(TestCase):
|
||||
self.assertEqual(results.failed, 0)
|
||||
self.assertEqual(results.total, 0)
|
||||
self.assertIsNone(results.earliest_task)
|
||||
self.assertEqual(results.running, 0)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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)
|
||||
@@ -17,16 +17,17 @@ from allianceauth.eveonline.tasks import update_character
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_ALWAYS_EAGER=True,ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
|
||||
CELERY_ALWAYS_EAGER=True, ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
|
||||
)
|
||||
class TestTaskSignals(TestCase):
|
||||
fixtures = ["disable_analytics"]
|
||||
|
||||
def test_should_record_successful_task(self):
|
||||
# given
|
||||
def setUp(self) -> None:
|
||||
succeeded_tasks.clear()
|
||||
retried_tasks.clear()
|
||||
failed_tasks.clear()
|
||||
|
||||
def test_should_record_successful_task(self):
|
||||
# when
|
||||
with patch(
|
||||
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
|
||||
@@ -39,10 +40,6 @@ class TestTaskSignals(TestCase):
|
||||
self.assertEqual(failed_tasks.count(), 0)
|
||||
|
||||
def test_should_record_retried_task(self):
|
||||
# given
|
||||
succeeded_tasks.clear()
|
||||
retried_tasks.clear()
|
||||
failed_tasks.clear()
|
||||
# when
|
||||
with patch(
|
||||
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
|
||||
@@ -55,10 +52,6 @@ class TestTaskSignals(TestCase):
|
||||
self.assertEqual(retried_tasks.count(), 1)
|
||||
|
||||
def test_should_record_failed_task(self):
|
||||
# given
|
||||
succeeded_tasks.clear()
|
||||
retried_tasks.clear()
|
||||
failed_tasks.clear()
|
||||
# when
|
||||
with patch(
|
||||
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{% block page_title %}{% translate "Login" %}{% endblock %}
|
||||
|
||||
{% block middle_box_content %}
|
||||
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
|
||||
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next | urlencode}}{%endif%}">
|
||||
<img class="img-responsive center-block" src="{% static 'allianceauth/authentication/img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" alt="{% translate 'Login with Eve SSO' %}">
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,16 +4,16 @@ from urllib import parse
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.http.response import HttpResponse
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse, URLPattern
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..decorators import main_character_required
|
||||
from ..models import CharacterOwnership
|
||||
|
||||
from ..decorators import decorate_url_patterns, main_character_required
|
||||
from ..models import CharacterOwnership
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
@@ -66,3 +66,33 @@ class DecoratorTestCase(TestCase):
|
||||
setattr(self.request, 'user', self.main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestDecorateUrlPatterns(TestCase):
|
||||
def test_should_add_decorator_by_default(self):
|
||||
# given
|
||||
decorator = mock.MagicMock(name="decorator")
|
||||
view = mock.MagicMock(name="view")
|
||||
path = mock.MagicMock(spec=URLPattern, name="path")
|
||||
path.callback = view
|
||||
path.lookup_str = "my_lookup_str"
|
||||
urls = [path]
|
||||
urlconf_module = urls
|
||||
# when
|
||||
decorate_url_patterns(urlconf_module, decorator)
|
||||
# then
|
||||
self.assertEqual(path.callback, decorator(view))
|
||||
|
||||
def test_should_not_add_decorator_when_excluded(self):
|
||||
# given
|
||||
decorator = mock.MagicMock(name="decorator")
|
||||
view = mock.MagicMock(name="view")
|
||||
path = mock.MagicMock(spec=URLPattern, name="path")
|
||||
path.callback = view
|
||||
path.lookup_str = "my_lookup_str"
|
||||
urls = [path]
|
||||
urlconf_module = urls
|
||||
# when
|
||||
decorate_url_patterns(urlconf_module, decorator, excluded_views=["my_lookup_str"])
|
||||
# then
|
||||
self.assertEqual(path.callback, view)
|
||||
|
||||
@@ -14,6 +14,7 @@ def sync_user_groups(modeladmin, request, queryset):
|
||||
agc.update_all_states_group_membership()
|
||||
|
||||
|
||||
@admin.register(AutogroupsConfig)
|
||||
class AutogroupsConfigAdmin(admin.ModelAdmin):
|
||||
formfield_overrides = {
|
||||
models.CharField: {'strip': False}
|
||||
@@ -36,6 +37,5 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
|
||||
return actions
|
||||
|
||||
|
||||
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
|
||||
admin.site.register(ManagedCorpGroup)
|
||||
admin.site.register(ManagedAllianceGroup)
|
||||
|
||||
0
allianceauth/eveonline/views.py
Executable file → Normal file
0
allianceauth/groupmanagement/views.py
Executable file → Normal file
2
allianceauth/hrapplications/admin.py
Executable file → Normal file
@@ -10,6 +10,7 @@ class ChoiceInline(admin.TabularInline):
|
||||
verbose_name_plural = 'Choices (optional)'
|
||||
verbose_name= 'Choice'
|
||||
|
||||
@admin.register(ApplicationQuestion)
|
||||
class QuestionAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {'fields': ['title', 'help_text', 'multi_select']}),
|
||||
@@ -18,6 +19,5 @@ class QuestionAdmin(admin.ModelAdmin):
|
||||
|
||||
admin.site.register(Application)
|
||||
admin.site.register(ApplicationComment)
|
||||
admin.site.register(ApplicationQuestion, QuestionAdmin)
|
||||
admin.site.register(ApplicationForm)
|
||||
admin.site.register(ApplicationResponse)
|
||||
|
||||
0
allianceauth/hrapplications/forms.py
Executable file → Normal file
0
allianceauth/hrapplications/models.py
Executable file → Normal file
0
allianceauth/hrapplications/views.py
Executable file → Normal file
@@ -1,3 +1 @@
|
||||
from .core import notify # noqa: F401
|
||||
|
||||
default_app_config = 'allianceauth.notifications.apps.NotificationsConfig'
|
||||
|
||||
@@ -15,18 +15,22 @@ class NotificationAdmin(admin.ModelAdmin):
|
||||
ordering = ("-timestamp", )
|
||||
search_fields = ["user__username", "user__profile__main_character__character_name"]
|
||||
|
||||
@admin.display(
|
||||
ordering="user__profile__main_character__character_name"
|
||||
)
|
||||
def _main(self, obj):
|
||||
try:
|
||||
return obj.user.profile.main_character
|
||||
except AttributeError:
|
||||
return obj.user
|
||||
|
||||
_main.admin_order_field = "user__profile__main_character__character_name"
|
||||
|
||||
@admin.display(
|
||||
ordering="user__profile__state__name"
|
||||
)
|
||||
def _state(self, obj):
|
||||
return obj.user.profile.state
|
||||
|
||||
_state.admin_order_field = "user__profile__state__name"
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
@@ -41,23 +41,23 @@ CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'esi_cleanup_callbackredirect': {
|
||||
'task': 'esi.tasks.cleanup_callbackredirect',
|
||||
'schedule': crontab(minute=0, hour='*/4'),
|
||||
'schedule': crontab(minute='0', hour='*/4'),
|
||||
},
|
||||
'esi_cleanup_token': {
|
||||
'task': 'esi.tasks.cleanup_token',
|
||||
'schedule': crontab(minute=0, hour=0),
|
||||
'schedule': crontab(minute='0', hour='0'),
|
||||
},
|
||||
'run_model_update': {
|
||||
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
||||
'schedule': crontab(minute=0, hour="*/6"),
|
||||
'schedule': crontab(minute='0', hour="*/6"),
|
||||
},
|
||||
'check_all_character_ownership': {
|
||||
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
||||
'schedule': crontab(minute=0, hour='*/4'),
|
||||
'schedule': crontab(minute='0', hour='*/4'),
|
||||
},
|
||||
'analytics_daily_stats': {
|
||||
'task': 'allianceauth.analytics.tasks.analytics_daily_stats',
|
||||
'schedule': crontab(minute=0, hour=2),
|
||||
'schedule': crontab(minute='0', hour='2'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@ INSTALLED_APPS += [
|
||||
# To change the logging level for extensions, uncomment the following line.
|
||||
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
|
||||
|
||||
# By default apps are prevented from having public views for security reasons.
|
||||
# If you want to allow specific apps to have public views
|
||||
# you can put there names here (same name as in INSTALLED_APPS):
|
||||
APPS_WITH_PUBLIC_VIEWS = []
|
||||
|
||||
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
||||
DATABASES['default'] = {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from django.conf.urls import include
|
||||
from allianceauth import urls
|
||||
from django.urls import re_path
|
||||
from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'', include(urls)),
|
||||
path('', include(urls)),
|
||||
]
|
||||
|
||||
handler500 = 'allianceauth.views.Generic500Redirect'
|
||||
|
||||
@@ -66,6 +66,8 @@ class NameFormatConfigAdmin(admin.ModelAdmin):
|
||||
form = NameFormatConfigForm
|
||||
list_display = ('service_name', 'get_state_display_string')
|
||||
|
||||
@admin.display(
|
||||
description='States'
|
||||
)
|
||||
def get_state_display_string(self, obj):
|
||||
return ', '.join([state.name for state in obj.states.all()])
|
||||
get_state_display_string.short_description = 'States'
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import re_path
|
||||
from string import Formatter
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import include, re_path
|
||||
from django.utils.functional import cached_property
|
||||
from django.conf import settings
|
||||
from string import Formatter
|
||||
|
||||
from allianceauth.hooks import get_hooks
|
||||
|
||||
from .models import NameFormatConfig
|
||||
|
||||
|
||||
def get_extension_logger(name):
|
||||
"""
|
||||
Takes the name of a plugin/extension and generates a child logger of the extensions logger
|
||||
@@ -156,8 +158,32 @@ class MenuItemHook:
|
||||
|
||||
|
||||
class UrlHook:
|
||||
def __init__(self, urls, namespace, base_url):
|
||||
"""A hook for registering the URLs of a Django app.
|
||||
|
||||
Args:
|
||||
- urls: The urls module to include
|
||||
- namespace: The URL namespace to apply. This is usually just the app name.
|
||||
- base_url: The URL prefix to match against in regex form.
|
||||
Example ``r'^app_name/'``.
|
||||
This prefix will be applied in front of all URL patterns included.
|
||||
It is possible to use the same prefix as existing apps (or no prefix at all),
|
||||
but standard URL resolution ordering applies
|
||||
(hook URLs are the last ones registered).
|
||||
- excluded_views: Optional list of views to be excluded
|
||||
from auto-decorating them with the
|
||||
default ``main_character_required`` decorator, e.g. to make them public.
|
||||
Views must be specified by their qualified name,
|
||||
e.g. ``["example.views.my_public_view"]``
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
urls,
|
||||
namespace: str,
|
||||
base_url: str,
|
||||
excluded_views : Optional[Iterable[str]] = None
|
||||
):
|
||||
self.include_pattern = re_path(base_url, include(urls, namespace=namespace))
|
||||
self.excluded_views = set(excluded_views or [])
|
||||
|
||||
|
||||
class NameFormatter:
|
||||
|
||||
@@ -588,16 +588,17 @@ class DiscordClient:
|
||||
return None # User is no longer a member
|
||||
guild_roles = RolesSet(self.guild_roles(guild_id=guild_id))
|
||||
logger.debug('Current guild roles: %s', guild_roles.ids())
|
||||
_roles = set(member_info.roles)
|
||||
if not guild_roles.has_roles(member_info.roles):
|
||||
guild_roles = RolesSet(
|
||||
self.guild_roles(guild_id=guild_id, use_cache=False)
|
||||
)
|
||||
if not guild_roles.has_roles(member_info.roles):
|
||||
role_ids = set(member_info.roles).difference(guild_roles.ids())
|
||||
raise RuntimeError(
|
||||
f'Discord user {user_id} has unknown roles: {role_ids}'
|
||||
)
|
||||
return guild_roles.subset(member_info.roles)
|
||||
logger.warning(f'Discord user {user_id} has unknown roles: {role_ids}')
|
||||
for _r in role_ids:
|
||||
_roles.remove(_r)
|
||||
return guild_roles.subset(_roles)
|
||||
|
||||
@classmethod
|
||||
def _is_member_unknown_error(cls, r: requests.Response) -> bool:
|
||||
|
||||
@@ -899,8 +899,8 @@ class TestGuildMemberRoles(NoSocketsTestCase):
|
||||
mock_guild_roles.return_value = {role_a, role_b}
|
||||
client = DiscordClientStub(TEST_BOT_TOKEN, mock_redis)
|
||||
# when/then
|
||||
with self.assertRaises(RuntimeError):
|
||||
client.guild_member_roles(TEST_GUILD_ID, TEST_USER_ID)
|
||||
roles = client.guild_member_roles(TEST_GUILD_ID, TEST_USER_ID)
|
||||
self.assertEqual(roles, RolesSet([role_a]))
|
||||
|
||||
# TODO: Re-enable after adding Discord general error handling
|
||||
# def test_should_raise_exception_if_member_info_is_invalid(
|
||||
|
||||
0
allianceauth/services/modules/discord/tests/piloting_tasks.py
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
app_name = 'example'
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ from django.contrib import admin
|
||||
from .models import Ips4User
|
||||
|
||||
|
||||
@admin.register(Ips4User)
|
||||
class Ips4UserAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'username', 'id')
|
||||
search_fields = ('user__username', 'username', 'id')
|
||||
|
||||
admin.site.register(Ips4User, Ips4UserAdmin)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
0
allianceauth/services/modules/openfire/manager.py
Executable file → Normal file
0
allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
0
allianceauth/services/modules/phpbb3/manager.py
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
@@ -36,10 +36,12 @@ class AuthTSgroupAdmin(admin.ModelAdmin):
|
||||
kwargs['queryset'] = TSgroup.objects.exclude(ts_group_name__in=ReservedGroupName.objects.values_list('name', flat=True))
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
@admin.display(
|
||||
description='ts groups'
|
||||
)
|
||||
def _ts_group(self, obj):
|
||||
return [x for x in obj.ts_group.all().order_by('ts_group_id')]
|
||||
|
||||
_ts_group.short_description = 'ts groups'
|
||||
# _ts_group.admin_order_field = 'profile__state'
|
||||
|
||||
|
||||
|
||||
0
allianceauth/services/modules/teamspeak3/manager.py
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
0
allianceauth/services/modules/teamspeak3/util/__init__.py
Executable file → Normal file
0
allianceauth/services/modules/teamspeak3/util/ts3.py
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import path
|
||||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
0
allianceauth/services/templates/services/services.html
Executable file → Normal file
30
allianceauth/services/tests/test_hooks.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
from allianceauth.groupmanagement import urls
|
||||
|
||||
|
||||
class TestUrlHook(TestCase):
|
||||
def test_can_create_simple_hook(self):
|
||||
# when
|
||||
obj = UrlHook(urls, "groupmanagement", r"^groupmanagement/")
|
||||
# then
|
||||
self.assertEqual(obj.include_pattern.app_name, "groupmanagement")
|
||||
self.assertFalse(obj.excluded_views)
|
||||
|
||||
def test_can_create_hook_with_excluded_views(self):
|
||||
# when
|
||||
obj = UrlHook(
|
||||
urls,
|
||||
"groupmanagement",
|
||||
r"^groupmanagement/",
|
||||
["groupmanagement.views.group_management"],
|
||||
)
|
||||
# then
|
||||
self.assertEqual(obj.include_pattern.app_name, "groupmanagement")
|
||||
self.assertIn("groupmanagement.views.group_management", obj.excluded_views)
|
||||
|
||||
def test_should_raise_error_when_called_with_invalid_excluded_views(self):
|
||||
# when/then
|
||||
with self.assertRaises(TypeError):
|
||||
UrlHook(urls, "groupmanagement", r"^groupmanagement/", 99)
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.conf.urls import include
|
||||
from django.urls import include
|
||||
from allianceauth.hooks import get_hooks
|
||||
from django.urls import path
|
||||
|
||||
|
||||
0
allianceauth/services/views.py
Executable file → Normal file
0
allianceauth/srp/__init__.py
Executable file → Normal file
0
allianceauth/srp/admin.py
Executable file → Normal file
0
allianceauth/srp/form.py
Executable file → Normal file
0
allianceauth/srp/models.py
Executable file → Normal file
0
allianceauth/srp/tests/__init__.py
Executable file → Normal file
0
allianceauth/srp/tests/test_managers.py
Executable file → Normal file
0
allianceauth/srp/views.py
Executable file → Normal file
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 26 KiB |
BIN
allianceauth/static/allianceauth/icons/apple-touch-icon.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 7.0 KiB |
9
allianceauth/static/allianceauth/icons/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/static/allianceauth/icons/mstile-150x150.png"/>
|
||||
<TileColor>#2d89ef</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
allianceauth/static/allianceauth/icons/favicon-16x16.png
Executable file → Normal file
|
Before Width: | Height: | Size: 483 B After Width: | Height: | Size: 941 B |
BIN
allianceauth/static/allianceauth/icons/favicon-32x32.png
Executable file → Normal file
|
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 1.5 KiB |
BIN
allianceauth/static/allianceauth/icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
allianceauth/static/allianceauth/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
41
allianceauth/static/allianceauth/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="575.000000pt" height="575.000000pt" viewBox="0 0 575.000000 575.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,575.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2765 5731 c-58 -21 -122 -62 -143 -92 -8 -11 -32 -62 -54 -112 -21
|
||||
-51 -45 -105 -53 -122 -7 -16 -47 -109 -88 -205 -112 -261 -375 -871 -415
|
||||
-960 -11 -25 -61 -142 -112 -260 -50 -118 -102 -237 -115 -265 -12 -27 -71
|
||||
-162 -130 -300 -217 -505 -297 -691 -310 -720 -8 -16 -145 -334 -305 -705
|
||||
-160 -371 -298 -691 -307 -710 -28 -63 -40 -91 -168 -390 -70 -162 -133 -308
|
||||
-140 -325 -8 -16 -25 -55 -38 -85 -13 -30 -35 -80 -47 -110 -12 -30 -31 -73
|
||||
-41 -95 -16 -35 -36 -81 -106 -244 l-14 -33 33 20 c18 11 103 60 188 110 85
|
||||
49 171 99 190 111 19 11 55 32 80 46 25 14 70 40 101 58 208 121 326 189 339
|
||||
198 8 5 94 55 190 110 96 56 191 111 210 122 19 11 46 27 60 34 14 8 46 26 71
|
||||
41 69 41 557 326 577 337 9 6 68 39 130 75 62 36 125 71 140 79 15 8 29 18 30
|
||||
23 2 4 8 8 13 8 4 0 46 22 91 49 46 27 99 58 118 68 19 11 172 100 340 198
|
||||
168 98 320 186 338 197 18 10 103 59 187 108 84 49 169 98 187 108 18 11 56
|
||||
33 84 49 27 17 70 41 95 55 24 14 123 72 219 128 96 56 195 114 220 128 25 13
|
||||
50 29 57 36 9 8 -16 74 -103 276 -63 146 -121 279 -128 295 -8 17 -28 64 -46
|
||||
105 -34 80 -55 127 -75 175 -71 163 -148 341 -355 820 -133 308 -250 578 -260
|
||||
600 -10 22 -44 101 -75 175 -32 74 -66 153 -76 175 -10 22 -65 148 -121 281
|
||||
-113 263 -126 282 -224 326 -67 29 -173 34 -239 9z"/>
|
||||
<path d="M4500 2370 c-14 -11 -28 -20 -32 -20 -6 0 -107 -58 -158 -90 -8 -5
|
||||
-28 -17 -45 -26 -16 -9 -168 -97 -337 -195 -169 -99 -322 -187 -340 -198 -18
|
||||
-10 -58 -33 -88 -51 -30 -18 -71 -42 -90 -53 -44 -25 -414 -239 -418 -242 -2
|
||||
-2 16 -14 40 -27 24 -14 153 -89 288 -168 135 -78 259 -150 275 -160 17 -9
|
||||
116 -67 220 -127 105 -61 206 -120 225 -130 19 -11 86 -50 148 -87 62 -36 132
|
||||
-77 155 -90 23 -13 98 -56 167 -96 68 -40 138 -81 155 -90 16 -9 64 -37 105
|
||||
-62 41 -24 82 -47 90 -51 8 -4 29 -15 45 -26 151 -92 639 -370 643 -366 3 3
|
||||
-1 17 -8 32 -7 16 -56 129 -110 253 -93 216 -131 302 -150 345 -5 11 -62 142
|
||||
-126 290 -64 149 -124 288 -134 310 -10 22 -39 90 -64 150 -26 61 -54 124 -61
|
||||
141 -8 16 -30 68 -50 114 -78 183 -84 197 -124 283 -17 37 -31 74 -31 80 0 7
|
||||
-4 17 -9 22 -4 6 -41 87 -81 180 -40 94 -73 171 -74 172 -1 1 -12 -7 -26 -17z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
19
allianceauth/static/allianceauth/icons/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/allianceauth/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/allianceauth/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
30
allianceauth/static/allianceauth/js/timerboard.js
Normal file
@@ -0,0 +1,30 @@
|
||||
$(document).ready(() => {
|
||||
'use strict';
|
||||
const inputAbsoluteTime = $('input#id_absolute_time');
|
||||
const inputCountdown = $('#id_days_left, #id_hours_left, #id_minutes_left');
|
||||
|
||||
//inputAbsoluteTime.prop('disabled', true);
|
||||
inputAbsoluteTime.parent().hide()
|
||||
inputAbsoluteTime.parent().prev('label').hide()
|
||||
inputCountdown.prop('required', true);
|
||||
|
||||
$('input#id_absolute_checkbox').change(function () {
|
||||
if ($(this).prop("checked")) {
|
||||
// check box enabled
|
||||
inputAbsoluteTime.parent().show()
|
||||
inputAbsoluteTime.parent().prev('label').show()
|
||||
inputCountdown.parent().hide()
|
||||
inputCountdown.parent().prev('label').hide()
|
||||
inputAbsoluteTime.prop('required', true);
|
||||
inputCountdown.prop('required', false);
|
||||
} else {
|
||||
// Checkbox is not checked
|
||||
inputAbsoluteTime.parent().hide()
|
||||
inputAbsoluteTime.parent().prev('label').hide()
|
||||
inputCountdown.parent().show()
|
||||
inputCountdown.parent().prev('label').show()
|
||||
inputAbsoluteTime.prop('required', false);
|
||||
inputCountdown.prop('required', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -92,8 +92,11 @@
|
||||
{% include "allianceauth/admin-status/celery_bar_partial.html" with label="failed" level="danger" tasks_count=tasks_failed %}
|
||||
</div>
|
||||
<p>
|
||||
{% blocktranslate with running_count=tasks_running|default_if_none:"?"|intcomma %}
|
||||
{{ running_count }} running |
|
||||
{% endblocktranslate %}
|
||||
{% blocktranslate with queue_length=task_queue_length|default_if_none:"?"|intcomma %}
|
||||
{{ queue_length }} queued tasks
|
||||
{{ queue_length }} queued
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
{% load static %}
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'allianceauth/icons/apple-touch-icon.png' %}">
|
||||
<link rel="icon" type="image/png" href="{% static 'allianceauth/icons/favicon-16x16.png' %}" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="{% static 'allianceauth/icons/favicon-32x32.png' %}" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="{% static 'allianceauth/icons/favicon-96x96.png' %}" sizes="96x96">
|
||||
<link rel="apple-touch-icon" href="{% static 'allianceauth/icons/apple-touch-icon.png' %}">
|
||||
<link rel="manifest" href="{% static 'allianceauth/icons/site.webmanifest' %}">
|
||||
<link rel="mask-icon" href="{% static 'allianceauth/icons/safari-pinned-tab.svg' %}" color="#5bbad5">
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'allianceauth/icons/favicon.ico' %}">
|
||||
<meta name="msapplication-TileColor" content="#2d89ef">
|
||||
<meta name="msapplication-config" content="{% static 'allianceauth/icons/browserconfig.xml' %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
3
allianceauth/templates/bundles/timerboard-js.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% load static %}
|
||||
|
||||
<script src="{% static 'allianceauth/js/timerboard.js' %}"></script>
|
||||
@@ -40,7 +40,7 @@ def decimal_widthratio(this_value, max_value, max_width) -> str:
|
||||
if max_value == 0:
|
||||
return str(0)
|
||||
|
||||
return str(round(this_value/max_value * max_width, 2))
|
||||
return str(round(this_value / max_value * max_width, 2))
|
||||
|
||||
|
||||
@register.inclusion_tag('allianceauth/admin-status/overview.html')
|
||||
@@ -54,7 +54,8 @@ def status_overview() -> dict:
|
||||
"tasks_failed": 0,
|
||||
"tasks_total": 0,
|
||||
"tasks_hours": 0,
|
||||
"earliest_task": None
|
||||
"earliest_task": None,
|
||||
"tasks_running": 0
|
||||
}
|
||||
response.update(_current_notifications())
|
||||
response.update(_current_version_summary())
|
||||
@@ -72,7 +73,8 @@ def _celery_stats() -> dict:
|
||||
"tasks_failed": results.failed,
|
||||
"tasks_total": results.total,
|
||||
"tasks_hours": results.hours,
|
||||
"earliest_task": results.earliest_task
|
||||
"earliest_task": results.earliest_task,
|
||||
"tasks_running": results.running,
|
||||
}
|
||||
|
||||
|
||||
|
||||
72
allianceauth/tests/test_urls.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch, MagicMock
|
||||
from django.urls import URLPattern
|
||||
|
||||
from allianceauth.services.hooks import UrlHook
|
||||
from allianceauth.urls import urls_from_apps
|
||||
|
||||
MODULE_PATH = "allianceauth.urls"
|
||||
|
||||
|
||||
@patch(MODULE_PATH + ".main_character_required")
|
||||
@patch(MODULE_PATH + ".decorate_url_patterns")
|
||||
class TestUrlsFromApps(TestCase):
|
||||
def test_should_decorate_url_by_default(self, mock_decorate_url_patterns, mock_main_character_required):
|
||||
# given
|
||||
def hook_function():
|
||||
return UrlHook(urlconf_module, "my_namespace", r"^my_app/")
|
||||
|
||||
view = MagicMock(name="view")
|
||||
path = MagicMock(spec=URLPattern, name="path")
|
||||
path.callback = view
|
||||
urlconf_module = [patch], "my_app"
|
||||
# when
|
||||
result = urls_from_apps([hook_function], [])
|
||||
# then
|
||||
self.assertIsInstance(result[0], URLPattern)
|
||||
self.assertTrue(mock_decorate_url_patterns.called)
|
||||
args, _ = mock_decorate_url_patterns.call_args
|
||||
decorator = args[1]
|
||||
self.assertEqual(decorator, mock_main_character_required)
|
||||
excluded_views = args[2]
|
||||
self.assertIsNone(excluded_views)
|
||||
|
||||
def test_should_not_decorate_when_excluded(self, mock_decorate_url_patterns, mock_main_character_required):
|
||||
# given
|
||||
def hook_function():
|
||||
return UrlHook(urlconf_module, "my_namespace", r"^my_app/", ["excluded_view"])
|
||||
|
||||
view = MagicMock(name="view")
|
||||
path = MagicMock(spec=URLPattern, name="path")
|
||||
path.callback = view
|
||||
urlconf_module = [patch], "my_app"
|
||||
# when
|
||||
result = urls_from_apps([hook_function], ["my_app"])
|
||||
# then
|
||||
self.assertIsInstance(result[0], URLPattern)
|
||||
self.assertTrue(mock_decorate_url_patterns.called)
|
||||
args, _ = mock_decorate_url_patterns.call_args
|
||||
decorator = args[1]
|
||||
self.assertEqual(decorator, mock_main_character_required)
|
||||
excluded_views = args[2]
|
||||
self.assertSetEqual(excluded_views, {"excluded_view"})
|
||||
|
||||
def test_should_decorate_when_app_has_no_permission(self, mock_decorate_url_patterns, mock_main_character_required):
|
||||
# given
|
||||
def hook_function():
|
||||
return UrlHook(urlconf_module, "my_namespace", r"^my_app/", ["excluded_view"])
|
||||
|
||||
view = MagicMock(name="view")
|
||||
path = MagicMock(spec=URLPattern, name="path")
|
||||
path.callback = view
|
||||
urlconf_module = [patch], "my_app"
|
||||
# when
|
||||
result = urls_from_apps([hook_function], ["other_app"])
|
||||
# then
|
||||
self.assertIsInstance(result[0], URLPattern)
|
||||
self.assertTrue(mock_decorate_url_patterns.called)
|
||||
args, _ = mock_decorate_url_patterns.call_args
|
||||
decorator = args[1]
|
||||
self.assertEqual(decorator, mock_main_character_required)
|
||||
excluded_views = args[2]
|
||||
self.assertIsNone(excluded_views)
|
||||
0
allianceauth/timerboard/__init__.py
Executable file → Normal file
0
allianceauth/timerboard/admin.py
Executable file → Normal file
35
allianceauth/timerboard/form.py
Executable file → Normal file
@@ -61,14 +61,17 @@ class TimerForm(forms.ModelForm):
|
||||
structure = forms.ChoiceField(choices=structure_choices, required=True, label=_("Structure Type"))
|
||||
timer_type = forms.ChoiceField(choices=TimerType.choices, label=_("Timer Type"))
|
||||
objective = forms.ChoiceField(choices=objective_choices, required=True, label=_("Objective"))
|
||||
days_left = forms.IntegerField(required=True, label=_("Days Remaining"), validators=[MinValueValidator(0)])
|
||||
hours_left = forms.IntegerField(required=True, label=_("Hours Remaining"),
|
||||
absolute_checkbox = forms.BooleanField(label=_("Absolute Timer"), required=False, initial=False)
|
||||
absolute_time = forms.CharField(required=False,label=_("Date and Time"))
|
||||
days_left = forms.IntegerField(required=False, label=_("Days Remaining"), validators=[MinValueValidator(0)])
|
||||
hours_left = forms.IntegerField(required=False, label=_("Hours Remaining"),
|
||||
validators=[MinValueValidator(0), MaxValueValidator(23)])
|
||||
minutes_left = forms.IntegerField(required=True, label=_("Minutes Remaining"),
|
||||
minutes_left = forms.IntegerField(required=False, label=_("Minutes Remaining"),
|
||||
validators=[MinValueValidator(0), MaxValueValidator(59)])
|
||||
important = forms.BooleanField(label=_("Important"), required=False)
|
||||
corp_timer = forms.BooleanField(label=_("Corp-Restricted"), required=False)
|
||||
|
||||
|
||||
def save(self, commit=True):
|
||||
timer = super().save(commit=False)
|
||||
|
||||
@@ -77,18 +80,30 @@ class TimerForm(forms.ModelForm):
|
||||
corporation = character.corporation
|
||||
logger.debug("Determined timer save request on behalf "
|
||||
"of character {} corporation {}".format(character, corporation))
|
||||
# calculate future time
|
||||
future_time = datetime.timedelta(days=self.cleaned_data['days_left'], hours=self.cleaned_data['hours_left'],
|
||||
minutes=self.cleaned_data['minutes_left'])
|
||||
current_time = timezone.now()
|
||||
eve_time = current_time + future_time
|
||||
logger.debug(
|
||||
f"Determined timer eve time is {eve_time} - current time {current_time}, adding {future_time}")
|
||||
|
||||
days_left = self.cleaned_data['days_left']
|
||||
hours_left = self.cleaned_data['hours_left']
|
||||
minutes_left = self.cleaned_data['minutes_left']
|
||||
absolute_time = self.cleaned_data['absolute_time']
|
||||
|
||||
if days_left or hours_left or minutes_left:
|
||||
# Calculate future time
|
||||
future_time = datetime.timedelta(days=days_left, hours=hours_left, minutes=minutes_left)
|
||||
current_time = timezone.now()
|
||||
eve_time = current_time + future_time
|
||||
logger.debug(f"Determined timer eve time is {eve_time} - current time {current_time}, adding {future_time}")
|
||||
elif absolute_time:
|
||||
# Use absolute time
|
||||
eve_time = absolute_time
|
||||
else:
|
||||
raise ValueError("Either future time or absolute time must be provided.")
|
||||
|
||||
timer.eve_time = eve_time
|
||||
timer.eve_character = character
|
||||
timer.eve_corp = corporation
|
||||
timer.user = self.user
|
||||
|
||||
if commit:
|
||||
timer.save()
|
||||
|
||||
return timer
|
||||
|
||||
0
allianceauth/timerboard/models.py
Executable file → Normal file
@@ -30,3 +30,21 @@
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/jquery-datetimepicker-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$('#id_start').datetimepicker({
|
||||
setlocale: '{{ LANGUAGE_CODE }}',
|
||||
{% if NIGHT_MODE %}
|
||||
theme: 'dark',
|
||||
{% else %}
|
||||
theme: 'default',
|
||||
{% endif %}
|
||||
mask: true,
|
||||
format: 'Y-m-d H:i',
|
||||
minDate: 0
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -12,3 +12,19 @@
|
||||
{% block submit_button_text %}
|
||||
{% translate "Create Timer" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/timerboard-js.html' %}
|
||||
{% include 'bundles/jquery-datetimepicker-js.html' %}
|
||||
{% include 'bundles/jquery-datetimepicker-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$('input#id_absolute_time').datetimepicker({
|
||||
setlocale: '{{ LANGUAGE_CODE }}',
|
||||
theme: {% if NIGHT_MODE %}'dark'{% else %}'default'{% endif %},
|
||||
format: 'Y-m-d H:i',
|
||||
minDate: 0,
|
||||
defaultDate: null
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -12,3 +12,23 @@
|
||||
{% block submit_button_text %}
|
||||
{% translate "Update Structure Timer" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/timerboard-js.html' %}
|
||||
{% include 'bundles/jquery-datetimepicker-js.html' %}
|
||||
{% include 'bundles/jquery-datetimepicker-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$('input#id_absolute_time').datetimepicker({
|
||||
setlocale: '{{ LANGUAGE_CODE }}',
|
||||
{% if NIGHT_MODE %}
|
||||
theme: 'dark',
|
||||
{% else %}
|
||||
theme: 'default',
|
||||
{% endif %}
|
||||
mask: true,
|
||||
format: 'Y-m-d H:i',
|
||||
defaultDate: null
|
||||
});
|
||||
{% endblock extra_script %}
|
||||
|
||||
0
allianceauth/timerboard/views.py
Executable file → Normal file
54
allianceauth/urls.py
Executable file → Normal file
@@ -1,24 +1,54 @@
|
||||
from django.urls import path
|
||||
import esi.urls
|
||||
from typing import List, Iterable, Callable
|
||||
|
||||
from django.conf.urls import include
|
||||
import esi.urls
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.urls import URLPattern, include, path
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
import allianceauth.authentication.views
|
||||
import allianceauth.authentication.urls
|
||||
import allianceauth.notifications.urls
|
||||
import allianceauth.authentication.views
|
||||
import allianceauth.groupmanagement.urls
|
||||
import allianceauth.notifications.urls
|
||||
import allianceauth.services.urls
|
||||
from allianceauth.authentication.decorators import main_character_required, decorate_url_patterns
|
||||
from allianceauth import NAME
|
||||
from allianceauth import views
|
||||
from allianceauth import NAME, views
|
||||
from allianceauth.authentication import hmac_urls
|
||||
from allianceauth.authentication.decorators import (
|
||||
decorate_url_patterns,
|
||||
main_character_required
|
||||
)
|
||||
from allianceauth.hooks import get_hooks
|
||||
|
||||
admin.site.site_header = NAME
|
||||
|
||||
|
||||
def urls_from_apps(
|
||||
apps_hook_functions: Iterable[Callable], public_views_allowed: List[str]
|
||||
) -> List[URLPattern]:
|
||||
"""Return urls from apps and add default decorators."""
|
||||
|
||||
url_patterns = []
|
||||
allowed_apps = set(public_views_allowed)
|
||||
for app_hook_function in apps_hook_functions:
|
||||
url_hook = app_hook_function()
|
||||
app_pattern = url_hook.include_pattern
|
||||
excluded_views = (
|
||||
url_hook.excluded_views
|
||||
if app_pattern.app_name in allowed_apps
|
||||
else None
|
||||
)
|
||||
url_patterns += [
|
||||
path(
|
||||
"",
|
||||
decorate_url_patterns(
|
||||
[app_pattern], main_character_required, excluded_views
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
return url_patterns
|
||||
|
||||
|
||||
# Functional/Untranslated URL's
|
||||
urlpatterns = [
|
||||
# Locale
|
||||
@@ -49,8 +79,6 @@ urlpatterns = [
|
||||
path('night/', views.NightModeRedirectView.as_view(), name='nightmode')
|
||||
]
|
||||
|
||||
|
||||
# Append app urls
|
||||
app_urls = get_hooks('url_hook')
|
||||
for app in app_urls:
|
||||
urlpatterns += [path('', decorate_url_patterns([app().include_pattern], main_character_required))]
|
||||
url_hooks = get_hooks("url_hook")
|
||||
public_views_allows = getattr(settings, "APPS_WITH_PUBLIC_VIEWS", [])
|
||||
urlpatterns += urls_from_apps(url_hooks, public_views_allows)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PROTOCOL=https://
|
||||
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
||||
DOMAIN=%DOMAIN%
|
||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.5.1
|
||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.6.0
|
||||
|
||||
# Nginx Proxy Manager
|
||||
PROXY_HTTP_PORT=80
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM python:3.9-slim
|
||||
ARG AUTH_VERSION=v3.5.1
|
||||
ARG AUTH_VERSION=v3.6.0
|
||||
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
||||
ENV VIRTUAL_ENV=/opt/venv
|
||||
ENV AUTH_USER=allianceauth
|
||||
|
||||
@@ -1,54 +1,65 @@
|
||||
# URL Hooks
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
URLs added through URL Hooks are protected by a decorator which ensures the requesting user is logged in and has a main character set.
|
||||
```
|
||||
## Base functionality
|
||||
|
||||
The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook.
|
||||
|
||||
To register a UrlHook class you would do the following:
|
||||
|
||||
@hooks.register('url_hook')
|
||||
def register_urls():
|
||||
return UrlHook(app_name.urls, 'app_name', r^'app_name/')
|
||||
```python
|
||||
@hooks.register('url_hook')
|
||||
def register_urls():
|
||||
return UrlHook(app_name.urls, 'app_name', r^'app_name/')
|
||||
```
|
||||
|
||||
### Public views
|
||||
|
||||
The `UrlHook` class specifies some parameters/instance variables required for URL pattern inclusion.
|
||||
In addition is it possible to make views public. Normally, all views are automatically decorated with the `main_character_required` decorator. That decorator ensures a user needs to be logged in and have a main before he can access that view. This feature protects against a community app sneaking in a public view without the administrator knowing about it.
|
||||
|
||||
`UrlHook(urls, app_name, base_url)`
|
||||
An app can opt-out of this feature by adding a list of views to be excluded when registering the URLs. See the `excluded_views` parameter for details.
|
||||
|
||||
#### urls
|
||||
The urls module to include. See [the Django docs](https://docs.djangoproject.com/en/dev/topics/http/urls/#example) for designing urlpatterns.
|
||||
#### namespace
|
||||
The URL namespace to apply. This is usually just the app name.
|
||||
#### base_url
|
||||
The URL prefix to match against in regex form. Example `r'^app_name/'`. This prefix will be applied in front of all URL patterns included. It is possible to use the same prefix as existing apps (or no prefix at all) but [standard URL resolution](https://docs.djangoproject.com/en/dev/topics/http/urls/#how-django-processes-a-request) ordering applies (hook URLs are the last ones registered).
|
||||
```eval_rst
|
||||
.. note::
|
||||
Note that for a public view to work, administrators need to also explicitly allow apps to have public views in their AA installation, by adding the apps label to ``APPS_WITH_PUBLIC_VIEWS`` setting.
|
||||
```
|
||||
|
||||
### Example
|
||||
## Examples
|
||||
|
||||
An app called `plugin` provides a single view:
|
||||
|
||||
def index(request):
|
||||
return render(request, 'plugin/index.html')
|
||||
```python
|
||||
def index(request):
|
||||
return render(request, 'plugin/index.html')
|
||||
```
|
||||
|
||||
The app's `urls.py` would look like so:
|
||||
|
||||
from django.urls import path
|
||||
import plugin.views
|
||||
```python
|
||||
from django.urls import path
|
||||
import plugin.views
|
||||
|
||||
urlpatterns = [
|
||||
path('index/', plugins.views.index, name='index'),
|
||||
]
|
||||
urlpatterns = [
|
||||
path('index/', plugins.views.index, name='index'),
|
||||
]
|
||||
```
|
||||
|
||||
Subsequently it would implement the UrlHook in a dedicated `auth_hooks.py` file like so:
|
||||
|
||||
from alliance_auth import hooks
|
||||
from services.hooks import UrlHook
|
||||
import plugin.urls
|
||||
```python
|
||||
from alliance_auth import hooks
|
||||
from services.hooks import UrlHook
|
||||
import plugin.urls
|
||||
|
||||
@hooks.register('url_hook')
|
||||
def register_urls():
|
||||
return UrlHook(plugin.urls, 'plugin', r^'plugin/')
|
||||
@hooks.register('url_hook')
|
||||
def register_urls():
|
||||
return UrlHook(plugin.urls, 'plugin', r^'plugin/')
|
||||
```
|
||||
|
||||
When this app is included in the project's `settings.INSTALLED_APPS` users would access the index view by navigating to `https://example.com/plugin/index`.
|
||||
|
||||
## API
|
||||
|
||||
```eval_rst
|
||||
.. autoclass:: allianceauth.services.hooks.UrlHook
|
||||
:members:
|
||||
```
|
||||
|
||||
@@ -113,7 +113,7 @@ By default Corp Stats are only updated on demand. If you want to automatically r
|
||||
```python
|
||||
CELERYBEAT_SCHEDULE['update_all_corpstats'] = {
|
||||
'task': 'allianceauth.corputils.tasks.update_all_corpstats',
|
||||
'schedule': crontab(minute=0, hour="*/6"),
|
||||
'schedule': crontab(minute="0", hour="*/6"),
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ DISCORD_SYNC_NAMES = False
|
||||
|
||||
CELERYBEAT_SCHEDULE['discord.update_all_usernames'] = {
|
||||
'task': 'discord.update_all_usernames',
|
||||
'schedule': crontab(minute=0, hour='*/12'),
|
||||
'schedule': crontab(minute='0', hour='*/12'),
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ Place your virtual host configuration in the appropriate section within `/etc/ht
|
||||
|
||||
ProxyPassMatch ^/static !
|
||||
ProxyPassMatch ^/robots.txt !
|
||||
ProxyPassMatch ^/favicon.ico !
|
||||
|
||||
ProxyPass / http://127.0.0.1:8000/
|
||||
ProxyPassReverse / http://127.0.0.1:8000/
|
||||
@@ -82,6 +83,7 @@ Place your virtual host configuration in the appropriate section within `/etc/ht
|
||||
|
||||
Alias "/static" "/var/www/myauth/static"
|
||||
Alias "/robots.txt" "/var/www/myauth/static/robots.txt"
|
||||
Alias "/favicon.ico" "/var/www/myauth/static/allianceauth/icons/favicon.ico"
|
||||
|
||||
<Directory "/var/www/myauth/static">
|
||||
Require all granted
|
||||
@@ -91,6 +93,11 @@ Place your virtual host configuration in the appropriate section within `/etc/ht
|
||||
SetHandler None
|
||||
Require all granted
|
||||
</Location>
|
||||
|
||||
<Location "/favicon.ico">
|
||||
SetHandler None
|
||||
Require all granted
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
|
||||
@@ -83,8 +83,6 @@ server {
|
||||
|
||||
server_name example.com;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
location /static {
|
||||
alias /var/www/myauth/static;
|
||||
autoindex off;
|
||||
@@ -94,6 +92,10 @@ server {
|
||||
alias /var/www/myauth/static/robots.txt;
|
||||
}
|
||||
|
||||
location /favicon.ico {
|
||||
alias /var/www/myauth/static/allianceauth/icons/favicon.ico;
|
||||
}
|
||||
|
||||
# Gunicorn config goes below
|
||||
location / {
|
||||
include proxy_params;
|
||||
|
||||
@@ -1,6 +1,91 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
requires = ["flit_core >=3.2,<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
[project]
|
||||
name = "allianceauth"
|
||||
dynamic = ["version", "description"]
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
requires-python = ">=3.8"
|
||||
authors = [
|
||||
{ name = "Alliance Auth", email = "adarnof@gmail.com" },
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
keywords = [
|
||||
"allianceauth",
|
||||
"eveonline",
|
||||
]
|
||||
classifiers = [
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 4.0",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||
]
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"beautifulsoup4",
|
||||
"celery-once>=3.0.1",
|
||||
"celery>=5.2.0,<6",
|
||||
"django-bootstrap-form",
|
||||
"django-celery-beat>=2.3.0",
|
||||
"django-esi>=4.0.1",
|
||||
"django-redis>=5.2.0",
|
||||
"django-registration>=3.3,<3.4",
|
||||
"django-sortedm2m",
|
||||
"django>=4.0.9,<4.1.0",
|
||||
"dnspython",
|
||||
"mysqlclient>=2.1.0",
|
||||
"openfire-restapi",
|
||||
"packaging>=21.0",
|
||||
"passlib",
|
||||
"pydiscourse",
|
||||
"python-slugify>=1.2",
|
||||
"redis>=4.0.0",
|
||||
"requests-oauthlib",
|
||||
"requests>=2.9.1",
|
||||
"semantic-version",
|
||||
"slixmpp",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"coverage>=4.3.1",
|
||||
"django-webtest",
|
||||
"requests-mock>=1.2.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
allianceauth = "allianceauth.bin.allianceauth:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://gitlab.com/allianceauth/allianceauth"
|
||||
Documentation = "https://allianceauth.readthedocs.io/"
|
||||
Source = "https://gitlab.com/allianceauth/allianceauth"
|
||||
Tracker = "https://gitlab.com/allianceauth/allianceauth/-/issues"
|
||||
|
||||
[tool.flit.module]
|
||||
name = "allianceauth"
|
||||
|
||||
[tool.isort]
|
||||
profile = "django"
|
||||
sections = [
|
||||
"FUTURE",
|
||||
"STDLIB",
|
||||
"THIRDPARTY",
|
||||
"DJANGO",
|
||||
"ESI",
|
||||
"FIRSTPARTY",
|
||||
"LOCALFOLDER"
|
||||
]
|
||||
known_esi = ["esi"]
|
||||
known_django = ["django"]
|
||||
skip_gitignore = true
|
||||
|
||||
79
setup.cfg
@@ -1,79 +0,0 @@
|
||||
[metadata]
|
||||
name = allianceauth
|
||||
version = attr: allianceauth.__version__
|
||||
description = An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
author = Alliance Auth
|
||||
author_email = adarnof@gmail.com
|
||||
license = GPL-2.0
|
||||
license_files = LICENSE
|
||||
classifiers =
|
||||
Environment :: Web Environment
|
||||
Framework :: Django
|
||||
Framework :: Django :: 4
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: GNU General Public License v2 (GPLv2)
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Topic :: Internet :: WWW/HTTP
|
||||
Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
home_page = https://gitlab.com/allianceauth/allianceauth
|
||||
keywords =
|
||||
allianceauth
|
||||
eveonline
|
||||
project_urls =
|
||||
Issue / Bug Reports = https://gitlab.com/allianceauth/allianceauth/-/issues
|
||||
Documentation = https://allianceauth.readthedocs.io/
|
||||
|
||||
[options]
|
||||
packages = find_namespace:
|
||||
install_requires =
|
||||
bcrypt
|
||||
beautifulsoup4
|
||||
celery>=5.2.0,<6
|
||||
celery-once>=3.0.1
|
||||
django>=4.0.9,<4.1.0
|
||||
django-bootstrap-form
|
||||
django-celery-beat>=2.3.0
|
||||
django-esi>=4.0.1
|
||||
django-redis>=5.2.0
|
||||
django-registration>=3.3,<3.4
|
||||
django-sortedm2m
|
||||
dnspython
|
||||
mysqlclient>=2.1.0
|
||||
openfire-restapi
|
||||
packaging>=21.0
|
||||
passlib
|
||||
pydiscourse
|
||||
python-slugify>=1.2
|
||||
redis>=4.0.0
|
||||
requests>=2.9.1
|
||||
requests-oauthlib
|
||||
semantic-version
|
||||
slixmpp
|
||||
python_requires = >=3.8
|
||||
include_package_data = True
|
||||
zip_safe = False
|
||||
|
||||
[options.packages.find]
|
||||
include = allianceauth*
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
allianceauth = allianceauth.bin.allianceauth:main
|
||||
|
||||
[options.extras_require]
|
||||
test =
|
||||
coverage>=4.3.1
|
||||
django-webtest
|
||||
requests-mock>=1.2.0
|
||||
|
||||
[options.package_data]
|
||||
* = *
|
||||