Compare commits

..

19 Commits

Author SHA1 Message Date
Ariel Rin
113977b19f Version Bump 2.13.0 2022-06-18 13:07:36 +10:00
Ariel Rin
8f39b50b6d Merge branch 'Maestro-Zacht-fix-fat-attributeerror' into 'master'
fixed attribute error

See merge request allianceauth/allianceauth!1421
2022-06-18 02:53:11 +00:00
Maestro-Zacht
95b309c358 fixed attribute error 2022-06-18 02:53:11 +00:00
Ariel Rin
cf3df3b715 Merge branch 'fix_issue_1328' into 'master'
Fix: Changing group's state setting does not kick existing non-conforming group members

Closes #1328

See merge request allianceauth/allianceauth!1400
2022-06-18 02:47:14 +00:00
Erik Kalkoken
d815028c4d Fix: Changing group's state setting does not kick existing non-conforming group members 2022-06-18 02:47:14 +00:00
Ariel Rin
ac5570abe2 Merge branch 'fix_issue_1268' into 'master'
Fix: Service group updates broken when adding users to groups

Closes #1268

See merge request allianceauth/allianceauth!1403
2022-06-18 02:41:23 +00:00
Erik Kalkoken
84ad571aa4 Fix: Service group updates broken when adding users to groups 2022-06-18 02:41:23 +00:00
Ariel Rin
38e7705ae7 Merge branch 'docs-dark-mode' into 'master'
Add automatic dark mode to docs

See merge request allianceauth/allianceauth!1427
2022-06-18 02:39:59 +00:00
ErikKalkoken
0b6af014fa Add automatic dark mode to docs 2022-06-17 21:49:18 +02:00
Ariel Rin
2401f2299d Merge branch 'fix-doc-redis-issue' into 'master'
Fix: Broken docs generation on readthedocs.org (2nd attempt)

See merge request allianceauth/allianceauth!1425
2022-06-17 11:58:45 +00:00
Erik Kalkoken
919768c8bb Fix: Broken docs generation on readthedocs.org (2nd attempt) 2022-06-17 11:58:45 +00:00
Ariel Rin
24db21463b Merge branch 'docs-template-tags-example' into 'master'
Add example for template tags to docs

See merge request allianceauth/allianceauth!1426
2022-06-17 11:58:05 +00:00
Erik Kalkoken
1e029af83a Add example for template tags to docs 2022-06-17 11:58:05 +00:00
Ariel Rin
2b31be789d Merge branch 'fix-issue-1336' into 'master'
Fix: Broken docs generation on readthedocs.org

Closes #1336

See merge request allianceauth/allianceauth!1423
2022-06-06 10:48:16 +00:00
Erik Kalkoken
bf1b4bb549 Fix: Broken docs generation on readthedocs.org 2022-06-06 10:48:16 +00:00
Ariel Rin
dd42b807f0 Version Bump 2.12.1 2022-05-13 00:19:45 +10:00
Ariel Rin
542fbafd98 Merge branch 'cherry-pick-4836559a' into 'v2.12.x'
Merge branch 'fix-decimal_widthratio-template-tag' into 'v2.12.x'

See merge request allianceauth/allianceauth!1420
2022-05-12 14:14:01 +00:00
Ariel Rin
37b9f5c882 Merge branch 'fix-decimal_widthratio-template-tag' into 'v3.x'
[FIX] Division by zero in decimal_widthratio template tag

See merge request allianceauth/allianceauth!1419

(cherry picked from commit 4836559abe)

8dd07b97 [FIX] Devision by zero in decimal_widthratio template tag
17b06c88 Make it a string in accordance to the return value type
2022-05-12 13:33:45 +00:00
Ariel Rin
5bde9a6952 Version Bump 2.12.0 2022-05-12 18:54:22 +10:00
147 changed files with 1104 additions and 999 deletions

View File

@@ -14,7 +14,6 @@ stages:
include:
- template: Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
before_script:
- apt-get update && apt-get install redis-server -y
@@ -25,7 +24,7 @@ before_script:
pre-commit-check:
<<: *only-default
stage: pre-commit
image: python:3.8-bullseye
image: python:3.6-buster
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
@@ -47,6 +46,18 @@ dependency_scanning:
- python -V
- pip install wheel tox
test-3.7-core:
<<: *only-default
image: python:3.7-bullseye
script:
- tox -e py37-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.8-core:
<<: *only-default
image: python:3.8-bullseye
@@ -96,6 +107,18 @@ test-3.11-core:
path: coverage.xml
allow_failure: true
test-3.7-all:
<<: *only-default
image: python:3.7-bullseye
script:
- tox -e py37-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.8-all:
<<: *only-default
image: python:3.8-bullseye
@@ -145,18 +168,22 @@ test-3.11-all:
path: coverage.xml
allow_failure: true
test-docs:
<<: *only-default
image: python:3.9-bullseye
script:
- tox -e docs
deploy_production:
stage: deploy
image: python:3.10-bullseye
before_script:
- python -m pip install --upgrade pip
- python -m pip install --upgrade build
- python -m pip install --upgrade setuptools wheel twine
- pip install twine wheel
script:
- python -m build
- python -m twine upload dist/*
- python setup.py sdist bdist_wheel
- twine upload dist/*
rules:
- if: $CI_COMMIT_TAG

View File

@@ -5,7 +5,7 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.0.1
hooks:
- id: check-case-conflict
- id: check-json
@@ -22,18 +22,13 @@ repos:
args: [ '--remove' ]
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.4.0
rev: 2.3.54
hooks:
- id: editorconfig-checker
exclude: ^(LICENSE|allianceauth\/static\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
rev: v2.29.0
hooks:
- id: pyupgrade
args: [ --py38-plus ]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.20.0
hooks:
- id: setup-cfg-fmt
args: [ --py37-plus ]

View File

@@ -5,22 +5,19 @@
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
apt_packages:
- redis
tools:
python: "3.10"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml
# Optionally build your docs in additional formats such as PDF and ePub
formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
install:
- requirements: docs/requirements.txt

View File

@@ -1,7 +1,8 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '3.0.0a5'
__version__ = '2.13.0'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}'
default_app_config = 'allianceauth.apps.AllianceAuthConfig'

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.analytics.apps.AnalyticsConfig'

View File

@@ -1,6 +1,5 @@
from allianceauth.analytics.middleware import AnalyticsMiddleware
from unittest.mock import Mock
from django.http import HttpResponse
from django.test.testcases import TestCase
@@ -8,7 +7,7 @@ from django.test.testcases import TestCase
class TestAnalyticsMiddleware(TestCase):
def setUp(self):
self.middleware = AnalyticsMiddleware(HttpResponse)
self.middleware = AnalyticsMiddleware()
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.authentication.apps.AuthenticationConfig'

View File

@@ -1,16 +1,14 @@
from django.conf.urls import include
from django.conf.urls import url, include
from allianceauth.authentication import views
from django.urls import re_path
from django.urls import path
urlpatterns = [
path('activate/complete/', views.activation_complete, name='registration_activation_complete'),
url(r'^activate/complete/$', views.activation_complete, name='registration_activation_complete'),
# The activation key can make use of any character from the
# URL-safe base64 alphabet, plus the colon as a separator.
re_path(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
path('register/', views.RegistrationView.as_view(), name='registration_register'),
path('register/complete/', views.registration_complete, name='registration_complete'),
path('register/closed/', views.registration_closed, name='registration_disallowed'),
path('', include('django.contrib.auth.urls')),
url(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'),
url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
url(r'', include('django.contrib.auth.urls')),
]

View File

@@ -1,45 +0,0 @@
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
import logging
logger = logging.getLogger(__name__)
class UserSettingsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
"""Django Middleware: User Settings."""
# Intercept the built in django /setlang/ view and also save it to Database.
# Note the annoymous user check, only logged in users will ever hit the DB here
if request.path == '/i18n/setlang/' and not request.user.is_anonymous:
try:
request.user.profile.language = request.POST['language']
request.user.profile.save()
except Exception as e:
logger.exception(e)
# Only act during the login flow, _after_ user is activated (step 2: post-sso)
elif request.path == '/sso/login' and not request.user.is_anonymous:
# Set the Language Cookie, if it doesnt match the DB
# Null = hasnt been set by the user ever, dont act.
try:
if request.user.profile.language != request.LANGUAGE_CODE and request.user.profile.language is not None:
response.set_cookie(key=settings.LANGUAGE_COOKIE_NAME,
value=request.user.profile.language,
max_age=settings.LANGUAGE_COOKIE_AGE)
except Exception as e:
logger.exception(e)
# Set our Night mode flag from the DB
# Null = hasnt been set by the user ever, dont act.
#
# Night mode intercept is not needed in this middleware.
# is saved direct to DB in NightModeRedirectView
try:
if request.user.profile.night_mode is not None:
request.session["NIGHT_MODE"] = request.user.profile.night_mode
except Exception as e:
logger.exception(e)
return response

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.0.2 on 2022-02-26 03:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0019_merge_20211026_0919'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='language',
field=models.CharField(blank=True, choices=[('en', 'English'), ('de', 'German'), ('es', 'Spanish'), ('zh-hans', 'Chinese Simplified'), ('ru', 'Russian'), ('ko', 'Korean'), ('fr', 'French'), ('ja', 'Japanese'), ('it', 'Italian')], default='', max_length=10, verbose_name='Language'),
),
migrations.AddField(
model_name='userprofile',
name='night_mode',
field=models.BooleanField(blank=True, null=True, verbose_name='Night Mode'),
),
]

View File

@@ -2,10 +2,9 @@ import logging
from django.contrib.auth.models import User, Permission
from django.db import models, transaction
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
from allianceauth.notifications import notify
from django.conf import settings
from .managers import CharacterOwnershipManager, StateManager
@@ -63,39 +62,9 @@ class UserProfile(models.Model):
class Meta:
default_permissions = ('change',)
user = models.OneToOneField(
User,
related_name='profile',
on_delete=models.CASCADE)
main_character = models.OneToOneField(
EveCharacter,
blank=True,
null=True,
on_delete=models.SET_NULL)
state = models.ForeignKey(
State,
on_delete=models.SET_DEFAULT,
default=get_guest_state_pk)
LANGUAGE_CHOICES = [
('en', _('English')),
('de', _('German')),
('es', _('Spanish')),
('zh-hans', _('Chinese Simplified')),
('ru', _('Russian')),
('ko', _('Korean')),
('fr', _('French')),
('ja', _('Japanese')),
('it', _('Italian')),
]
language = models.CharField(
_("Language"), max_length=10,
choices=LANGUAGE_CHOICES,
blank=True,
default='')
night_mode = models.BooleanField(
_("Night Mode"),
blank=True,
null=True)
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
main_character = models.OneToOneField(EveCharacter, blank=True, null=True, on_delete=models.SET_NULL)
state = models.ForeignKey(State, on_delete=models.SET_DEFAULT, default=get_guest_state_pk)
def assign_state(self, state=None, commit=True):
if not state:
@@ -124,6 +93,8 @@ class UserProfile(models.Model):
def __str__(self):
return str(self.user)
class CharacterOwnership(models.Model):
class Meta:
default_permissions = ('change', 'delete')

View File

@@ -1,11 +1,6 @@
import logging
from .models import (
CharacterOwnership,
UserProfile,
get_guest_state,
State,
OwnershipRecord)
from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord
from django.contrib.auth.models import User
from django.db.models import Q
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
@@ -16,7 +11,7 @@ from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__)
state_changed = Signal()
state_changed = Signal(providing_args=['user', 'state'])
def trigger_state_check(state):
@@ -76,7 +71,7 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
@receiver(post_save, sender=User)
def create_required_models(sender, instance, created, *args, **kwargs):
# ensure all users have our Sub-Models
# ensure all users have a model
if created:
logger.debug(f'User {instance} created. Creating default UserProfile.')
UserProfile.objects.get_or_create(user=instance)

View File

@@ -1,27 +1,62 @@
import datetime as dt
from typing import Optional, List
import logging
from typing import List, Optional
from redis import Redis
from pytz import utc
from redis import Redis, RedisError
from django_redis import get_redis_connection
from django.core.cache import cache
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:
"""API for recording and analysing a series of events."""
"""API for recording and analyzing a series of events."""
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
def __init__(self, key_id: str, redis: Redis = None) -> None:
self._redis = get_redis_connection("default") if not redis else redis
if not isinstance(self._redis, Redis):
raise TypeError(
"This class requires a Redis client, but none was provided "
"and the default Django cache backend is not Redis either."
self._redis = cache.get_master_client() 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.clear()
@property
def is_disabled(self):
"""True when this object is disabled, e.g. Redis was not available at startup."""
return isinstance(self._redis, _RedisStub)
@property
def _key_counter(self):
return f"{self._ROOT_KEY}_{self._key_id}_COUNTER"

View File

@@ -1,13 +1,48 @@
import datetime as dt
from unittest.mock import patch
from pytz import utc
from redis import RedisError
from django.test import TestCase
from django.utils.timezone import now
from allianceauth.authentication.task_statistics.event_series import EventSeries
from allianceauth.authentication.task_statistics.event_series import (
EventSeries,
_RedisStub,
)
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
class TestEventSeries(TestCase):
def test_should_abort_without_redis_client(self):
# when
with patch(MODULE_PATH + ".cache.get_master_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 + ".cache.get_master_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 + ".cache.get_master_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):
# given
events = EventSeries("dummy")

View File

@@ -7,8 +7,8 @@
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Submit" %}</button>
<br>
<br/>
</form>
{% endblock %}

View File

@@ -1,17 +1,4 @@
from django.db.models.signals import (
m2m_changed,
post_save,
pre_delete,
pre_save
)
from django.urls import reverse
from unittest import mock
MODULE_PATH = 'allianceauth.authentication'
def patch(target, *args, **kwargs):
return mock.patch(f'{MODULE_PATH}{target}', *args, **kwargs)
def get_admin_change_view_url(obj: object) -> str:

View File

@@ -1,175 +0,0 @@
from unittest import mock
from allianceauth.authentication.middleware import UserSettingsMiddleware
from unittest.mock import Mock
from django.http import HttpResponse
from django.test.testcases import TestCase
class TestUserSettingsMiddlewareSaveLang(TestCase):
def setUp(self):
self.middleware = UserSettingsMiddleware(HttpResponse)
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/i18n/setlang/'
self.request.POST = {
'language': 'fr'
}
self.request.user.profile.language = 'de'
self.request.user.is_anonymous = False
self.response = Mock()
self.response.content = 'hello world'
def test_middleware_passthrough(self):
"""
Simply tests the middleware runs cleanly
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.response, response)
def test_middleware_save_language_false_anonymous(self):
"""
Ensures the middleware wont change the usersettings
of a non-existent (anonymous) user
"""
self.request.user.is_anonymous = True
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.user.profile.language, 'de')
self.assertFalse(self.request.user.profile.save.called)
self.assertEqual(self.request.user.profile.save.call_count, 0)
def test_middleware_save_language_new(self):
"""
does the middleware change a language not set in the DB
"""
self.request.user.profile.language = None
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.user.profile.language, 'fr')
self.assertTrue(self.request.user.profile.save.called)
self.assertEqual(self.request.user.profile.save.call_count, 1)
def test_middleware_save_language_changed(self):
"""
Tests the middleware will change a language setting
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.user.profile.language, 'fr')
self.assertTrue(self.request.user.profile.save.called)
self.assertEqual(self.request.user.profile.save.call_count, 1)
class TestUserSettingsMiddlewareLoginFlow(TestCase):
def setUp(self):
self.middleware = UserSettingsMiddleware(HttpResponse)
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/sso/login'
self.request.session = {
'NIGHT_MODE': False
}
self.request.LANGUAGE_CODE = 'en'
self.request.user.profile.language = 'de'
self.request.user.profile.night_mode = True
self.request.user.is_anonymous = False
self.response = Mock()
self.response.content = 'hello world'
def test_middleware_passthrough(self):
"""
Simply tests the middleware runs cleanly
"""
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.response, middleware_response)
def test_middleware_sets_language_cookie_true_no_cookie(self):
"""
tests the middleware will set a cookie, while none is set
"""
self.request.LANGUAGE_CODE = None
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertTrue(middleware_response.set_cookie.called)
self.assertEqual(middleware_response.set_cookie.call_count, 1)
args, kwargs = middleware_response.set_cookie.call_args
self.assertEqual(kwargs['value'], 'de')
def test_middleware_sets_language_cookie_true_wrong_cookie(self):
"""
tests the middleware will set a cookie, while a different value is set
"""
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertTrue(middleware_response.set_cookie.called)
self.assertEqual(middleware_response.set_cookie.call_count, 1)
args, kwargs = middleware_response.set_cookie.call_args
self.assertEqual(kwargs['value'], 'de')
def test_middleware_sets_language_cookie_false_anonymous(self):
"""
ensures the middleware wont set a value for a non existent user (anonymous)
"""
self.request.user.is_anonymous = True
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertFalse = middleware_response.set_cookie.called
self.assertEqual(middleware_response.set_cookie.call_count, 0)
def test_middleware_sets_language_cookie_false_already_set(self):
"""
tests the middleware skips setting the cookie, if its already set correctly
"""
self.request.user.profile.language = 'en'
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertFalse = middleware_response.set_cookie.called
self.assertEqual(middleware_response.set_cookie.call_count, 0)
def test_middleware_sets_night_mode_not_set(self):
"""
tests the middleware will set night_mode if not set
"""
self.request.session = {}
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.session["NIGHT_MODE"], True)
def test_middleware_sets_night_mode_set(self):
"""
tests the middleware will set night_mode if set.
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.session["NIGHT_MODE"], True)

View File

@@ -1,94 +0,0 @@
from allianceauth.authentication.models import User, UserProfile
from allianceauth.eveonline.models import (
EveCharacter,
EveCorporationInfo,
EveAllianceInfo
)
from django.db.models.signals import (
pre_save,
post_save,
pre_delete,
m2m_changed
)
from allianceauth.tests.auth_utils import AuthUtils
from django.test.testcases import TestCase
from unittest.mock import Mock
from . import patch
class TestUserProfileSignals(TestCase):
def setUp(self):
state = AuthUtils.get_member_state()
self.char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.alliance = EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_name='alliance name',
alliance_ticker='TIKR',
executor_corp_id='2345',
)
self.corp = EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_name='corp name',
corporation_ticker='TIKK',
member_count=10,
alliance=self.alliance,
)
state.member_alliances.add(self.alliance)
state.member_corporations.add(self.corp)
self.member = AuthUtils.create_user('test user')
self.member.profile.main_character = self.char
self.member.profile.save()
@patch('.signals.create_required_models')
def test_create_required_models_triggered_true(
self, create_required_models):
"""
Create a User object here,
to generate UserProfile models
"""
post_save.connect(create_required_models, sender=User)
AuthUtils.create_user('test_create_required_models_triggered')
self.assertTrue = create_required_models.called
self.assertEqual(create_required_models.call_count, 1)
user = User.objects.get(username='test_create_required_models_triggered')
self.assertIsNot(UserProfile.objects.get(user=user), False)
@patch('.signals.create_required_models')
def test_create_required_models_triggered_false(
self, create_required_models):
"""
Only call a User object Update here,
which does not need to generate UserProfile models
"""
post_save.connect(create_required_models, sender=User)
char = EveCharacter.objects.create(
character_id='1266',
character_name='test character2',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
self.assertTrue = create_required_models.called
self.assertEqual(create_required_models.call_count, 0)
self.assertIsNot(UserProfile.objects.get(user=self.member), False)

View File

@@ -1,4 +1,5 @@
from django.urls import path
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from django.views.generic.base import TemplateView
from . import views
@@ -6,21 +7,21 @@ from . import views
app_name = 'authentication'
urlpatterns = [
path('', views.index, name='index'),
path(
'account/login/',
url(r'^$', views.index, name='index'),
url(
r'^account/login/$',
TemplateView.as_view(template_name='public/login.html'),
name='login'
),
path(
'account/characters/main/',
url(
r'^account/characters/main/$',
views.main_character_change,
name='change_main_character'
),
path(
'account/characters/add/',
url(
r'^account/characters/add/$',
views.add_character,
name='add_character'
),
path('dashboard/', views.dashboard, name='dashboard'),
url(r'^dashboard/$', views.dashboard, name='dashboard'),
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.corputils.apps.CorpUtilsConfig'

View File

@@ -1,5 +1,5 @@
from allianceauth.services.hooks import MenuItemHook, UrlHook
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth import hooks
from allianceauth.corputils import urls

View File

@@ -1,11 +1,12 @@
from django.urls import path
from django.conf.urls import url
from . import views
app_name = 'corputils'
urlpatterns = [
path('', views.corpstats_view, name='view'),
path('add/', views.corpstats_add, name='add'),
path('<int:corp_id>/', views.corpstats_view, name='view_corp'),
path('<int:corp_id>/update/', views.corpstats_update, name='update'),
path('search/', views.corpstats_search, name='search'),
]
url(r'^$', views.corpstats_view, name='view'),
url(r'^add/$', views.corpstats_add, name='add'),
url(r'^(?P<corp_id>(\d)*)/$', views.corpstats_view, name='view_corp'),
url(r'^(?P<corp_id>(\d)+)/update/$', views.corpstats_update, name='update'),
url(r'^search/$', views.corpstats_search, name='search'),
]

View File

@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required, permission_required,
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.db import IntegrityError
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from esi.decorators import token_required
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.eveonline.apps.EveonlineConfig'

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.eveonline.autogroups.apps.EveAutogroupsConfig'

View File

@@ -1,18 +0,0 @@
# Generated by Django 3.2.10 on 2022-01-05 18:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0015_factions'),
]
operations = [
migrations.AlterField(
model_name='evecharacter',
name='character_name',
field=models.CharField(db_index=True, max_length=254),
),
]

View File

@@ -25,8 +25,6 @@ DOOMHEIM_CORPORATION_ID = 1000001
class EveFactionInfo(models.Model):
"""A faction in Eve Online."""
faction_id = models.PositiveIntegerField(unique=True, db_index=True)
faction_name = models.CharField(max_length=254, unique=True)
@@ -68,8 +66,6 @@ class EveFactionInfo(models.Model):
class EveAllianceInfo(models.Model):
"""An alliance in Eve Online."""
alliance_id = models.PositiveIntegerField(unique=True)
alliance_name = models.CharField(max_length=254, unique=True)
alliance_ticker = models.CharField(max_length=254)
@@ -136,8 +132,6 @@ class EveAllianceInfo(models.Model):
class EveCorporationInfo(models.Model):
"""A corporation in Eve Online."""
corporation_id = models.PositiveIntegerField(unique=True)
corporation_name = models.CharField(max_length=254, unique=True)
corporation_ticker = models.CharField(max_length=254)
@@ -201,10 +195,9 @@ class EveCorporationInfo(models.Model):
class EveCharacter(models.Model):
"""A character in Eve Online."""
"""Character in Eve Online"""
character_id = models.PositiveIntegerField(unique=True)
character_name = models.CharField(max_length=254, db_index=True)
character_name = models.CharField(max_length=254, unique=True)
corporation_id = models.PositiveIntegerField()
corporation_name = models.CharField(max_length=254)
corporation_ticker = models.CharField(max_length=5)

View File

@@ -40,7 +40,7 @@ def update_character(character_id: int) -> None:
def run_model_update():
"""Update all alliances, corporations and characters from ESI"""
#update existing corp models
# update existing corp models
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.fleetactivitytracking.apps.FatConfig'

View File

@@ -1,5 +1,5 @@
from . import urls
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth import hooks
from allianceauth.services.hooks import MenuItemHook, UrlHook

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class FatlinkForm(forms.Form):

View File

@@ -21,7 +21,7 @@
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit" name="submit_fat">{% translate "Create fatlink" %}</button>
</form>
</div>

View File

@@ -1,30 +1,30 @@
from django.urls import path
from django.conf.urls import url
from . import views
app_name = 'fleetactivitytracking'
urlpatterns = [
# FleetActivityTracking (FAT)
path('', views.fatlink_view, name='view'),
path('statistics/', views.fatlink_statistics_view, name='statistics'),
path('statistics/corp/<int:corpid>/', views.fatlink_statistics_corp_view,
url(r'^$', views.fatlink_view, name='view'),
url(r'^statistics/$', views.fatlink_statistics_view, name='statistics'),
url(r'^statistics/corp/(\w+)$', views.fatlink_statistics_corp_view,
name='statistics_corp'),
path('statistics/corp/<int:corpid>/<int:year>/<int:month>/',
url(r'^statistics/corp/(?P<corpid>\w+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/',
views.fatlink_statistics_corp_view,
name='statistics_corp_month'),
path('statistics/<int:year>/<int:month>/', views.fatlink_statistics_view,
url(r'^statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', views.fatlink_statistics_view,
name='statistics_month'),
path('user/statistics/', views.fatlink_personal_statistics_view,
url(r'^user/statistics/$', views.fatlink_personal_statistics_view,
name='personal_statistics'),
path('user/statistics/<int:year>/', views.fatlink_personal_statistics_view,
url(r'^user/statistics/(?P<year>[0-9]+)/$', views.fatlink_personal_statistics_view,
name='personal_statistics_year'),
path('user/statistics/<int:year>/<int:month>/',
url(r'^user/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$',
views.fatlink_monthly_personal_statistics_view,
name='personal_statistics_month'),
path('user/<int:char_id>/statistics/<int:year>/<int:month>/',
url(r'^user/(?P<char_id>[0-9]+)/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$',
views.fatlink_monthly_personal_statistics_view,
name='user_statistics_month'),
path('create/', views.create_fatlink_view, name='create'),
path('modify/<str:fat_hash>/', views.modify_fatlink_view, name='modify'),
path('link/<str:fat_hash>/', views.click_fatlink_view, name='click'),
url(r'^create/$', views.create_fatlink_view, name='create'),
url(r'^modify/(?P<fat_hash>[a-zA-Z0-9_-]+)/$', views.modify_fatlink_view, name='modify'),
url(r'^link/(?P<fat_hash>[a-zA-Z0-9]+)/$', views.click_fatlink_view, name='click'),
]

View File

@@ -10,7 +10,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.shortcuts import render, redirect, get_object_or_404, Http404
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from esi.decorators import token_required
from allianceauth.eveonline.providers import provider
from .forms import FatlinkForm
@@ -212,7 +212,14 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
start_of_previous_month = first_day_of_previous_month(year, month)
if request.user.has_perm('auth.fleetactivitytracking_statistics') and char_id:
user = EveCharacter.objects.get(character_id=char_id).user
try:
user = EveCharacter.objects.get(character_id=char_id).character_ownership.user
except EveCharacter.DoesNotExist:
messages.error(request, _('Character does not exist'))
return redirect('fatlink:view')
except AttributeError:
messages.error(request, _('User does not exist'))
return redirect('fatlink:view')
else:
user = request.user
logger.debug(f"Personal monthly statistics view for user {user} called by {request.user}")

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.groupmanagement.apps.GroupManagementConfig'

View File

@@ -1,8 +1,8 @@
from django.apps import apps
from django.contrib import admin
from django.contrib.auth.models import Group as BaseGroup
from django.contrib.auth.models import Permission, User
from django.db.models import Count
from django.contrib.auth.models import Group as BaseGroup, Permission, User
from django.db.models import Count, Exists, OuterRef
from django.db.models.functions import Lower
from django.db.models.signals import (
m2m_changed,
@@ -15,6 +15,7 @@ from django.dispatch import receiver
from .forms import GroupAdminForm, ReservedGroupNameAdminForm
from .models import AuthGroup, GroupRequest, ReservedGroupName
from .tasks import remove_users_not_matching_states_from_group
if 'eve_autogroups' in apps.app_configs:
_has_auto_groups = True
@@ -106,14 +107,13 @@ class HasLeaderFilter(admin.SimpleListFilter):
class GroupAdmin(admin.ModelAdmin):
form = GroupAdminForm
list_select_related = ('authgroup',)
ordering = ('name',)
list_display = (
'name',
'_description',
'_properties',
'_member_count',
'has_leader'
'has_leader',
)
list_filter = [
'authgroup__internal',
@@ -129,31 +129,51 @@ class GroupAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if _has_auto_groups:
qs = qs.prefetch_related('managedalliancegroup_set', 'managedcorpgroup_set')
qs = qs.prefetch_related('authgroup__group_leaders').select_related('authgroup')
qs = qs.annotate(
member_count=Count('user', distinct=True),
has_leader_qs = (
AuthGroup.objects.filter(group=OuterRef('pk'), group_leaders__isnull=False)
)
has_leader_groups_qs = (
AuthGroup.objects.filter(
group=OuterRef('pk'), group_leader_groups__isnull=False
)
)
qs = (
qs.select_related('authgroup')
.annotate(member_count=Count('user', distinct=True))
.annotate(has_leader=Exists(has_leader_qs))
.annotate(has_leader_groups=Exists(has_leader_groups_qs))
)
if _has_auto_groups:
is_autogroup_corp = (
Group.objects.filter(
pk=OuterRef('pk'), managedcorpgroup__isnull=False
)
)
is_autogroup_alliance = (
Group.objects.filter(
pk=OuterRef('pk'), managedalliancegroup__isnull=False
)
)
qs = (
qs.annotate(is_autogroup_corp=Exists(is_autogroup_corp))
.annotate(is_autogroup_alliance=Exists(is_autogroup_alliance))
)
return qs
def _description(self, obj):
return obj.authgroup.description
@admin.display(description="Members", ordering="member_count")
@admin.display(description='Members', ordering='member_count')
def _member_count(self, obj):
return obj.member_count
@admin.display(boolean=True)
def has_leader(self, obj):
return obj.authgroup.group_leaders.exists() or obj.authgroup.group_leader_groups.exists()
return obj.has_leader or obj.has_leader_groups
def _properties(self, obj):
properties = list()
if _has_auto_groups and (
obj.managedalliancegroup_set.exists()
or obj.managedcorpgroup_set.exists()
):
if _has_auto_groups and (obj.is_autogroup_corp or obj.is_autogroup_alliance):
properties.append('Auto Group')
elif obj.authgroup.internal:
properties.append('Internal')
@@ -183,6 +203,8 @@ class GroupAdmin(admin.ModelAdmin):
ag_instance = inline_form.save(commit=False)
ag_instance.group = form.instance
ag_instance.save()
if ag_instance.states.exists():
remove_users_not_matching_states_from_group.delay(ag_instance.group.pk)
formset.save()
def get_readonly_fields(self, request, obj=None):

View File

@@ -1,4 +1,4 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth.services.hooks import MenuItemHook, UrlHook
from allianceauth import hooks

View File

@@ -189,6 +189,15 @@ class AuthGroup(models.Model):
| User.objects.filter(groups__in=list(self.group_leader_groups.all()))
)
def remove_users_not_matching_states(self):
"""Remove users not matching defined states from related group."""
states_qs = self.states.all()
if states_qs.exists():
states = list(states_qs)
non_compliant_users = self.group.user_set.exclude(profile__state__in=states)
for user in non_compliant_users:
self.group.user_set.remove(user)
class ReservedGroupName(models.Model):
"""Name that can not be used for groups.

View File

@@ -0,0 +1,10 @@
from celery import shared_task
from django.contrib.auth.models import Group
@shared_task
def remove_users_not_matching_states_from_group(group_pk: int) -> None:
"""Remove users not matching defined states from related group."""
group = Group.objects.get(pk=group_pk)
group.authgroup.remove_users_not_matching_states()

View File

@@ -6,7 +6,7 @@ from django.conf import settings
from django.contrib import admin
from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import User
from django.test import TestCase, RequestFactory, Client
from django.test import TestCase, RequestFactory, Client, override_settings
from allianceauth.authentication.models import CharacterOwnership, State
from allianceauth.eveonline.models import (
@@ -236,60 +236,104 @@ class TestGroupAdmin(TestCase):
self.assertEqual(result, expected)
def test_member_count(self):
expected = 1
obj = self.modeladmin.get_queryset(MockRequest(user=self.user_1))\
.get(pk=self.group_1.pk)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_1.pk)
# when
result = self.modeladmin._member_count(obj)
self.assertEqual(result, expected)
# then
self.assertEqual(result, 1)
def test_has_leader_user(self):
result = self.modeladmin.has_leader(self.group_1)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_1.pk)
# when
result = self.modeladmin.has_leader(obj)
# then
self.assertTrue(result)
def test_has_leader_group(self):
result = self.modeladmin.has_leader(self.group_2)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_2.pk)
# when
result = self.modeladmin.has_leader(obj)
# then
self.assertTrue(result)
def test_properties_1(self):
expected = ['Default']
result = self.modeladmin._properties(self.group_1)
self.assertListEqual(result, expected)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_1.pk)
# when
result = self.modeladmin._properties(obj)
self.assertListEqual(result, ['Default'])
def test_properties_2(self):
expected = ['Internal']
result = self.modeladmin._properties(self.group_2)
self.assertListEqual(result, expected)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_2.pk)
# when
result = self.modeladmin._properties(obj)
self.assertListEqual(result, ['Internal'])
def test_properties_3(self):
expected = ['Hidden']
result = self.modeladmin._properties(self.group_3)
self.assertListEqual(result, expected)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_3.pk)
# when
result = self.modeladmin._properties(obj)
self.assertListEqual(result, ['Hidden'])
def test_properties_4(self):
expected = ['Open']
result = self.modeladmin._properties(self.group_4)
self.assertListEqual(result, expected)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_4.pk)
# when
result = self.modeladmin._properties(obj)
self.assertListEqual(result, ['Open'])
def test_properties_5(self):
expected = ['Public']
result = self.modeladmin._properties(self.group_5)
self.assertListEqual(result, expected)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_5.pk)
# when
result = self.modeladmin._properties(obj)
self.assertListEqual(result, ['Public'])
def test_properties_6(self):
expected = ['Hidden', 'Open', 'Public']
result = self.modeladmin._properties(self.group_6)
self.assertListEqual(result, expected)
# given
request = MockRequest(user=self.user_1)
obj = self.modeladmin.get_queryset(request).get(pk=self.group_6.pk)
# when
result = self.modeladmin._properties(obj)
self.assertListEqual(result, ['Hidden', 'Open', 'Public'])
if _has_auto_groups:
@patch(MODULE_PATH + '._has_auto_groups', True)
def test_properties_7(self):
def test_should_show_autogroup_for_corporation(self):
# given
self._create_autogroups()
expected = ['Auto Group']
my_group = Group.objects\
.filter(managedcorpgroup__isnull=False)\
.first()
result = self.modeladmin._properties(my_group)
self.assertListEqual(result, expected)
request = MockRequest(user=self.user_1)
queryset = self.modeladmin.get_queryset(request)
obj = queryset.filter(managedcorpgroup__isnull=False).first()
# when
result = self.modeladmin._properties(obj)
# then
self.assertListEqual(result, ['Auto Group'])
@patch(MODULE_PATH + '._has_auto_groups', True)
def test_should_show_autogroup_for_alliance(self):
# given
self._create_autogroups()
request = MockRequest(user=self.user_1)
queryset = self.modeladmin.get_queryset(request)
obj = queryset.filter(managedalliancegroup__isnull=False).first()
# when
result = self.modeladmin._properties(obj)
# then
self.assertListEqual(result, ['Auto Group'])
# actions
@@ -539,6 +583,68 @@ class TestGroupAdminChangeFormSuperuserExclusiveEdits(WebTest):
self.assertNotIn(field, form.fields)
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
class TestGroupAdmin2(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.superuser = User.objects.create_superuser("super")
def test_should_remove_users_from_state_groups(self):
# given
user_member = AuthUtils.create_user("Bruce Wayne")
character_member = AuthUtils.add_main_character_2(
user_member,
name="Bruce Wayne",
character_id=1001,
corp_id=2001,
corp_name="Wayne Technologies",
)
user_guest = AuthUtils.create_user("Lex Luthor")
AuthUtils.add_main_character_2(
user_guest,
name="Lex Luthor",
character_id=1011,
corp_id=2011,
corp_name="Luthor Corp",
)
member_state = AuthUtils.get_member_state()
member_state.member_characters.add(character_member)
user_member.refresh_from_db()
user_guest.refresh_from_db()
group = Group.objects.create(name="dummy")
user_member.groups.add(group)
user_guest.groups.add(group)
group.authgroup.states.add(member_state)
self.client.force_login(self.superuser)
# when
response = self.client.post(
f"/admin/groupmanagement/group/{group.pk}/change/",
data={
"name": f"{group.name}",
"authgroup-TOTAL_FORMS": "1",
"authgroup-INITIAL_FORMS": "1",
"authgroup-MIN_NUM_FORMS": "0",
"authgroup-MAX_NUM_FORMS": "1",
"authgroup-0-description": "",
"authgroup-0-states": f"{member_state.pk}",
"authgroup-0-internal": "on",
"authgroup-0-hidden": "on",
"authgroup-0-group": f"{group.pk}",
"authgroup-__prefix__-description": "",
"authgroup-__prefix__-internal": "on",
"authgroup-__prefix__-hidden": "on",
"authgroup-__prefix__-group": f"{group.pk}",
"_save": "Save"
}
)
# then
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/admin/groupmanagement/group/")
self.assertIn(group, user_member.groups.all())
self.assertNotIn(group, user_guest.groups.all())
class TestReservedGroupNameAdmin(TestCase):
@classmethod
def setUpClass(cls):

View File

@@ -232,6 +232,38 @@ class TestAuthGroup(TestCase):
expected = 'Superheros'
self.assertEqual(str(group.authgroup), expected)
def test_should_remove_guests_from_group_when_restricted_to_members_only(self):
# given
user_member = AuthUtils.create_user("Bruce Wayne")
character_member = AuthUtils.add_main_character_2(
user_member,
name="Bruce Wayne",
character_id=1001,
corp_id=2001,
corp_name="Wayne Technologies",
)
user_guest = AuthUtils.create_user("Lex Luthor")
AuthUtils.add_main_character_2(
user_guest,
name="Lex Luthor",
character_id=1011,
corp_id=2011,
corp_name="Luthor Corp",
)
member_state = AuthUtils.get_member_state()
member_state.member_characters.add(character_member)
user_member.refresh_from_db()
user_guest.refresh_from_db()
group = Group.objects.create(name="dummy")
user_member.groups.add(group)
user_guest.groups.add(group)
group.authgroup.states.add(member_state)
# when
group.authgroup.remove_users_not_matching_states()
# then
self.assertIn(group, user_member.groups.all())
self.assertNotIn(group, user_guest.groups.all())
class TestAuthGroupRequestApprovers(TestCase):
def setUp(self) -> None:

View File

@@ -1,50 +1,51 @@
from django.urls import path
from . import views
from django.conf.urls import url
app_name = "groupmanagement"
urlpatterns = [
# groups
path("groups", views.groups_view, name="groups"),
path("group/request/join/<int:group_id>/", views.group_request_add, name="request_add"),
path(
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"
url(r"^groups/$", views.groups_view, name="groups"),
url(r"^group/request/join/(\w+)/$", views.group_request_add, name="request_add"),
url(
r"^group/request/leave/(\w+)/$", views.group_request_leave, name="request_leave"
),
# group management
path("groupmanagement/requests/", views.group_management, name="management"),
path("groupmanagement/membership/", views.group_membership, name="membership"),
path(
"groupmanagement/membership/<int:group_id>/",
url(r"^groupmanagement/requests/$", views.group_management, name="management"),
url(r"^groupmanagement/membership/$", views.group_membership, name="membership"),
url(
r"^groupmanagement/membership/(\w+)/$",
views.group_membership_list,
name="membership",
),
path(
"groupmanagement/membership/<int:group_id>/audit-log/",
url(
r"^groupmanagement/membership/(\w+)/audit-log/$",
views.group_membership_audit,
name="audit_log",
),
path(
"groupmanagement/membership/<int:group_id>/remove/<int:user_id>/",
url(
r"^groupmanagement/membership/(\w+)/remove/(\w+)/$",
views.group_membership_remove,
name="membership_remove",
),
path(
"groupmanagement/request/join/accept/<int:group_request_id>/",
url(
r"^groupmanagement/request/join/accept/(\w+)/$",
views.group_accept_request,
name="accept_request",
),
path(
"groupmanagement/request/join/reject/<int:group_request_id>/",
url(
r"^groupmanagement/request/join/reject/(\w+)/$",
views.group_reject_request,
name="reject_request",
),
path(
"groupmanagement/request/leave/accept/<int:group_request_id>/",
url(
r"^groupmanagement/request/leave/accept/(\w+)/$",
views.group_leave_accept_request,
name="leave_accept_request",
),
path(
"groupmanagement/request/leave/reject/<int:group_request_id>/",
url(
r"^groupmanagement/request/leave/reject/(\w+)/$",
views.group_leave_reject_request,
name="leave_reject_request",
),

View File

@@ -9,7 +9,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Count
from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth.notifications import notify

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.hrapplications.apps.HRApplicationsConfig'

View File

@@ -1,4 +1,4 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth import hooks
from allianceauth.services.hooks import MenuItemHook, UrlHook

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class HRApplicationCommentForm(forms.Form):

View File

@@ -19,8 +19,8 @@
<div cass="text-center">{{ question.help_text }}</div>
{% endif %}
{% for choice in question.choices.all %}
<input type={% if question.multi_select == False %}"radio"{% else %}"checkbox"{% endif %} name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
<input type={% if question.multi_select == False %}"radio"{% else %}"checkbox"{% endif %} name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% empty %}
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea>
{% endfor %}

View File

@@ -181,7 +181,7 @@
<form class="form-signin" role="form" action={% url 'hrapplications:search' %} method="POST">
{% csrf_token %}
{{ search_form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Search" %}</button>
</form>
</div>

View File

@@ -67,7 +67,7 @@
<form class="form-signin" role="form" action={% url 'hrapplications:search' %} method="POST">
{% csrf_token %}
{{ search_form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Search" %}</button>
</form>
</div>

View File

@@ -140,7 +140,7 @@
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ comment_form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Add Comment" %}</button>
</form>
</div>

View File

@@ -1,31 +1,31 @@
from django.urls import path
from django.conf.urls import url
from . import views
app_name = 'hrapplications'
urlpatterns = [
path('', views.hr_application_management_view,
url(r'^$', views.hr_application_management_view,
name="index"),
path('create/', views.hr_application_create_view,
url(r'^create/$', views.hr_application_create_view,
name="create_view"),
path('create/<int:form_id>/', views.hr_application_create_view,
url(r'^create/(\d+)', views.hr_application_create_view,
name="create_view"),
path('remove/<int:app_id>/', views.hr_application_remove,
url(r'^remove/(\w+)', views.hr_application_remove,
name="remove"),
path('view/<int:app_id>/', views.hr_application_view,
url(r'^view/(\w+)', views.hr_application_view,
name="view"),
path('personal/view/<int:app_id>/', views.hr_application_personal_view,
url(r'^personal/view/(\w+)', views.hr_application_personal_view,
name="personal_view"),
path('personal/removal/<int:app_id>/',
url(r'^personal/removal/(\w+)',
views.hr_application_personal_removal,
name="personal_removal"),
path('approve/<int:app_id>/', views.hr_application_approve,
url(r'^approve/(\w+)', views.hr_application_approve,
name="approve"),
path('reject/<int:app_id>/', views.hr_application_reject,
url(r'^reject/(\w+)', views.hr_application_reject,
name="reject"),
path('search/', views.hr_application_search,
url(r'^search/', views.hr_application_search,
name="search"),
path('mark_in_progress/<int:app_id>/', views.hr_application_mark_in_progress,
url(r'^mark_in_progress/(\w+)', views.hr_application_mark_in_progress,
name="mark_in_progress"),
]
]

View File

@@ -1,16 +1,16 @@
from django.urls import path
from django.conf.urls import url
from . import views
app_name = 'notifications'
# Notifications
urlpatterns = [
path('remove_notifications/<int:notif_id>/', views.remove_notification, name='remove'),
path('notifications/mark_all_read/', views.mark_all_read, name='mark_all_read'),
path('notifications/delete_all_read/', views.delete_all_read, name='delete_all_read'),
path('notifications/', views.notification_list, name='list'),
path('notifications/<int:notif_id>/', views.notification_view, name='view'),
path(
'user_notifications_count/<int:user_pk>/',
url(r'^remove_notifications/(\w+)/$', views.remove_notification, name='remove'),
url(r'^notifications/mark_all_read/$', views.mark_all_read, name='mark_all_read'),
url(r'^notifications/delete_all_read/$', views.delete_all_read, name='delete_all_read'),
url(r'^notifications/$', views.notification_list, name='list'),
url(r'^notifications/(\w+)/$', views.notification_view, name='view'),
url(
r'^user_notifications_count/(?P<user_pk>\d+)/$',
views.user_notifications_count,
name='user_notifications_count'
),

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.optimer.apps.OptimerConfig'

View File

@@ -1,5 +1,5 @@
from allianceauth.services.hooks import MenuItemHook, UrlHook
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth import hooks
from . import urls

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from allianceauth.optimer.form_widgets import DataListWidget

View File

@@ -19,7 +19,7 @@
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Create Fleet Operation" %}</button>
</form>
</div>

View File

@@ -21,7 +21,7 @@
<b>{% translate "Current Eve Time:" %} </b>
</div>
<strong class="label label-info text-left" id="current-time"></strong>
<br>
<br />
</div>
<h4><b>{% translate "Next Fleet Operations" %}</b></h4>

View File

@@ -24,7 +24,7 @@
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Update Fleet Operation" %}
</button>
</form>

View File

@@ -1,12 +1,12 @@
from django.urls import path
from django.conf.urls import url
from . import views
app_name = 'optimer'
urlpatterns = [
path('', views.optimer_view, name='view'),
path('add/', views.add_optimer_view, name='add'),
path('<int:optimer_id>/remove/', views.remove_optimer, name='remove'),
path('<int:optimer_id>/edit/', views.edit_optimer, name='edit'),
]
url(r'^$', views.optimer_view, name='view'),
url(r'^add$', views.add_optimer_view, name='add'),
url(r'^(\w+)/remove$', views.remove_optimer, name='remove'),
url(r'^(\w+)/edit$', views.edit_optimer, name='edit'),
]

View File

@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import permission_required
from django.shortcuts import get_object_or_404
from django.shortcuts import render, redirect
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from .form import OpForm
from .models import OpTimer, OpTimerType

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.permissions_tool.apps.PermissionsToolConfig'

View File

@@ -1,12 +1,11 @@
from django.urls import re_path
from django.urls import path
from django.conf.urls import url
from . import views
app_name = 'permissions_tool'
urlpatterns = [
path('overview/', views.permissions_overview, name='overview'),
re_path(r'^audit/(?P<app_label>[\w\-_]+)/(?P<model>[\w\-_]+)/(?P<codename>[\w\-_]+)/$', views.permissions_audit,
url(r'^overview/$', views.permissions_overview, name='overview'),
url(r'^audit/(?P<app_label>[\w\-_]+)/(?P<model>[\w\-_]+)/(?P<codename>[\w\-_]+)/$', views.permissions_audit,
name='audit'),
]

View File

@@ -68,7 +68,6 @@ BASE_DIR = os.path.dirname(PROJECT_DIR)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'allianceauth.authentication.middleware.UserSettingsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -154,6 +153,8 @@ TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
@@ -171,8 +172,11 @@ MESSAGE_TAGS = {
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1" # change the 1 here to change the database used
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "localhost:6379",
"OPTIONS": {
"DB": 1,
}
}
}

View File

@@ -61,13 +61,6 @@ EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = ''
# Cache compression can help on bigger auths where ram starts to become an issue.
# Uncomment the following 3 lines to enable.
#CACHES["default"]["OPTIONS"] = {
# "COMPRESSOR": "django_redis.compressors.lzma.LzmaCompressor",
#}
#######################################
# Add any custom settings below here. #
#######################################

View File

@@ -1,9 +1,8 @@
from django.conf.urls import include
from django.conf.urls import include, url
from allianceauth import urls
from django.urls import re_path
urlpatterns = [
re_path(r'', include(urls)),
url(r'', include(urls)),
]
handler500 = 'allianceauth.views.Generic500Redirect'

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.apps.ServicesConfig'

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class FleetFormatterForm(forms.Form):

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import re_path
from django.conf.urls import include, url
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
from django.utils.functional import cached_property
@@ -10,6 +9,7 @@ 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
@@ -157,7 +157,7 @@ class MenuItemHook:
class UrlHook:
def __init__(self, urls, namespace, base_url):
self.include_pattern = re_path(base_url, include(urls, namespace=namespace))
self.include_pattern = url(base_url, include(urls, namespace=namespace))
class NameFormatter:

View File

@@ -1 +1,3 @@
default_app_config = 'allianceauth.services.modules.discord.apps.DiscordServiceConfig' # noqa
__title__ = 'Discord Service'

View File

@@ -8,7 +8,7 @@ from uuid import uuid1
from redis import Redis
import requests
from django_redis import get_redis_connection
from django.core.cache import caches
from allianceauth import __title__ as AUTH_TITLE, __url__, __version__
@@ -103,7 +103,8 @@ class DiscordClient:
self._access_token = str(access_token)
self._is_rate_limited = bool(is_rate_limited)
if not redis:
self._redis = get_redis_connection("default")
default_cache = caches['default']
self._redis = default_cache.get_master_client()
if not isinstance(self._redis, Redis):
raise RuntimeError(
'This class requires a Redis client, but none was provided '

View File

@@ -85,24 +85,28 @@ class TestBasicsAndHelpers(TestCase):
client = DiscordClient(TEST_BOT_TOKEN, mock_redis, is_rate_limited=True)
self.assertTrue(client.is_rate_limited)
@patch(MODULE_PATH + '.get_redis_connection')
@patch(MODULE_PATH + '.caches')
def test_use_default_redis_if_none_provided(self, mock_caches):
my_redis = MagicMock(spec=Redis)
mock_caches.return_value = my_redis
mock_default_cache = MagicMock(**{'get_master_client.return_value': my_redis})
my_dict = {'default': mock_default_cache}
mock_caches.__getitem__.side_effect = my_dict.__getitem__
client = DiscordClient(TEST_BOT_TOKEN)
self.assertTrue(mock_caches.called)
self.assertTrue(mock_default_cache.get_master_client.called)
self.assertEqual(client._redis, my_redis)
@patch(MODULE_PATH + '.get_redis_connection')
@patch(MODULE_PATH + '.caches')
def test_raise_exception_if_default_cache_is_not_redis(self, mock_caches):
my_redis = MagicMock()
mock_caches.return_value = my_redis
mock_default_cache = MagicMock(**{'get_master_client.return_value': my_redis})
my_dict = {'default': mock_default_cache}
mock_caches.__getitem__.side_effect = my_dict.__getitem__
with self.assertRaises(RuntimeError):
DiscordClient(TEST_BOT_TOKEN)
self.assertTrue(mock_caches.called)
self.assertTrue(mock_default_cache.get_master_client.called)
@requests_mock.Mocker()

View File

@@ -14,7 +14,7 @@ from requests.exceptions import HTTPError
import requests_mock
from django.contrib.auth.models import Group, User
from django_redis import get_redis_connection
from django.core.cache import caches
from django.shortcuts import reverse
from django.test import TransactionTestCase, TestCase
from django.test.utils import override_settings
@@ -87,7 +87,8 @@ remove_guild_member_request = DiscordRequest(
def clear_cache():
redis = get_redis_connection('default')
default_cache = caches['default']
redis = default_cache.get_master_client()
redis.flushall()
logger.info('Cache flushed')

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,13 +6,13 @@ app_name = 'discord'
module_urls = [
# Discord Service Control
path('activate/', views.activate_discord, name='activate'),
path('deactivate/', views.deactivate_discord, name='deactivate'),
path('reset/', views.reset_discord, name='reset'),
path('callback/', views.discord_callback, name='callback'),
path('add_bot/', views.discord_add_bot, name='add_bot'),
url(r'^activate/$', views.activate_discord, name='activate'),
url(r'^deactivate/$', views.deactivate_discord, name='deactivate'),
url(r'^reset/$', views.reset_discord, name='reset'),
url(r'^callback/$', views.discord_callback, name='callback'),
url(r'^add_bot/$', views.discord_add_bot, name='add_bot'),
]
urlpatterns = [
path('discord/', include((module_urls, app_name), namespace=app_name))
url(r'^discord/', include((module_urls, app_name), namespace=app_name))
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.discourse.apps.DiscourseServiceConfig'

View File

@@ -1,8 +1,8 @@
from django.urls import path
from django.conf.urls import url
from . import views
urlpatterns = [
# Discourse Service Control
path('discourse/sso', views.discourse_sso, name='auth_discourse_sso'),
url(r'^discourse/sso$', views.discourse_sso, name='auth_discourse_sso'),
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.example.apps.ExampleServiceConfig'

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
app_name = 'example'
@@ -8,5 +7,5 @@ module_urls = [
]
urlpatterns = [
path('example/', include((module_urls, app_name), namespace=app_name)),
url(r'^example/', include((module_urls, app_name), namespace=app_name)),
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.ips4.apps.Ips4ServiceConfig'

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,12 +6,12 @@ app_name = 'ips4'
module_urls = [
# IPS4 Service Control
path('activate/', views.activate_ips4, name='activate'),
path('deactivate/', views.deactivate_ips4, name='deactivate'),
path('reset_password/', views.reset_ips4_password, name='reset_password'),
path('set_password/', views.set_ips4_password, name='set_password'),
url(r'^activate/$', views.activate_ips4, name='activate'),
url(r'^deactivate/$', views.deactivate_ips4, name='deactivate'),
url(r'^reset_password/$', views.reset_ips4_password, name='reset_password'),
url(r'^set_password/$', views.set_ips4_password, name='set_password'),
]
urlpatterns = [
path('ips4/', include((module_urls, app_name), namespace=app_name))
url(r'^ips4/', include((module_urls, app_name), namespace=app_name))
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.mumble.apps.MumbleServiceConfig'

View File

@@ -62,12 +62,6 @@ class MumbleManager(models.Manager):
class MumbleUser(AbstractServiceModel):
user = models.OneToOneField(
'auth.User',
primary_key=True,
on_delete=models.CASCADE,
related_name='mumble'
)
username = models.CharField(max_length=254, unique=True)
pwhash = models.CharField(max_length=90)
hashfn = models.CharField(max_length=20, default='sha1')

View File

@@ -3,7 +3,7 @@
<td class="text-center">{{ username }}</td>
<td class="text-center"><a href="mumble://{{ service_url }}">{{ service_url }}</a></td>
<td class="text-center">
{% if username == "" %}
{% ifequal username "" %}
<a href="{% url urls.auth_activate %}" title="Activate" class="btn btn-warning">
<span class="glyphicon glyphicon-ok"></span>
</a>
@@ -20,6 +20,6 @@
<a href="mumble://{{ connect_url }}" class="btn btn-success" title="Connect">
<span class="glyphicon glyphicon-arrow-right"></span>
</a>
{% endif %}
{% endifequal %}
</td>
</tr>

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,12 +6,12 @@ app_name = 'mumble'
module_urls = [
# Mumble service control
path('activate/', views.CreateAccountMumbleView.as_view(), name='activate'),
path('deactivate/', views.DeleteMumbleView.as_view(), name='deactivate'),
path('reset_password/', views.ResetPasswordMumbleView.as_view(), name='reset_password'),
path('set_password/', views.SetPasswordMumbleView.as_view(), name='set_password'),
url(r'^activate/$', views.CreateAccountMumbleView.as_view(), name='activate'),
url(r'^deactivate/$', views.DeleteMumbleView.as_view(), name='deactivate'),
url(r'^reset_password/$', views.ResetPasswordMumbleView.as_view(), name='reset_password'),
url(r'^set_password/$', views.SetPasswordMumbleView.as_view(), name='set_password'),
]
urlpatterns = [
path('mumble/', include((module_urls, app_name), namespace=app_name))
url(r'^mumble/', include((module_urls, app_name), namespace=app_name))
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.openfire.apps.OpenfireServiceConfig'

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class JabberBroadcastForm(forms.Form):

View File

@@ -3,7 +3,7 @@ import random
import string
from urllib.parse import urlparse
import slixmpp
import sleekxmpp
from django.conf import settings
from ofrestapi.users import Users as ofUsers
from ofrestapi import exception
@@ -172,13 +172,13 @@ class OpenfireManager:
raise PingBotException("Unable to connect to jabber server.")
class PingBot(slixmpp.ClientXMPP):
class PingBot(sleekxmpp.ClientXMPP):
"""
A copy-paste of the example client bot from
http://sleekxmpp.com/getting_started/sendlogout.html
"""
def __init__(self, jid, password, recipient, message):
slixmpp.ClientXMPP.__init__(self, jid, password)
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.reconnect_max_attempts = 5
self.auto_reconnect = False

View File

@@ -19,7 +19,7 @@
<form class="form-signin" role="form" action="" method="POST" onsubmit="submitbutton.disabled = true; return true;">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" name="submitbutton" type="submit">{% translate "Broadcast" %}
</button>
</form>

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,13 +6,13 @@ app_name = 'openfire'
module_urls = [
# Jabber Service Control
path('activate/', views.activate_jabber, name='activate'),
path('deactivate/', views.deactivate_jabber, name='deactivate'),
path('reset_password/', views.reset_jabber_password, name='reset_password'),
path('set_password/', views.set_jabber_password, name='set_password'),
path('broadcast/', views.jabber_broadcast_view, name='broadcast'),
url(r'^activate/$', views.activate_jabber, name='activate'),
url(r'^deactivate/$', views.deactivate_jabber, name='deactivate'),
url(r'^reset_password/$', views.reset_jabber_password, name='reset_password'),
url(r'^set_password/$', views.set_jabber_password, name='set_password'),
url(r'^broadcast/$', views.jabber_broadcast_view, name='broadcast'),
]
urlpatterns = [
path('openfire/', include((module_urls, app_name), namespace=app_name)),
url(r'^openfire/', include((module_urls, app_name), namespace=app_name)),
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.phpbb3.apps.Phpbb3ServiceConfig'

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,12 +6,12 @@ app_name = 'phpbb3'
module_urls = [
# Forum Service Control
path('activate/', views.activate_forum, name='activate'),
path('deactivate/', views.deactivate_forum, name='deactivate'),
path('reset_password/', views.reset_forum_password, name='reset_password'),
path('set_password/', views.set_forum_password, name='set_password'),
url(r'^activate/$', views.activate_forum, name='activate'),
url(r'^deactivate/$', views.deactivate_forum, name='deactivate'),
url(r'^reset_password/$', views.reset_forum_password, name='reset_password'),
url(r'^set_password/$', views.set_forum_password, name='set_password'),
]
urlpatterns = [
path('phpbb3/', include((module_urls, app_name), namespace=app_name))
url(r'^phpbb3/', include((module_urls, app_name), namespace=app_name))
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.smf.apps.SmfServiceConfig'

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,12 +6,12 @@ app_name = 'smf'
module_urls = [
# SMF Service Control
path('activate/', views.activate_smf, name='activate'),
path('deactivate/', views.deactivate_smf, name='deactivate'),
path('reset_password/', views.reset_smf_password, name='reset_password'),
path('set_password/', views.set_smf_password, name='set_password'),
url(r'^activate/$', views.activate_smf, name='activate'),
url(r'^deactivate/$', views.deactivate_smf, name='deactivate'),
url(r'^reset_password/$', views.reset_smf_password, name='reset_password'),
url(r'^set_password/$', views.set_smf_password, name='set_password'),
]
urlpatterns = [
path('smf/', include((module_urls, app_name), namespace=app_name)),
url(r'^smf/', include((module_urls, app_name), namespace=app_name)),
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.teamspeak3.apps.Teamspeak3ServiceConfig'

View File

@@ -1,5 +1,5 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from .manager import Teamspeak3Manager

View File

@@ -5,7 +5,7 @@
<td class="text-center">{{ authinfo.teamspeak3_uid }}</td>
<td class="text-center"><a href="ts3server://{{ TEAMSPEAK3_PUBLIC_URL }}">{{ TEAMSPEAK3_PUBLIC_URL }}</a></td>
<td class="text-center">
{% if authinfo.teamspeak3_uid == "" %}
{% ifequal authinfo.teamspeak3_uid "" %}
<a href="{% url 'teamspeak3:activate' %}" title="Activate" class="btn btn-warning">
<span class="glyphicon glyphicon-ok"></span>
</a>
@@ -22,6 +22,6 @@
<a href="ts3server://{{ TEAMSPEAK3_PUBLIC_URL }}?nickname={{ authinfo.teamspeak3_uid }}" title="Connect" class="btn btn-success">
<span class="glyphicon glyphicon-arrow-right"></span>
</a>
{% endif %}
{% endifequal %}
</td>
</tr>

View File

@@ -12,11 +12,11 @@
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<a href="ts3server://{{ public_url }}?token={{ authinfo.teamspeak3_perm_key }}&nickname={{ authinfo.teamspeak3_uid }}" class="btn btn-primary btn-block btn-lg" title="Join">{% translate "Join Server" %}</a>
<br>
<br/>
<form class="form-signin" role="form" action="{% url 'teamspeak3:verify' %}" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Continue" %}</button>
</form>
</div>

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,19 +6,19 @@ app_name = 'teamspeak3'
module_urls = [
# Teamspeak3 service control
path('activate/', views.activate_teamspeak3, name='activate'),
path('deactivate/', views.deactivate_teamspeak3, name='deactivate'),
path('reset_perm/', views.reset_teamspeak3_perm, name='reset_perm'),
path(
'admin_update_ts3_groups/',
url(r'^activate/$', views.activate_teamspeak3, name='activate'),
url(r'^deactivate/$', views.deactivate_teamspeak3, name='deactivate'),
url(r'^reset_perm/$', views.reset_teamspeak3_perm, name='reset_perm'),
url(
r'^admin_update_ts3_groups/$',
views.admin_update_ts3_groups,
name='admin_update_ts3_groups'
),
# Teamspeak Urls
path('verify/', views.verify_teamspeak3, name='verify'),
url(r'^verify/$', views.verify_teamspeak3, name='verify'),
]
urlpatterns = [
path('teamspeak3/', include((module_urls, app_name), namespace=app_name)),
url(r'^teamspeak3/', include((module_urls, app_name), namespace=app_name)),
]

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.services.modules.xenforo.apps.XenforoServiceConfig'

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.conf.urls import url, include
from . import views
@@ -7,12 +6,12 @@ app_name = 'xenforo'
module_urls = [
# XenForo service control
path('activate/', views.activate_xenforo_forum, name='activate'),
path('deactivate/', views.deactivate_xenforo_forum, name='deactivate'),
path('reset_password/', views.reset_xenforo_password, name='reset_password'),
path('set_password/', views.set_xenforo_password, name='set_password'),
url(r'^activate/$', views.activate_xenforo_forum, name='activate'),
url(r'^deactivate/$', views.deactivate_xenforo_forum, name='deactivate'),
url(r'^reset_password/$', views.reset_xenforo_password, name='reset_password'),
url(r'^set_password/$', views.set_xenforo_password, name='set_password'),
]
urlpatterns = [
path('xenforo/', include((module_urls, app_name), namespace=app_name)),
url(r'^xenforo/', include((module_urls, app_name), namespace=app_name)),
]

View File

@@ -1,4 +1,5 @@
import logging
from functools import partial
from django.contrib.auth.models import User, Group, Permission
from django.core.exceptions import ObjectDoesNotExist
@@ -8,7 +9,7 @@ from django.db.models.signals import pre_delete
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .hooks import ServicesHook
from .tasks import disable_user
from .tasks import disable_user, update_groups_for_user
from allianceauth.authentication.models import State, UserProfile
from allianceauth.authentication.signals import state_changed
@@ -19,21 +20,27 @@ logger = logging.getLogger(__name__)
@receiver(m2m_changed, sender=User.groups.through)
def m2m_changed_user_groups(sender, instance, action, *args, **kwargs):
logger.debug(f"Received m2m_changed from {instance} groups with action {action}")
def trigger_service_group_update():
logger.debug("Triggering service group update for %s" % instance)
# Iterate through Service hooks
for svc in ServicesHook.get_services():
try:
svc.validate_user(instance)
svc.update_groups(instance)
except:
logger.exception(f'Exception running update_groups for services module {svc} on user {instance}')
if instance.pk and (action == "post_add" or action == "post_remove" or action == "post_clear"):
logger.debug("Waiting for commit to trigger service group update for %s" % instance)
transaction.on_commit(trigger_service_group_update)
logger.debug(
"%s: Received m2m_changed from groups with action %s", instance, action
)
if instance.pk and (
action == "post_add" or action == "post_remove" or action == "post_clear"
):
if isinstance(instance, User):
logger.debug(
"Waiting for commit to trigger service group update for %s", instance
)
transaction.on_commit(partial(update_groups_for_user.delay, instance.pk))
elif (
isinstance(instance, Group)
and kwargs.get("model") is User
and "pk_set" in kwargs
):
for user_pk in kwargs["pk_set"]:
logger.debug(
"%s: Waiting for commit to trigger service group update for user", user_pk
)
transaction.on_commit(partial(update_groups_for_user.delay, user_pk))
@receiver(m2m_changed, sender=User.user_permissions.through)

Some files were not shown because too many files have changed in this diff Show More