Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
321af5ec87 | ||
|
|
9ccf340b3d | ||
|
|
d7dcacb899 | ||
|
|
8addd483c2 | ||
|
|
4d27e5ac9b | ||
|
|
31290f6e80 | ||
|
|
c31cc4dbee | ||
|
|
cc1f94cf61 | ||
|
|
a9132b8d50 | ||
|
|
7b4a9891aa | ||
|
|
dcaaf38ecc | ||
|
|
653a8aa850 | ||
|
|
274af11385 | ||
|
|
170b246901 | ||
|
|
5250432ce3 | ||
|
|
53d6e973eb | ||
|
|
c9bdd62d53 | ||
|
|
7eb98af528 | ||
|
|
385e3e21b3 | ||
|
|
127ec63d76 | ||
|
|
4988b5f260 | ||
|
|
f28a50f92c | ||
|
|
e8efe8e609 | ||
|
|
d7e7457bc5 | ||
|
|
daff927811 | ||
|
|
8861ec0a61 | ||
|
|
bd4321f61a | ||
|
|
d831482fe0 | ||
|
|
9ea79ea389 | ||
|
|
b6fdf840ef | ||
|
|
73f262ce4b | ||
|
|
f63434adc3 | ||
|
|
42948386ec | ||
|
|
32e0621b0a | ||
|
|
78e05b84e9 | ||
|
|
76ebd21163 | ||
|
|
38aaf545c6 | ||
|
|
527d7ef671 | ||
|
|
e54b80e061 | ||
|
|
27f95a8b2c | ||
|
|
a1e8903128 | ||
|
|
b00ac2aef4 | ||
|
|
8865d15ed9 | ||
|
|
fc3d4b7f33 | ||
|
|
934cc44540 | ||
|
|
106de3dd4c | ||
|
|
9b55cfcbe3 | ||
|
|
8137f1023a | ||
|
|
d670e33b6f | ||
|
|
3d3bb8fc94 | ||
|
|
9c880eae8a | ||
|
|
54a71630f1 | ||
|
|
923a8453cc | ||
|
|
00447ca819 | ||
|
|
81af610c11 |
6
.gitignore
vendored
@@ -69,3 +69,9 @@ celerybeat-schedule
|
||||
|
||||
#gitlab configs
|
||||
.gitlab/
|
||||
|
||||
#transifex
|
||||
.tx/
|
||||
|
||||
#other
|
||||
.flake8
|
||||
|
||||
@@ -6,11 +6,6 @@ before_script:
|
||||
- python -V
|
||||
- pip install wheel tox
|
||||
|
||||
test-3.5-core:
|
||||
image: python:3.5-buster
|
||||
script:
|
||||
- tox -e py35-core
|
||||
|
||||
test-3.6-core:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
@@ -26,11 +21,6 @@ test-3.8-core:
|
||||
script:
|
||||
- tox -e py38-core
|
||||
|
||||
test-3.5-all:
|
||||
image: python:3.5-buster
|
||||
script:
|
||||
- tox -e py35-all
|
||||
|
||||
test-3.6-all:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
|
||||
27
.readthedocs.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# 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:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- testing
|
||||
system_packages: true
|
||||
18
README.md
@@ -11,32 +11,34 @@
|
||||
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
## Contens
|
||||
## Content
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Documentation](http://allianceauth.rtfd.io)
|
||||
- [Support](#support)
|
||||
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
|
||||
- [Devloper Team](#developer-team)
|
||||
- [Developer Team](#developer-team)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Overview
|
||||
|
||||
Alliance Auth (AA) is a web application that helps Eve Online organizations efficiently manage access to their applications and services.
|
||||
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
|
||||
|
||||
Main features:
|
||||
|
||||
- Automatically grants or revokes user access to external applications / services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/groups/)
|
||||
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
|
||||
|
||||
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
|
||||
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/installation/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
|
||||
- Includes a set of web apps called ["plug-in apps"](https://allianceauth.readthedocs.io/en/latest/features/) which add many useful functions: fleet schedule, timer board, SRP request management, fleet activity tracker and character application management
|
||||
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
|
||||
|
||||
- Can be easily extended with new services and plugin-apps. Many additional services and plugin-apps are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [offical documentation](http://allianceauth.rtfd.io).
|
||||
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
|
||||
## Screenshot
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '2.6.1'
|
||||
__version__ = '2.6.4'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||
|
||||
@@ -506,7 +506,7 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||
'character',
|
||||
)
|
||||
search_fields = (
|
||||
'user__user',
|
||||
'user__username',
|
||||
'character__character_name',
|
||||
'character__corporation_name',
|
||||
'character__alliance_name'
|
||||
|
||||
@@ -94,12 +94,12 @@
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-success" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Groups" %}</h3>
|
||||
<h3 class="panel-title">{% trans "Group Memberships" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
|
||||
<table class="table table-aa">
|
||||
{% for group in user.groups.all %}
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
@@ -128,16 +128,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ownership in request.user.character_ownerships.all %}
|
||||
{% with ownership.character as char %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this email because you requested a password reset for your
|
||||
{% blocktrans trimmed %}You're receiving this email because you requested a password reset for your
|
||||
user account.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
|
||||
@@ -2,10 +2,17 @@ from django.urls import reverse
|
||||
|
||||
|
||||
def get_admin_change_view_url(obj: object) -> str:
|
||||
"""returns URL to admin change view for given object"""
|
||||
return reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label,
|
||||
type(obj).__name__.lower()
|
||||
obj._meta.app_label, type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
|
||||
def get_admin_search_url(ModelClass: type) -> str:
|
||||
"""returns URL to search URL for model of given object"""
|
||||
return '{}{}/'.format(
|
||||
reverse('admin:app_list', args=(ModelClass._meta.app_label,)),
|
||||
ModelClass.__name__.lower()
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
from urllib.parse import quote
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
@@ -6,8 +7,9 @@ from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.auth.models import User as BaseUser, Group
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership, State, \
|
||||
OwnershipRecord
|
||||
from allianceauth.authentication.models import (
|
||||
CharacterOwnership, State, OwnershipRecord
|
||||
)
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
)
|
||||
@@ -28,7 +30,7 @@ from ..admin import (
|
||||
user_username,
|
||||
update_main_character_model
|
||||
)
|
||||
from . import get_admin_change_view_url
|
||||
from . import get_admin_change_view_url, get_admin_search_url
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
@@ -175,6 +177,17 @@ def create_test_data():
|
||||
return user_1, user_2, user_3, group_1, group_2
|
||||
|
||||
|
||||
def make_generic_search_request(ModelClass: type, search_term: str):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
return c.get(
|
||||
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
|
||||
)
|
||||
|
||||
|
||||
class TestCharacterOwnershipAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
@@ -197,6 +210,14 @@ class TestCharacterOwnershipAdmin(TestCase):
|
||||
response = c.get(get_admin_change_view_url(ownership))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = CharacterOwnership.objects\
|
||||
.filter(user=self.user_1)\
|
||||
.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestOwnershipRecordAdmin(TestCase):
|
||||
|
||||
@@ -222,6 +243,12 @@ class TestOwnershipRecordAdmin(TestCase):
|
||||
response = c.get(get_admin_change_view_url(ownership_record))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = OwnershipRecord.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestStateAdmin(TestCase):
|
||||
|
||||
@@ -250,6 +277,11 @@ class TestStateAdmin(TestCase):
|
||||
response = c.get(get_admin_change_view_url(member_state))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = State.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.name)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
class TestUserAdmin(TestCase):
|
||||
|
||||
@@ -541,3 +573,9 @@ class TestUserAdmin(TestCase):
|
||||
c.login(username='superuser', password='secret')
|
||||
response = c.get(get_admin_change_view_url(self.user_1))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = User.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
@@ -7,11 +7,21 @@ from . import views
|
||||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', login_required(TemplateView.as_view(template_name='authentication/dashboard.html')),),
|
||||
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='login'),
|
||||
url(r'^account/characters/main/$', views.main_character_change, name='change_main_character'),
|
||||
url(r'^account/characters/add/$', views.add_character, name='add_character'),
|
||||
url(r'^help/$', login_required(TemplateView.as_view(template_name='allianceauth/help.html')), name='help'),
|
||||
url(r'^dashboard/$',
|
||||
login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'),
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(
|
||||
r'^account/login/$',
|
||||
TemplateView.as_view(template_name='public/login.html'),
|
||||
name='login'
|
||||
),
|
||||
url(
|
||||
r'^account/characters/main/$',
|
||||
views.main_character_change,
|
||||
name='change_main_character'
|
||||
),
|
||||
url(
|
||||
r'^account/characters/add/$',
|
||||
views.add_character,
|
||||
name='add_character'
|
||||
),
|
||||
url(r'^dashboard/$', views.dashboard, name='dashboard'),
|
||||
]
|
||||
|
||||
@@ -7,20 +7,58 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import signing
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from esi.decorators import token_required
|
||||
from esi.models import Token
|
||||
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
|
||||
ActivationView as BaseActivationView, REGISTRATION_SALT
|
||||
|
||||
from registration.backends.hmac.views import (
|
||||
RegistrationView as BaseRegistrationView,
|
||||
ActivationView as BaseActivationView,
|
||||
REGISTRATION_SALT
|
||||
)
|
||||
from registration.signals import user_registered
|
||||
|
||||
from .models import CharacterOwnership
|
||||
from .forms import RegistrationForm
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
from allianceauth.eveonline.autogroups.models import *
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
groups = request.user.groups.all()
|
||||
if _has_auto_groups:
|
||||
groups = groups\
|
||||
.filter(managedalliancegroup__isnull=True)\
|
||||
.filter(managedcorpgroup__isnull=True)
|
||||
groups = groups.order_by('name')
|
||||
characters = EveCharacter.objects\
|
||||
.filter(character_ownership__user=request.user)\
|
||||
.select_related()\
|
||||
.order_by('character_name')
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
'characters': characters
|
||||
}
|
||||
return render(request, 'authentication/dashboard.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def main_character_change(request, token):
|
||||
@@ -31,7 +69,10 @@ def main_character_change(request, token):
|
||||
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
else:
|
||||
messages.error(request, 'Cannot change main character to %(char)s: character owned by a different account.' % ({'char': token.character_name}))
|
||||
messages.error(
|
||||
request,
|
||||
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
|
||||
)
|
||||
co = None
|
||||
if co:
|
||||
request.user.profile.main_character = co.character
|
||||
|
||||
@@ -7,6 +7,7 @@ from .models import EveCorporationInfo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TASK_PRIORITY = 7
|
||||
|
||||
@shared_task
|
||||
def update_corp(corp_id):
|
||||
@@ -27,11 +28,12 @@ def update_character(character_id):
|
||||
def run_model_update():
|
||||
# update existing corp models
|
||||
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
|
||||
update_corp.delay(corp['corporation_id'])
|
||||
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
|
||||
|
||||
# update existing alliance models
|
||||
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
|
||||
update_alliance.delay(alliance['alliance_id'])
|
||||
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
|
||||
|
||||
#update existing character models
|
||||
for character in EveCharacter.objects.all().values('character_id'):
|
||||
update_character.delay(character['character_id'])
|
||||
update_character.apply_async(args=[character['character_id']], priority=TASK_PRIORITY)
|
||||
|
||||
@@ -80,28 +80,28 @@ class TestTasks(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='character.corp.id',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='c.c.t', # max 5 chars
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
run_model_update()
|
||||
|
||||
self.assertEqual(mock_update_corp.delay.call_count, 1)
|
||||
self.assertEqual(mock_update_corp.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_corp.delay.call_args[0][0]),
|
||||
int(mock_update_corp.apply_async.call_args[1]['args'][0]),
|
||||
2345
|
||||
)
|
||||
|
||||
self.assertEqual(mock_update_alliance.delay.call_count, 1)
|
||||
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_alliance.delay.call_args[0][0]),
|
||||
int(mock_update_alliance.apply_async.call_args[1]['args'][0]),
|
||||
3456
|
||||
)
|
||||
|
||||
self.assertEqual(mock_update_character.delay.call_count, 1)
|
||||
self.assertEqual(mock_update_character.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_character.delay.call_args[0][0]),
|
||||
int(mock_update_character.apply_async.call_args[1]['args'][0]),
|
||||
1234
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} link{{ n_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
<h2>
|
||||
{% blocktrans count links=n_fats trimmed %}
|
||||
{{ user }} has collected one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has collected {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
</h2>
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-2 text-center">{% trans "Ship" %}</th>
|
||||
@@ -29,7 +35,13 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} link{{ n_created_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
<h2>
|
||||
{% blocktrans count links=n_created_fats trimmed %}
|
||||
{{ user }} has created one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has created {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
|
||||
@@ -1,27 +1,53 @@
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db.models import Q
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.db.models import Q, QuerySet
|
||||
|
||||
from allianceauth.authentication.models import State
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GroupManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_joinable_groups_for_user(
|
||||
cls, user: User, include_hidden = True
|
||||
) -> QuerySet:
|
||||
"""get groups a user could join incl. groups already joined"""
|
||||
groups_qs = cls.get_joinable_groups(user.profile.state)
|
||||
|
||||
if not user.has_perm('groupmanagement.request_groups'):
|
||||
groups_qs = groups_qs.filter(authgroup__public=True)
|
||||
|
||||
if not include_hidden:
|
||||
groups_qs = groups_qs.filter(authgroup__hidden=False)
|
||||
|
||||
return groups_qs
|
||||
|
||||
@staticmethod
|
||||
def get_joinable_groups(state):
|
||||
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)\
|
||||
def get_joinable_groups(state: State) -> QuerySet:
|
||||
"""get groups that can be joined by user with given state"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)\
|
||||
.filter(Q(authgroup__states=state) | Q(authgroup__states=None))
|
||||
|
||||
@staticmethod
|
||||
def get_all_non_internal_groups():
|
||||
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)
|
||||
def get_all_non_internal_groups() -> QuerySet:
|
||||
"""get groups that are not internal"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)
|
||||
|
||||
@staticmethod
|
||||
def get_group_leaders_groups(user):
|
||||
def get_group_leaders_groups(user: User):
|
||||
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \
|
||||
Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
|
||||
|
||||
@staticmethod
|
||||
def joinable_group(group, state):
|
||||
def joinable_group(group: Group, state: State) -> bool:
|
||||
"""
|
||||
Check if a group is a user/state joinable group, i.e.
|
||||
not an internal group for Corp, Alliance, Members etc,
|
||||
@@ -30,12 +56,15 @@ class GroupManager:
|
||||
:param state: allianceauth.authentication.State object
|
||||
:return: bool True if its joinable, False otherwise
|
||||
"""
|
||||
if len(group.authgroup.states.all()) != 0 and state not in group.authgroup.states.all():
|
||||
if (len(group.authgroup.states.all()) != 0
|
||||
and state not in group.authgroup.states.all()
|
||||
):
|
||||
return False
|
||||
return not group.authgroup.internal
|
||||
else:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def check_internal_group(group):
|
||||
def check_internal_group(group: Group) -> bool:
|
||||
"""
|
||||
Check if a group is auditable, i.e not an internal group
|
||||
:param group: django.contrib.auth.models.Group object
|
||||
@@ -44,20 +73,11 @@ class GroupManager:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def check_internal_group(group):
|
||||
"""
|
||||
Check if a group is auditable, i.e not an internal group
|
||||
:param group: django.contrib.auth.models.Group object
|
||||
:return: bool True if it is auditable, false otherwise
|
||||
"""
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def has_management_permission(user):
|
||||
def has_management_permission(user: User) -> bool:
|
||||
return user.has_perm('auth.group_management')
|
||||
|
||||
@classmethod
|
||||
def can_manage_groups(cls, user):
|
||||
def can_manage_groups(cls, user:User ) -> bool:
|
||||
"""
|
||||
For use with user_passes_test decorator.
|
||||
Check if the user can manage groups. Either has the
|
||||
@@ -71,7 +91,7 @@ class GroupManager:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_manage_group(cls, user, group):
|
||||
def can_manage_group(cls, user: User, group: Group) -> bool:
|
||||
"""
|
||||
Check user has permission to manage the given group
|
||||
:param user: User object to test permission of
|
||||
@@ -79,5 +99,5 @@ class GroupManager:
|
||||
:return: True if the user can manage the group
|
||||
"""
|
||||
if user.is_authenticated:
|
||||
return cls.has_management_permission(user) or user.leads_groups.filter(group=group).exists()
|
||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
|
||||
return False
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
Back
|
||||
{% trans "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if entries %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
Back
|
||||
{% trans "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if group.user_set %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
Groups
|
||||
{% trans "Groups" %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if groups %}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
from django.contrib.auth.models import User, Group
|
||||
from allianceauth.groupmanagement.managers import GroupManager
|
||||
from allianceauth.groupmanagement.signals import check_groups_on_state_change
|
||||
|
||||
class GroupManagementVisibilityTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
|
||||
cls.group1 = Group.objects.create(name='group1')
|
||||
cls.group2 = Group.objects.create(name='group2')
|
||||
cls.group3 = Group.objects.create(name='group3')
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
|
||||
def test_can_manage_group(self):
|
||||
|
||||
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertIn(self.group2, groups) #avail due to group1
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
|
||||
class GroupManagementStateTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
|
||||
cls.state_group = Group.objects.create(name='state_group')
|
||||
cls.open_group = Group.objects.create(name='open_group')
|
||||
cls.state = AuthUtils.create_state('test state', 500)
|
||||
cls.state_group.authgroup.states.add(cls.state)
|
||||
cls.state_group.authgroup.internal = False
|
||||
cls.state_group.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
self.state.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
def _refresh_test_group(self):
|
||||
self.state_group = Group.objects.get(pk=self.state_group.pk)
|
||||
|
||||
def test_drop_state_group(self):
|
||||
|
||||
self.user.groups.add(self.open_group)
|
||||
self.user.groups.add(self.state_group)
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
|
||||
self.state.member_corporations.add(self.corp)
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state, self.state)
|
||||
groups = self.user.groups.all()
|
||||
self.assertIn(self.state_group, groups) #keeps group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
|
||||
self.state.member_corporations.clear()
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
groups = self.user.groups.all()
|
||||
self.assertNotIn(self.state_group, groups) #looses group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
337
allianceauth/groupmanagement/tests/test_managers.py
Normal file
@@ -0,0 +1,337 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..models import AuthGroup
|
||||
from ..managers import GroupManager
|
||||
|
||||
|
||||
class MockUserNotAuthenticated():
|
||||
def __init__(self):
|
||||
self.is_authenticated = False
|
||||
|
||||
class GroupManagementVisibilityTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(
|
||||
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
|
||||
)
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
||||
cls.corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
|
||||
)
|
||||
cls.group1 = Group.objects.create(name='group1')
|
||||
cls.group2 = Group.objects.create(name='group2')
|
||||
cls.group3 = Group.objects.create(name='group3')
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
|
||||
def test_get_group_leaders_groups(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
|
||||
def test_can_manage_group(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
|
||||
self.assertTrue(GroupManager.can_manage_group(self.user, self.group1))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group2))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
|
||||
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self.group1.authgroup.group_leaders.remove(self.user)
|
||||
self._refresh_user()
|
||||
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group1))
|
||||
self.assertTrue(GroupManager.can_manage_group(self.user, self.group2))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
|
||||
|
||||
|
||||
class TestGroupManager(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# group 1
|
||||
cls.group_default = Group.objects.create(name='default')
|
||||
cls.group_default.authgroup.description = 'Default Group'
|
||||
cls.group_default.authgroup.internal = False
|
||||
cls.group_default.authgroup.hidden = False
|
||||
cls.group_default.authgroup.save()
|
||||
|
||||
# group 2
|
||||
cls.group_internal = Group.objects.create(name='internal')
|
||||
cls.group_internal.authgroup.description = 'Internal Group'
|
||||
cls.group_internal.authgroup.internal = True
|
||||
cls.group_internal.authgroup.save()
|
||||
|
||||
# group 3
|
||||
cls.group_hidden = Group.objects.create(name='hidden')
|
||||
cls.group_hidden.authgroup.description = 'Hidden Group'
|
||||
cls.group_hidden.authgroup.internal = False
|
||||
cls.group_hidden.authgroup.hidden = True
|
||||
cls.group_hidden.authgroup.save()
|
||||
|
||||
# group 4
|
||||
cls.group_open = Group.objects.create(name='open')
|
||||
cls.group_open.authgroup.description = 'Open Group'
|
||||
cls.group_open.authgroup.internal = False
|
||||
cls.group_open.authgroup.hidden = False
|
||||
cls.group_open.authgroup.open = True
|
||||
cls.group_open.authgroup.save()
|
||||
|
||||
# group 5
|
||||
cls.group_public_1 = Group.objects.create(name='public 1')
|
||||
cls.group_public_1.authgroup.description = 'Public Group 1'
|
||||
cls.group_public_1.authgroup.internal = False
|
||||
cls.group_public_1.authgroup.hidden = False
|
||||
cls.group_public_1.authgroup.public = True
|
||||
cls.group_public_1.authgroup.save()
|
||||
|
||||
# group 6
|
||||
cls.group_public_2 = Group.objects.create(name='public 2')
|
||||
cls.group_public_2.authgroup.description = 'Public Group 2'
|
||||
cls.group_public_2.authgroup.internal = False
|
||||
cls.group_public_2.authgroup.hidden = True
|
||||
cls.group_public_2.authgroup.open = True
|
||||
cls.group_public_2.authgroup.public = True
|
||||
cls.group_public_2.authgroup.save()
|
||||
|
||||
# group 7
|
||||
cls.group_default_member = Group.objects.create(name='default members')
|
||||
cls.group_default_member.authgroup.description = \
|
||||
'Default Group for members only'
|
||||
cls.group_default_member.authgroup.internal = False
|
||||
cls.group_default_member.authgroup.hidden = False
|
||||
cls.group_default_member.authgroup.open = False
|
||||
cls.group_default_member.authgroup.public = False
|
||||
cls.group_default_member.authgroup.states.add(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
cls.group_default_member.authgroup.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_get_joinable_group_member(self):
|
||||
result = GroupManager.get_joinable_groups(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_group_guest(self):
|
||||
result = GroupManager.get_joinable_groups(
|
||||
AuthUtils.get_guest_state()
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_joinable_group_member(self):
|
||||
member_state = AuthUtils.get_member_state()
|
||||
for x in [
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
]:
|
||||
self.assertTrue(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
for x in [
|
||||
self.group_internal,
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
|
||||
def test_joinable_group_guest(self):
|
||||
guest_state = AuthUtils.get_guest_state()
|
||||
for x in [
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
]:
|
||||
self.assertTrue(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
for x in [
|
||||
self.group_internal,
|
||||
self.group_default_member
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
|
||||
def test_get_all_non_internal_groups(self):
|
||||
result = GroupManager.get_all_non_internal_groups()
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_check_internal_group(self):
|
||||
self.assertTrue(
|
||||
GroupManager.check_internal_group(self.group_default)
|
||||
)
|
||||
self.assertFalse(
|
||||
GroupManager.check_internal_group(self.group_internal)
|
||||
)
|
||||
|
||||
def test_get_joinable_groups_for_user_no_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected= {self.group_public_1, self.group_public_2}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_guest_w_permission_(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission_no_hidden(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(
|
||||
self.user, include_hidden=False
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_has_management_permission(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.has_management_permission(user))
|
||||
|
||||
def test_can_manage_groups_no_perm_no_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.assertFalse(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_user_not_authenticated(self):
|
||||
user = MockUserNotAuthenticated()
|
||||
self.assertFalse(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_no_perm_leads_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_default.authgroup.group_leaders.add(user)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_group_no_perm_no_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_no_perm_leads_correct_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_default.authgroup.group_leaders.add(user)
|
||||
self.assertTrue(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_no_perm_leads_other_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_hidden.authgroup.group_leaders.add(user)
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_user_not_authenticated(self):
|
||||
user = MockUserNotAuthenticated()
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
167
allianceauth/groupmanagement/tests/test_models.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
)
|
||||
|
||||
from ..models import GroupRequest, RequestLog
|
||||
|
||||
|
||||
def create_testdata():
|
||||
# clear DB
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
EveCharacter.objects.all().delete()
|
||||
EveCorporationInfo.objects.all().delete()
|
||||
EveAllianceInfo.objects.all().delete()
|
||||
|
||||
# group 1
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
# user 1
|
||||
user_1 = AuthUtils.create_user('Bruce Wayne')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_1,
|
||||
name='Bruce Wayne',
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name='Wayne Technologies'
|
||||
)
|
||||
user_1.groups.add(group)
|
||||
group.authgroup.group_leaders.add(user_1)
|
||||
|
||||
# user 2
|
||||
user_2 = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_2,
|
||||
name='Clark Kent',
|
||||
character_id=1002,
|
||||
corp_id=2002,
|
||||
corp_name='Wayne Technologies'
|
||||
)
|
||||
return group, user_1, user_2
|
||||
|
||||
|
||||
|
||||
class TestGroupRequest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, _ = create_testdata()
|
||||
|
||||
def test_main_char(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
status='Pending',
|
||||
user=self.user_1,
|
||||
group=self.group
|
||||
)
|
||||
expected = self.user_1.profile.main_character
|
||||
self.assertEqual(group_request.main_char, expected)
|
||||
|
||||
def test_str(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
status='Pending',
|
||||
user=self.user_1,
|
||||
group=self.group
|
||||
)
|
||||
expected = 'Bruce Wayne:Superheros'
|
||||
self.assertEqual(str(group_request), expected)
|
||||
|
||||
|
||||
class TestRequestLog(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, cls.user_2 = create_testdata()
|
||||
|
||||
def test_requestor(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Clark Kent'
|
||||
self.assertEqual(request_log.requestor(), expected)
|
||||
|
||||
def test_type_to_str_removed(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=None,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Removed'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_type_to_str_leave(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=True,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Leave'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_type_to_str_join(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=False,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Join'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_action_to_str_accept(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = True
|
||||
)
|
||||
expected = 'Accept'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
|
||||
def test_action_to_str_reject(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
)
|
||||
expected = 'Reject'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
|
||||
def test_req_char(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
)
|
||||
expected = self.user_2.profile.main_character
|
||||
self.assertEqual(request_log.req_char(), expected)
|
||||
|
||||
|
||||
class TestAuthGroup(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
expected = 'Superheros'
|
||||
self.assertEqual(str(group.authgroup), expected)
|
||||
61
allianceauth/groupmanagement/tests/test_signals.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..signals import check_groups_on_state_change
|
||||
|
||||
|
||||
class GroupManagementStateTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(
|
||||
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
|
||||
)
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2'
|
||||
)
|
||||
cls.corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
|
||||
)
|
||||
cls.state_group = Group.objects.create(name='state_group')
|
||||
cls.open_group = Group.objects.create(name='open_group')
|
||||
cls.state = AuthUtils.create_state('test state', 500)
|
||||
cls.state_group.authgroup.states.add(cls.state)
|
||||
cls.state_group.authgroup.internal = False
|
||||
cls.state_group.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
self.state.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
def _refresh_test_group(self):
|
||||
self.state_group = Group.objects.get(pk=self.state_group.pk)
|
||||
|
||||
def test_drop_state_group(self):
|
||||
self.user.groups.add(self.open_group)
|
||||
self.user.groups.add(self.state_group)
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
|
||||
self.state.member_corporations.add(self.corp)
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state, self.state)
|
||||
groups = self.user.groups.all()
|
||||
self.assertIn(self.state_group, groups) #keeps group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
|
||||
self.state.member_corporations.clear()
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
groups = self.user.groups.all()
|
||||
self.assertNotIn(self.state_group, groups) #looses group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
22
allianceauth/groupmanagement/tests/test_views.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from esi.models import Token
|
||||
|
||||
from .. import views
|
||||
|
||||
|
||||
class TestViews(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_groups_view_can_load(self):
|
||||
request = self.factory.get(reverse('groupmanagement:groups'))
|
||||
request.user = self.user
|
||||
response = views.groups_view(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
@@ -10,12 +11,12 @@ 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 ugettext_lazy as _
|
||||
from .managers import GroupManager
|
||||
from .models import GroupRequest, RequestLog
|
||||
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
from django.conf import settings
|
||||
from .managers import GroupManager
|
||||
from .models import GroupRequest, RequestLog
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -33,7 +34,8 @@ def group_management(request):
|
||||
group_requests = base_group_query.all()
|
||||
else:
|
||||
# Group specific leader
|
||||
group_requests = base_group_query.filter(group__authgroup__group_leaders__in=[request.user])
|
||||
users__groups = GroupManager.get_group_leaders_groups(request.user)
|
||||
group_requests = base_group_query.filter(group__in=users__groups)
|
||||
|
||||
for grouprequest in group_requests:
|
||||
if grouprequest.leave_request:
|
||||
@@ -234,7 +236,7 @@ def group_reject_request(request, group_request_id):
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to reject group request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to reject group request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -268,9 +270,9 @@ def group_leave_accept_request(request, group_request_id):
|
||||
(request.user, group_request_id))
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to accept group leave request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to accept group leave request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -302,9 +304,9 @@ def group_leave_reject_request(request, group_request_id):
|
||||
(request.user, group_request_id))
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to reject group leave request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to reject group leave request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -314,24 +316,23 @@ def group_leave_reject_request(request, group_request_id):
|
||||
@login_required
|
||||
def groups_view(request):
|
||||
logger.debug("groups_view called by user %s" % request.user)
|
||||
|
||||
groups_qs = GroupManager.get_joinable_groups_for_user(
|
||||
request.user, include_hidden=False
|
||||
)
|
||||
groups_qs = groups_qs.order_by('name')
|
||||
groups = []
|
||||
for group in groups_qs:
|
||||
group_request = GroupRequest.objects\
|
||||
.filter(user=request.user)\
|
||||
.filter(group=group)
|
||||
groups.append({
|
||||
'group': group,
|
||||
'request': group_request[0] if group_request else None
|
||||
})
|
||||
|
||||
group_query = GroupManager.get_joinable_groups(request.user.profile.state)
|
||||
|
||||
if not request.user.has_perm('groupmanagement.request_groups'):
|
||||
# Filter down to public groups only for non-members
|
||||
group_query = group_query.filter(authgroup__public=True)
|
||||
logger.debug("Not a member, only public groups will be available")
|
||||
|
||||
for group in group_query:
|
||||
# Exclude hidden
|
||||
if not group.authgroup.hidden:
|
||||
group_request = GroupRequest.objects.filter(user=request.user).filter(group=group)
|
||||
|
||||
groups.append({'group': group, 'request': group_request[0] if group_request else None})
|
||||
|
||||
render_items = {'groups': groups}
|
||||
return render(request, 'groupmanagement/groups.html', context=render_items)
|
||||
context = {'groups': groups}
|
||||
return render(request, 'groupmanagement/groups.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -348,13 +349,13 @@ def group_request_add(request, group_id):
|
||||
# User is already a member of this group.
|
||||
logger.warning("User %s attempted to join group id %s but they are already a member." %
|
||||
(request.user, group_id))
|
||||
messages.warning(request, "You are already a member of that group.")
|
||||
messages.warning(request, _("You are already a member of that group."))
|
||||
return redirect('groupmanagement:groups')
|
||||
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
||||
# Does not have the required permission, trying to join a non-public group
|
||||
logger.warning("User %s attempted to join group id %s but it is not a public group" %
|
||||
(request.user, group_id))
|
||||
messages.warning(request, "You cannot join that group")
|
||||
messages.warning(request, _("You cannot join that group"))
|
||||
return redirect('groupmanagement:groups')
|
||||
if group.authgroup.open:
|
||||
logger.info("%s joining %s as is an open group" % (request.user, group))
|
||||
@@ -363,7 +364,7 @@ def group_request_add(request, group_id):
|
||||
req = GroupRequest.objects.filter(user=request.user, group=group)
|
||||
if len(req) > 0:
|
||||
logger.info("%s attempted to join %s but already has an open application" % (request.user, group))
|
||||
messages.warning(request, "You already have a pending application for that group.")
|
||||
messages.warning(request, _("You already have a pending application for that group."))
|
||||
return redirect("groupmanagement:groups")
|
||||
grouprequest = GroupRequest()
|
||||
grouprequest.status = _('Pending')
|
||||
@@ -397,7 +398,7 @@ def group_request_leave(request, group_id):
|
||||
req = GroupRequest.objects.filter(user=request.user, group=group)
|
||||
if len(req) > 0:
|
||||
logger.info("%s attempted to leave %s but already has an pending leave request." % (request.user, group))
|
||||
messages.warning(request, "You already have a pending leave request for that group.")
|
||||
messages.warning(request, _("You already have a pending leave request for that group."))
|
||||
return redirect("groupmanagement:groups")
|
||||
if getattr(settings, 'AUTO_LEAVE', False):
|
||||
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))
|
||||
|
||||
BIN
allianceauth/locale/en/LC_MESSAGES/django.mo
Normal file
2018
allianceauth/locale/en/LC_MESSAGES/django.po
Normal file
BIN
allianceauth/locale/ko_KR/LC_MESSAGES/django.mo
Normal file
2023
allianceauth/locale/ko_KR/LC_MESSAGES/django.po
Normal file
BIN
allianceauth/locale/ru/LC_MESSAGES/django.mo
Normal file
2038
allianceauth/locale/ru/LC_MESSAGES/django.po
Normal file
BIN
allianceauth/locale/zh_Hans/LC_MESSAGES/django.mo
Normal file
1811
allianceauth/locale/zh_Hans/LC_MESSAGES/django.po
Normal file
@@ -11,6 +11,15 @@ app = Celery('{{ project_name }}')
|
||||
# Using a string here means the worker don't have to serialize
|
||||
# the configuration object to child processes.
|
||||
app.config_from_object('django.conf:settings')
|
||||
|
||||
# setup priorities ( 0 Highest, 9 Lowest )
|
||||
app.conf.broker_transport_options = {
|
||||
'priority_steps': list(range(10)), # setup que to have 10 steps
|
||||
'queue_order_strategy': 'priority', # setup que to use prio sorting
|
||||
}
|
||||
app.conf.task_default_priority = 5 # anything called with the task.delay() will be given normal priority (5)
|
||||
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
|
||||
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'settings': {}
|
||||
|
||||
@@ -83,6 +83,9 @@ LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
('es', ugettext('Spanish')),
|
||||
('zh-hans', ugettext('Chinese Simplified')),
|
||||
('ru', ugettext('Russian')),
|
||||
('ko', ugettext('Korean')),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
@@ -217,6 +220,14 @@ LOGGING = {
|
||||
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
|
||||
'backupCount': 5, # edit this line to change number of log backups
|
||||
},
|
||||
'extension_file': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(BASE_DIR, 'log/extensions.log'),
|
||||
'formatter': 'verbose',
|
||||
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
|
||||
'backupCount': 5, # edit this line to change number of log backups
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG', # edit this line to change logging level to console
|
||||
'class': 'logging.StreamHandler',
|
||||
@@ -233,6 +244,10 @@ LOGGING = {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'extensions': {
|
||||
'handlers': ['extension_file', 'console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['log_file', 'console'],
|
||||
'level': 'ERROR',
|
||||
|
||||
@@ -4,3 +4,8 @@ from allianceauth import urls
|
||||
urlpatterns = [
|
||||
url(r'', include(urls)),
|
||||
]
|
||||
|
||||
handler500 = 'allianceauth.views.Generic500Redirect'
|
||||
handler404 = 'allianceauth.views.Generic404Redirect'
|
||||
handler403 = 'allianceauth.views.Generic403Redirect'
|
||||
handler400 = 'allianceauth.views.Generic400Redirect'
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
|
||||
from django.db import models, IntegrityError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import render, Http404, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .forms import ServicePasswordModelForm
|
||||
|
||||
@@ -68,7 +69,7 @@ class BaseCreatePasswordServiceAccountView(BaseServiceView, ServiceCredentialsVi
|
||||
try:
|
||||
svc_obj = self.model.objects.create(user=request.user)
|
||||
except IntegrityError:
|
||||
messages.error(request, "That service account already exists")
|
||||
messages.error(request, _("That service account already exists"))
|
||||
return redirect(self.index_redirect)
|
||||
|
||||
return render(request, self.template_name,
|
||||
@@ -100,7 +101,7 @@ class BaseSetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, Upda
|
||||
def post(self, request, *args, **kwargs):
|
||||
result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs)
|
||||
if self.get_form().is_valid():
|
||||
messages.success(request, "Successfully set your {} password".format(self.service_name))
|
||||
messages.success(request, _("Successfully set your {} password".format(self.service_name)))
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
|
||||
from .hooks import MenuItemHook
|
||||
from .hooks import ServicesHook
|
||||
|
||||
@@ -6,7 +8,7 @@ from .hooks import ServicesHook
|
||||
class Services(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Services',
|
||||
_('Services'),
|
||||
'fa fa-cogs fa-fw',
|
||||
'services:services', 100)
|
||||
|
||||
|
||||
@@ -9,6 +9,29 @@ 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
|
||||
to be used by the extension to log events to the extensions logger.
|
||||
|
||||
The logging level is decided by whether or not DEBUG is set to true in the project settings. If
|
||||
DEBUG is set to false, then the logging level is set to INFO.
|
||||
|
||||
:param: name: the name of the extension doing the logging
|
||||
:return: an extensions child logger
|
||||
"""
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger('extensions.' + name)
|
||||
logger.name = name
|
||||
logger.level = logging.INFO
|
||||
if settings.DEBUG:
|
||||
logger.level = logging.DEBUG
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
class ServicesHook:
|
||||
"""
|
||||
Abstract base class for creating a compatible services
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-09-05 21:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DiscordAuthToken',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('email', models.CharField(max_length=254, unique=True)),
|
||||
('token', models.CharField(max_length=254)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GroupCache',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('groups', models.TextField(default={})),
|
||||
('service', models.CharField(choices=[(b'discourse', b'discourse'), (b'discord', b'discord')], max_length=254, unique=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-10-16 01:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='discordauthtoken',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DiscordAuthToken',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-09-02 06:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0002_auto_20161016_0135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='GroupCache',
|
||||
),
|
||||
]
|
||||
18
allianceauth/services/migrations/0003_remove_broken_link.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.10 on 2020-03-21 13:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0002_nameformatter'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='nameformatconfig',
|
||||
name='format',
|
||||
field=models.CharField(help_text='For information on constructing name formats please see the official documentation, topic "Services Name Formats".', max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -4,14 +4,30 @@ from allianceauth.authentication.models import State
|
||||
|
||||
|
||||
class NameFormatConfig(models.Model):
|
||||
service_name = models.CharField(max_length=100, blank=False, null=False)
|
||||
default_to_username = models.BooleanField(default=True, help_text="If a user has no main_character, "
|
||||
"default to using their Auth username instead.")
|
||||
format = models.CharField(max_length=100, blank=False, null=False,
|
||||
help_text='For information on constructing name formats, please see the '
|
||||
'<a href="https://allianceauth.readthedocs.io/en/latest/features/nameformats">'
|
||||
'name format documentation</a>')
|
||||
states = models.ManyToManyField(State, help_text="States to apply this format to. You should only have one "
|
||||
"formatter for each state for each service.")
|
||||
service_name = models.CharField(max_length=100, blank=False)
|
||||
default_to_username = models.BooleanField(
|
||||
default=True,
|
||||
help_text=
|
||||
'If a user has no main_character, '
|
||||
'default to using their Auth username instead.'
|
||||
)
|
||||
format = models.CharField(
|
||||
max_length=100,
|
||||
blank=False,
|
||||
help_text=
|
||||
'For information on constructing name formats '
|
||||
'please see the official documentation, '
|
||||
'topic "Services Name Formats".'
|
||||
)
|
||||
states = models.ManyToManyField(
|
||||
State,
|
||||
help_text=
|
||||
"States to apply this format to. You should only have one "
|
||||
"formatter for each state for each service."
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s' % (
|
||||
self.service_name, ', '.join([str(x) for x in self.states.all()])
|
||||
)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class DiscordService(ServicesHook):
|
||||
|
||||
def sync_nickname(self, user):
|
||||
logger.debug('Syncing %s nickname for user %s' % (self.name, user))
|
||||
DiscordTasks.update_nickname.delay(user.pk)
|
||||
DiscordTasks.update_nickname.apply_async(args=[user.pk], countdown=5)
|
||||
|
||||
def update_all_groups(self):
|
||||
logger.debug('Update all %s groups called' % self.name)
|
||||
|
||||
@@ -27,7 +27,12 @@ class DiscordViewsTestCase(WebTest):
|
||||
self.login()
|
||||
manager.generate_oauth_redirect_url.return_value = '/example.com/oauth/'
|
||||
response = self.app.get('/discord/activate/', auto_follow=False)
|
||||
self.assertRedirects(response, expected_url='/example.com/oauth/', target_status_code=404)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
expected_url="/example.com/oauth/",
|
||||
target_status_code=404,
|
||||
fetch_redirect_response=False,
|
||||
)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager')
|
||||
def test_callback(self, manager):
|
||||
|
||||
@@ -5,8 +5,10 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.views import superuser_test
|
||||
|
||||
from .manager import DiscordOAuthManager
|
||||
from .tasks import DiscordTasks
|
||||
|
||||
@@ -21,10 +23,10 @@ def deactivate_discord(request):
|
||||
logger.debug("deactivate_discord called by user %s" % request.user)
|
||||
if DiscordTasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated discord for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated Discord account.')
|
||||
messages.success(request, _('Deactivated Discord account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate discord for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Discord account.')
|
||||
messages.error(request, _('An error occurred while processing your Discord account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -36,7 +38,7 @@ def reset_discord(request):
|
||||
logger.info("Successfully deleted discord user for user %s - forwarding to discord activation." % request.user)
|
||||
return redirect("discord:activate")
|
||||
logger.error("Unsuccessful attempt to reset discord for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Discord account.')
|
||||
messages.error(request, _('An error occurred while processing your Discord account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -57,10 +59,10 @@ def discord_callback(request):
|
||||
return redirect("services:services")
|
||||
if DiscordTasks.add_user(request.user, code):
|
||||
logger.info("Successfully activated Discord for user %s" % request.user)
|
||||
messages.success(request, 'Activated Discord account.')
|
||||
messages.success(request, _('Activated Discord account.'))
|
||||
else:
|
||||
logger.error("Failed to activate Discord for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Discord account.')
|
||||
messages.error(request, _('An error occurred while processing your Discord account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .manager import DiscourseManager
|
||||
from .tasks import DiscourseTasks
|
||||
@@ -33,12 +34,12 @@ def discourse_sso(request):
|
||||
|
||||
# Check if user has access
|
||||
if not request.user.has_perm(ACCESS_PERM):
|
||||
messages.error(request, 'You are not authorized to access Discourse.')
|
||||
messages.error(request, _('You are not authorized to access Discourse.'))
|
||||
logger.warning('User %s attempted to access Discourse but does not have permission.' % request.user)
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
if not request.user.profile.main_character:
|
||||
messages.error(request, "You must have a main character set to access Discourse.")
|
||||
messages.error(request, _("You must have a main character set to access Discourse."))
|
||||
logger.warning('User %s attempted to access Discourse but does not have a main character.' % request.user)
|
||||
return redirect('authentication:characters')
|
||||
|
||||
@@ -48,7 +49,7 @@ def discourse_sso(request):
|
||||
signature = request.GET.get('sig')
|
||||
|
||||
if None in [payload, signature]:
|
||||
messages.error(request, 'No SSO payload or signature. Please contact support if this problem persists.')
|
||||
messages.error(request, _('No SSO payload or signature. Please contact support if this problem persists.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
# Validate the payload
|
||||
@@ -58,7 +59,7 @@ def discourse_sso(request):
|
||||
assert 'nonce' in decoded
|
||||
assert len(payload) > 0
|
||||
except AssertionError:
|
||||
messages.error(request, 'Invalid payload. Please contact support if this problem persists.')
|
||||
messages.error(request, _('Invalid payload. Please contact support if this problem persists.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
key = str(settings.DISCOURSE_SSO_SECRET).encode('utf-8')
|
||||
@@ -66,7 +67,7 @@ def discourse_sso(request):
|
||||
this_signature = h.hexdigest()
|
||||
|
||||
if this_signature != signature:
|
||||
messages.error(request, 'Invalid payload. Please contact support if this problem persists.')
|
||||
messages.error(request, _('Invalid payload. Please contact support if this problem persists.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
## Build the return payload
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
from .manager import Ips4Manager
|
||||
@@ -27,7 +28,7 @@ def activate_ips4(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with IPS4 credentials." % request.user)
|
||||
# update_ips4_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated IPS4 for user %s" % request.user)
|
||||
messages.success(request, 'Activated IPSuite4 account.')
|
||||
messages.success(request, _('Activated IPSuite4 account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
@@ -36,7 +37,7 @@ def activate_ips4(request):
|
||||
context={'credentials': credentials, 'service': 'IPSuite4'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate IPS4 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -49,7 +50,7 @@ def reset_ips4_password(request):
|
||||
# false we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset IPS4 password for user %s" % request.user)
|
||||
messages.success(request, 'Reset IPSuite4 password.')
|
||||
messages.success(request, _('Reset IPSuite4 password.'))
|
||||
credentials = {
|
||||
'username': request.user.ips4.username,
|
||||
'password': result,
|
||||
@@ -58,7 +59,7 @@ def reset_ips4_password(request):
|
||||
context={'credentials': credentials, 'service': 'IPSuite4'})
|
||||
|
||||
logger.error("Unsuccessful attempt to reset IPS4 password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -76,10 +77,10 @@ def set_ips4_password(request):
|
||||
result = Ips4Manager.update_custom_password(request.user.ips4.username, plain_password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set IPS4 password for user %s" % request.user)
|
||||
messages.success(request, 'Set IPSuite4 password.')
|
||||
messages.success(request, _('Set IPSuite4 password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom IPS4 password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect('services:services')
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
@@ -96,9 +97,9 @@ def deactivate_ips4(request):
|
||||
logger.debug("deactivate_ips4 called by user %s" % request.user)
|
||||
if Ips4Tasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated IPS4 for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated IPSuite4 account.')
|
||||
messages.success(request, _('Deactivated IPSuite4 account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate IPS4 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ class MumbleService(ServicesHook):
|
||||
if MumbleTasks.has_account(user):
|
||||
MumbleTasks.update_groups.delay(user.pk)
|
||||
|
||||
def sync_nickname(self, user):
|
||||
logger.debug("Updating %s nickname for %s" % (self.name, user))
|
||||
if MumbleTasks.has_account(user):
|
||||
MumbleTasks.update_display_name.apply_async(args=[user.pk], countdown=5) # cooldown on this task to ensure DB clean when syncing
|
||||
|
||||
def validate_user(self, user):
|
||||
if MumbleTasks.has_account(user) and not self.service_active_for_user(user):
|
||||
self.delete_user(user, notify_user=True)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2.9 on 2020-03-16 07:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mumble', '0007_not_null_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mumbleuser',
|
||||
name='display_name',
|
||||
field=models.CharField(max_length=254, null=True),
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
from django.db import migrations, models
|
||||
from ..auth_hooks import MumbleService
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
|
||||
def fwd_func(apps, schema_editor):
|
||||
MumbleUser = apps.get_model("mumble", "MumbleUser")
|
||||
db_alias = schema_editor.connection.alias
|
||||
all_users = MumbleUser.objects.using(db_alias).all()
|
||||
for user in all_users:
|
||||
display_name = NameFormatter(MumbleService(), user.user).format_name()
|
||||
user.display_name = display_name
|
||||
user.save()
|
||||
|
||||
def rev_func(apps, schema_editor):
|
||||
MumbleUser = apps.get_model("mumble", "MumbleUser")
|
||||
db_alias = schema_editor.connection.alias
|
||||
all_users = MumbleUser.objects.using(db_alias).all()
|
||||
for user in all_users:
|
||||
user.display_name = None
|
||||
user.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mumble', '0008_mumbleuser_display_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fwd_func, rev_func),
|
||||
migrations.AlterField(
|
||||
model_name='mumbleuser',
|
||||
name='display_name',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -15,10 +15,14 @@ class MumbleManager(models.Manager):
|
||||
HASH_FN = 'bcrypt-sha256'
|
||||
|
||||
@staticmethod
|
||||
def get_username(user):
|
||||
def get_display_name(user):
|
||||
from .auth_hooks import MumbleService
|
||||
return NameFormatter(MumbleService(), user).format_name()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_username(user):
|
||||
return user.profile.main_character.character_name # main character as the user.username may be incorect
|
||||
|
||||
@staticmethod
|
||||
def sanitise_username(username):
|
||||
return username.replace(" ", "_")
|
||||
@@ -32,20 +36,26 @@ class MumbleManager(models.Manager):
|
||||
return bcrypt_sha256.encrypt(password.encode('utf-8'))
|
||||
|
||||
def create(self, user):
|
||||
username = self.get_username(user)
|
||||
logger.debug("Creating mumble user with username {}".format(username))
|
||||
username_clean = self.sanitise_username(username)
|
||||
password = self.generate_random_pass()
|
||||
pwhash = self.gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
|
||||
username_clean, pwhash[0:5]))
|
||||
logger.info("Creating mumble user {}".format(username_clean))
|
||||
try:
|
||||
username = self.get_username(user)
|
||||
logger.debug("Creating mumble user with username {}".format(username))
|
||||
username_clean = self.sanitise_username(username)
|
||||
display_name = self.get_display_name(user)
|
||||
password = self.generate_random_pass()
|
||||
pwhash = self.gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
|
||||
username_clean, pwhash[0:5]))
|
||||
logger.info("Creating mumble user {}".format(username_clean))
|
||||
|
||||
result = super(MumbleManager, self).create(user=user, username=username_clean,
|
||||
pwhash=pwhash, hashfn=self.HASH_FN)
|
||||
result.update_groups()
|
||||
result.credentials.update({'username': result.username, 'password': password})
|
||||
return result
|
||||
result = super(MumbleManager, self).create(user=user, username=username_clean,
|
||||
pwhash=pwhash, hashfn=self.HASH_FN,
|
||||
display_name=display_name)
|
||||
result.update_groups()
|
||||
result.credentials.update({'username': result.username, 'password': password})
|
||||
return result
|
||||
except AttributeError: # No Main or similar errors
|
||||
return False
|
||||
return False
|
||||
|
||||
def user_exists(self, username):
|
||||
return self.filter(username=username).exists()
|
||||
@@ -59,6 +69,8 @@ class MumbleUser(AbstractServiceModel):
|
||||
|
||||
objects = MumbleManager()
|
||||
|
||||
display_name = models.CharField(max_length=254, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
@@ -91,6 +103,12 @@ class MumbleUser(AbstractServiceModel):
|
||||
self.save()
|
||||
return True
|
||||
|
||||
def update_display_name(self):
|
||||
logger.info("Updating mumble user {} display name".format(self.user))
|
||||
self.display_name = MumbleManager.get_display_name(self.user)
|
||||
self.save()
|
||||
return True
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("access_mumble", u"Can access the Mumble service"),
|
||||
|
||||
@@ -45,9 +45,37 @@ class MumbleTasks:
|
||||
logger.debug("User %s does not have a mumble account, skipping" % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name="mumble.update_display_name", base=QueueOnce)
|
||||
def update_display_name(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating mumble groups for user %s" % user)
|
||||
if MumbleTasks.has_account(user):
|
||||
try:
|
||||
if not user.mumble.update_display_name():
|
||||
raise Exception("Display Name Sync failed")
|
||||
logger.debug("Updated user %s mumble display name." % user)
|
||||
return True
|
||||
except MumbleUser.DoesNotExist:
|
||||
logger.info("Mumble display name sync failed for {}, user does not have a mumble account".format(user))
|
||||
except:
|
||||
logger.exception("Mumble display name sync failed for %s, retrying in 10 mins" % user)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
else:
|
||||
logger.debug("User %s does not have a mumble account, skipping" % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(name="mumble.update_all_groups")
|
||||
def update_all_groups():
|
||||
logger.debug("Updating ALL mumble groups")
|
||||
for mumble_user in MumbleUser.objects.exclude(username__exact=''):
|
||||
MumbleTasks.update_groups.delay(mumble_user.user.pk)
|
||||
|
||||
@staticmethod
|
||||
@shared_task(name="mumble.update_all_display_names")
|
||||
def update_all_display_names():
|
||||
logger.debug("Updating ALL mumble display names")
|
||||
for mumble_user in MumbleUser.objects.exclude(username__exact=''):
|
||||
MumbleTasks.update_display_name.delay(mumble_user.user.pk)
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ class MumbleHooksTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.member = 'member_user'
|
||||
member = AuthUtils.create_member(self.member)
|
||||
AuthUtils.add_main_character(member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation',
|
||||
corp_ticker='TESTR')
|
||||
member = User.objects.get(pk=member.pk)
|
||||
MumbleUser.objects.create(user=member)
|
||||
self.none_user = 'none_user'
|
||||
none_user = AuthUtils.create_user(self.none_user)
|
||||
@@ -122,23 +125,45 @@ class MumbleViewsTestCase(TestCase):
|
||||
self.member.save()
|
||||
AuthUtils.add_main_character(self.member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation',
|
||||
corp_ticker='TESTR')
|
||||
self.member = User.objects.get(pk=self.member.pk)
|
||||
add_permissions()
|
||||
|
||||
def login(self):
|
||||
self.client.force_login(self.member)
|
||||
|
||||
def test_activate(self):
|
||||
def test_activate_update(self):
|
||||
self.login()
|
||||
expected_username = '[TESTR]auth_member'
|
||||
expected_username = 'auth_member'
|
||||
expected_displayname = '[TESTR]auth_member'
|
||||
response = self.client.get(urls.reverse('mumble:activate'), follow=False)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, expected_username)
|
||||
# create
|
||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||
self.assertEqual(mumble_user.username, expected_username)
|
||||
self.assertTrue(MumbleUser.objects.user_exists(expected_username))
|
||||
self.assertEqual(str(mumble_user), expected_username)
|
||||
self.assertEqual(mumble_user.display_name, expected_displayname)
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertIn('Guest', mumble_user.groups)
|
||||
self.assertIn('Member', mumble_user.groups)
|
||||
self.assertIn(',', mumble_user.groups)
|
||||
# test update
|
||||
self.member.profile.main_character.character_name = "auth_member_updated"
|
||||
self.member.profile.main_character.corporation_ticker = "TESTU"
|
||||
self.member.profile.main_character.save()
|
||||
mumble_user.update_display_name()
|
||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||
expected_displayname = '[TESTU]auth_member_updated'
|
||||
self.assertEqual(mumble_user.username, expected_username)
|
||||
self.assertTrue(MumbleUser.objects.user_exists(expected_username))
|
||||
self.assertEqual(str(mumble_user), expected_username)
|
||||
self.assertEqual(mumble_user.display_name, expected_displayname)
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertIn('Guest', mumble_user.groups)
|
||||
self.assertIn('Member', mumble_user.groups)
|
||||
self.assertIn(',', mumble_user.groups)
|
||||
|
||||
|
||||
def test_deactivate_post(self):
|
||||
self.login()
|
||||
@@ -171,7 +196,6 @@ class MumbleViewsTestCase(TestCase):
|
||||
self.assertTemplateUsed(response, 'services/service_credentials.html')
|
||||
self.assertContains(response, 'auth_member')
|
||||
|
||||
|
||||
class MumbleManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
from .models import MumbleManager
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import ServicesHook, MenuItemHook
|
||||
@@ -22,7 +23,7 @@ class OpenfireService(ServicesHook):
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return "Jabber"
|
||||
return _("Jabber")
|
||||
|
||||
def delete_user(self, user, notify_user=False):
|
||||
logger.debug('Deleting user %s %s account' % (user, self.name))
|
||||
@@ -74,7 +75,7 @@ def register_service():
|
||||
class JabberBroadcast(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Jabber Broadcast',
|
||||
_('Jabber Broadcast'),
|
||||
'fa fa-lock fa-fw fa-bullhorn',
|
||||
'openfire:broadcast')
|
||||
|
||||
@@ -87,7 +88,7 @@ class JabberBroadcast(MenuItemHook):
|
||||
class FleetBroadcastFormatter(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Fleet Broadcast Formatter',
|
||||
_('Fleet Broadcast Formatter'),
|
||||
'fa fa-lock fa-fw fa-space-shuttle',
|
||||
'services:fleet_format_tool')
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib.auth.models import Group
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .forms import JabberBroadcastForm
|
||||
from .manager import OpenfireManager, PingBotException
|
||||
from .models import OpenfireUser
|
||||
@@ -30,7 +32,7 @@ def activate_jabber(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with jabber credentials. Updating groups." % request.user)
|
||||
OpenfireTasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated jabber for user %s" % request.user)
|
||||
messages.success(request, 'Activated jabber account.')
|
||||
messages.success(request, _('Activated jabber account.'))
|
||||
credentials = {
|
||||
'username': info[0],
|
||||
'password': info[1],
|
||||
@@ -39,7 +41,7 @@ def activate_jabber(request):
|
||||
context={'credentials': credentials, 'service': 'Jabber'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate jabber for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -52,7 +54,7 @@ def deactivate_jabber(request):
|
||||
messages.success(request, 'Deactivated jabber account.')
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate jabber for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -65,7 +67,7 @@ def reset_jabber_password(request):
|
||||
# If our username is blank means we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset jabber password for user %s" % request.user)
|
||||
messages.success(request, 'Reset jabber password.')
|
||||
messages.success(request, _('Reset jabber password.'))
|
||||
credentials = {
|
||||
'username': request.user.openfire.username,
|
||||
'password': result,
|
||||
@@ -73,7 +75,7 @@ def reset_jabber_password(request):
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'Jabber'})
|
||||
logger.error("Unsuccessful attempt to reset jabber for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -114,7 +116,7 @@ def jabber_broadcast_view(request):
|
||||
|
||||
OpenfireManager.send_broadcast_message(group_to_send, message_to_send)
|
||||
|
||||
messages.success(request, 'Sent jabber broadcast to %s' % group_to_send)
|
||||
messages.success(request, _('Sent jabber broadcast to %s' % group_to_send))
|
||||
logger.info("Sent jabber broadcast on behalf of user %s" % request.user)
|
||||
except PingBotException as e:
|
||||
messages.error(request, e)
|
||||
@@ -143,10 +145,10 @@ def set_jabber_password(request):
|
||||
result = OpenfireManager.update_user_pass(request.user.openfire.username, password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set jabber password for user %s" % request.user)
|
||||
messages.success(request, 'Set jabber password.')
|
||||
messages.success(request, _('Set jabber password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom jabber password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .manager import Phpbb3Manager
|
||||
from .models import Phpbb3User
|
||||
from .tasks import Phpbb3Tasks
|
||||
@@ -29,7 +31,7 @@ def activate_forum(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with forum credentials. Updating groups." % request.user)
|
||||
Phpbb3Tasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated forum for user %s" % request.user)
|
||||
messages.success(request, 'Activated forum account.')
|
||||
messages.success(request, _('Activated forum account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
@@ -38,7 +40,7 @@ def activate_forum(request):
|
||||
context={'credentials': credentials, 'service': 'Forum'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -49,10 +51,10 @@ def deactivate_forum(request):
|
||||
# false we failed
|
||||
if Phpbb3Tasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated forum for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated forum account.')
|
||||
messages.success(request, _('Deactivated forum account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -66,7 +68,7 @@ def reset_forum_password(request):
|
||||
# false we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset forum password for user %s" % request.user)
|
||||
messages.success(request, 'Reset forum password.')
|
||||
messages.success(request, _('Reset forum password.'))
|
||||
credentials = {
|
||||
'username': request.user.phpbb3.username,
|
||||
'password': result,
|
||||
@@ -75,7 +77,7 @@ def reset_forum_password(request):
|
||||
context={'credentials': credentials, 'service': 'Forum'})
|
||||
|
||||
logger.error("Unsuccessful attempt to reset forum password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -95,10 +97,10 @@ def set_forum_password(request):
|
||||
password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set forum password for user %s" % request.user)
|
||||
messages.success(request, 'Set forum password.')
|
||||
messages.success(request, _('Set forum password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom forum password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .manager import SmfManager
|
||||
from .models import SmfUser
|
||||
from .tasks import SmfTasks
|
||||
@@ -29,7 +31,7 @@ def activate_smf(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with smf credentials. Updating groups." % request.user)
|
||||
SmfTasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated smf for user %s" % request.user)
|
||||
messages.success(request, 'Activated SMF account.')
|
||||
messages.success(request, _('Activated SMF account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
@@ -38,7 +40,7 @@ def activate_smf(request):
|
||||
context={'credentials': credentials, 'service': 'SMF'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -50,10 +52,10 @@ def deactivate_smf(request):
|
||||
# false we failed
|
||||
if result:
|
||||
logger.info("Successfully deactivated smf for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated SMF account.')
|
||||
messages.success(request, _('Deactivated SMF account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -67,7 +69,7 @@ def reset_smf_password(request):
|
||||
# false we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset smf password for user %s" % request.user)
|
||||
messages.success(request, 'Reset SMF password.')
|
||||
messages.success(request, _('Reset SMF password.'))
|
||||
credentials = {
|
||||
'username': request.user.smf.username,
|
||||
'password': result,
|
||||
@@ -75,7 +77,7 @@ def reset_smf_password(request):
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'SMF'})
|
||||
logger.error("Unsuccessful attempt to reset smf password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -95,10 +97,10 @@ def set_smf_password(request):
|
||||
password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set smf password for user %s" % request.user)
|
||||
messages.success(request, 'Set SMF password.')
|
||||
messages.success(request, _('Set SMF password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom smf password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -4,6 +4,8 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .manager import Teamspeak3Manager
|
||||
from .forms import TeamspeakJoinForm
|
||||
from .models import Teamspeak3User
|
||||
@@ -29,10 +31,10 @@ def activate_teamspeak3(request):
|
||||
Teamspeak3User.objects.update_or_create(user=request.user, defaults={'uid': result[0], 'perm_key': result[1]})
|
||||
logger.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
|
||||
logger.info("Successfully activated TS3 for user %s" % request.user)
|
||||
messages.success(request, 'Activated TeamSpeak3 account.')
|
||||
messages.success(request, _('Activated TeamSpeak3 account.'))
|
||||
return redirect("teamspeak3:verify")
|
||||
logger.error("Unsuccessful attempt to activate TS3 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your TeamSpeak3 account.')
|
||||
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -66,10 +68,10 @@ def deactivate_teamspeak3(request):
|
||||
logger.debug("deactivate_teamspeak3 called by user %s" % request.user)
|
||||
if Teamspeak3Tasks.has_account(request.user) and Teamspeak3Tasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated TS3 for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated TeamSpeak3 account.')
|
||||
messages.success(request, _('Deactivated TeamSpeak3 account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate TS3 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your TeamSpeak3 account.')
|
||||
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -92,8 +94,8 @@ def reset_teamspeak3_perm(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
|
||||
Teamspeak3Tasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully reset TS3 permission key for user %s" % request.user)
|
||||
messages.success(request, 'Reset TeamSpeak3 permission key.')
|
||||
messages.success(request, _('Reset TeamSpeak3 permission key.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to reset TS3 permission key for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your TeamSpeak3 account.')
|
||||
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .manager import XenForoManager
|
||||
from .models import XenforoUser
|
||||
from .tasks import XenforoTasks
|
||||
@@ -25,7 +27,7 @@ def activate_xenforo_forum(request):
|
||||
if result['response']['status_code'] == 200:
|
||||
XenforoUser.objects.update_or_create(user=request.user, defaults={'username': result['username']})
|
||||
logger.info("Updated user %s with XenForo credentials. Updating groups." % request.user)
|
||||
messages.success(request, 'Activated XenForo account.')
|
||||
messages.success(request, _('Activated XenForo account.'))
|
||||
credentials = {
|
||||
'username': result['username'],
|
||||
'password': result['password'],
|
||||
@@ -35,7 +37,7 @@ def activate_xenforo_forum(request):
|
||||
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate xenforo for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -45,9 +47,9 @@ def deactivate_xenforo_forum(request):
|
||||
logger.debug("deactivate_xenforo_forum called by user %s" % request.user)
|
||||
if XenforoTasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated XenForo for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated XenForo account.')
|
||||
messages.success(request, _('Deactivated XenForo account.'))
|
||||
else:
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -60,7 +62,7 @@ def reset_xenforo_password(request):
|
||||
# Based on XenAPI's response codes
|
||||
if result['response']['status_code'] == 200:
|
||||
logger.info("Successfully reset XenForo password for user %s" % request.user)
|
||||
messages.success(request, 'Reset XenForo account password.')
|
||||
messages.success(request, _('Reset XenForo account password.'))
|
||||
credentials = {
|
||||
'username': request.user.xenforo.username,
|
||||
'password': result['password'],
|
||||
@@ -68,7 +70,7 @@ def reset_xenforo_password(request):
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'XenForo'})
|
||||
logger.error("Unsuccessful attempt to reset XenForo password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -86,10 +88,10 @@ def set_xenforo_password(request):
|
||||
result = XenForoManager.update_user_password(request.user.xenforo.username, password)
|
||||
if result['response']['status_code'] == 200:
|
||||
logger.info("Successfully reset XenForo password for user %s" % request.user)
|
||||
messages.success(request, 'Changed XenForo password.')
|
||||
messages.success(request, _('Changed XenForo password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom XenForo password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect('services:services')
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.db.models.signals import pre_delete
|
||||
@@ -11,6 +12,7 @@ from .tasks import disable_user
|
||||
|
||||
from allianceauth.authentication.models import State, UserProfile
|
||||
from allianceauth.authentication.signals import state_changed
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -157,14 +159,45 @@ def disable_services_on_inactive(sender, instance, *args, **kwargs):
|
||||
|
||||
|
||||
@receiver(pre_save, sender=UserProfile)
|
||||
def disable_services_on_no_main(sender, instance, *args, **kwargs):
|
||||
if not instance.pk:
|
||||
def process_main_character_change(sender, instance, *args, **kwargs):
|
||||
|
||||
if not instance.pk: # ignore
|
||||
# new model being created
|
||||
return
|
||||
try:
|
||||
old_instance = UserProfile.objects.get(pk=instance.pk)
|
||||
if old_instance.main_character and not instance.main_character:
|
||||
if old_instance.main_character and not instance.main_character: # lost main char disable services
|
||||
logger.info("Disabling services due to loss of main character for user {0}".format(instance.user))
|
||||
disable_user(instance.user)
|
||||
elif old_instance.main_character is not instance.main_character: # swapping/changing main character
|
||||
logger.info("Updating Names due to change of main character for user {0}".format(instance.user))
|
||||
for svc in ServicesHook.get_services():
|
||||
try:
|
||||
svc.validate_user(instance.user)
|
||||
svc.sync_nickname(instance.user)
|
||||
except:
|
||||
logger.exception('Exception running sync_nickname for services module %s on user %s' % (svc, instance))
|
||||
|
||||
except UserProfile.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
@receiver(pre_save, sender=EveCharacter)
|
||||
def process_main_character_update(sender, instance, *args, **kwargs):
|
||||
try:
|
||||
if instance.userprofile:
|
||||
old_instance = EveCharacter.objects.get(pk=instance.pk)
|
||||
if not instance.character_name == old_instance.character_name or \
|
||||
not instance.corporation_name == old_instance.corporation_name or \
|
||||
not instance.alliance_name == old_instance.alliance_name:
|
||||
logger.info("syncing service nickname for user {0}".format(instance.userprofile.user))
|
||||
|
||||
for svc in ServicesHook.get_services():
|
||||
try:
|
||||
svc.validate_user(instance.userprofile.user)
|
||||
svc.sync_nickname(instance.userprofile.user)
|
||||
except:
|
||||
logger.exception('Exception running sync_nickname for services module %s on user %s' % (svc, instance))
|
||||
|
||||
except ObjectDoesNotExist: # not a main char ignore
|
||||
pass
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
|
||||
{% block page_title %}
|
||||
{% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %}
|
||||
{% endblock page_title %}
|
||||
@@ -18,8 +17,8 @@
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Are you sure you want to delete your {{ view.service_name }} account {{ object }}?
|
||||
{% blocktrans trimmed with service_name=view.service_name|title %}
|
||||
Are you sure you want to delete your {{ service_name }} account {{ object }}?
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<input class="btn btn-danger btn-block" type="submit" value="Confirm" />
|
||||
|
||||
17
allianceauth/services/tests/test_models.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.test import TestCase
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..models import NameFormatConfig
|
||||
|
||||
|
||||
class TestNameFormatConfig(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
obj = NameFormatConfig.objects.create(
|
||||
service_name='mumble',
|
||||
format='{{character_name}}'
|
||||
)
|
||||
obj.states.add(AuthUtils.get_member_state())
|
||||
obj.states.add(AuthUtils.get_guest_state())
|
||||
expected = 'mumble: Member, Guest'
|
||||
self.assertEqual(str(obj), expected)
|
||||
@@ -1,21 +1,17 @@
|
||||
from allianceauth import NAME
|
||||
from esi.clients import esi_client_factory
|
||||
import requests
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
from allianceauth import NAME
|
||||
from allianceauth.eveonline.providers import provider
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||
"""
|
||||
Swagger Operations:
|
||||
get_killmails_killmail_id_killmail_hash
|
||||
"""
|
||||
|
||||
|
||||
class SRPManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_kill_id(killboard_link):
|
||||
num_set = '0123456789'
|
||||
@@ -34,18 +30,23 @@ class SRPManager:
|
||||
if result:
|
||||
killmail_id = result['killmail_id']
|
||||
killmail_hash = result['zkb']['hash']
|
||||
c = esi_client_factory(spec_file=SWAGGER_SPEC_PATH)
|
||||
km = c.Killmails.get_killmails_killmail_id_killmail_hash(killmail_id=killmail_id,
|
||||
killmail_hash=killmail_hash).result()
|
||||
c = provider.client
|
||||
km = c.Killmails.get_killmails_killmail_id_killmail_hash(
|
||||
killmail_id=killmail_id,
|
||||
killmail_hash=killmail_hash
|
||||
).result()
|
||||
else:
|
||||
raise ValueError("Invalid Kill ID")
|
||||
if km:
|
||||
ship_type = km['victim']['ship_type_id']
|
||||
logger.debug("Ship type for kill ID %s is %s" % (kill_id, ship_type))
|
||||
logger.debug(
|
||||
"Ship type for kill ID %s is %s" % (kill_id, ship_type)
|
||||
)
|
||||
ship_value = result['zkb']['totalValue']
|
||||
logger.debug("Total loss value for kill id %s is %s" % (kill_id, ship_value))
|
||||
logger.debug(
|
||||
"Total loss value for kill id %s is %s" % (kill_id, ship_value)
|
||||
)
|
||||
victim_id = km['victim']['character_id']
|
||||
return ship_type, ship_value, victim_id
|
||||
else:
|
||||
raise ValueError("Invalid Kill ID or Hash.")
|
||||
|
||||
|
||||
@@ -90,10 +90,9 @@
|
||||
<th class="text-center">{% trans "Ship Type" %}</th>
|
||||
<th class="text-center">{% trans "Killboard Loss Amt" %}</th>
|
||||
<th class="text-center">{% trans "SRP ISK Cost" %}
|
||||
{% blocktrans %}<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Click value to edit
|
||||
Enter to save&next
|
||||
ESC to cancel"
|
||||
id="blah"></i></th>{% endblocktrans %}
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="{% blocktrans trimmed %}Click value to edit
|
||||
Enter to save & next
|
||||
ESC to cancel{% endblocktrans %}"id="blah"></i></th>
|
||||
<th class="text-center">{% trans "Post Time" %}</th>
|
||||
<th class="text-center">{% trans "Status" %}</th>
|
||||
{% if perms.auth.srp_management %}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Create your tests here.
|
||||
0
allianceauth/srp/tests/__init__.py
Executable file
72
allianceauth/srp/tests/test_managers.py
Executable file
@@ -0,0 +1,72 @@
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ..managers import SRPManager
|
||||
|
||||
MODULE_PATH = 'allianceauth.srp.managers'
|
||||
|
||||
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(
|
||||
inspect.currentframe()
|
||||
)))
|
||||
|
||||
def load_data(filename):
|
||||
"""loads given JSON file from `testdata` sub folder and returns content"""
|
||||
with open(
|
||||
currentdir + '/testdata/%s.json' % filename, 'r', encoding='utf-8'
|
||||
) as f:
|
||||
data = json.load(f)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class TestSrpManager(TestCase):
|
||||
|
||||
def test_can_extract_kill_id(self):
|
||||
link = 'https://zkillboard.com/kill/81973979/'
|
||||
expected = 81973979
|
||||
self.assertEqual(int(SRPManager.get_kill_id(link)), expected)
|
||||
|
||||
@patch(MODULE_PATH + '.provider')
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
def test_can_get_kill_data(self, mock_get, mock_provider):
|
||||
mock_get.return_value.json.return_value = load_data(
|
||||
'zkillboard_killmail_api_81973979'
|
||||
)
|
||||
mock_provider.client.Killmails.\
|
||||
get_killmails_killmail_id_killmail_hash.return_value.\
|
||||
result.return_value = load_data(
|
||||
'get_killmails_killmail_id_killmail_hash_81973979'
|
||||
)
|
||||
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
self.assertEqual(ship_type, 19720)
|
||||
self.assertEqual(ship_value, 3177859026.86)
|
||||
self.assertEqual(victim_id, 93330670)
|
||||
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
def test_invalid_id_for_zkb_raises_exception(self, mock_get):
|
||||
mock_get.return_value.json.return_value = ['']
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
|
||||
@patch(MODULE_PATH + '.provider')
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
def test_invalid_id_for_esi_raises_exception(
|
||||
self, mock_get, mock_provider
|
||||
):
|
||||
mock_get.return_value.json.return_value = load_data(
|
||||
'zkillboard_killmail_api_81973979'
|
||||
)
|
||||
mock_provider.client.Killmails.\
|
||||
get_killmails_killmail_id_killmail_hash.return_value.\
|
||||
result.return_value = None
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
|
||||
|
||||
953
allianceauth/srp/tests/testdata/get_killmails_killmail_id_killmail_hash_81973979.json
vendored
Normal file
@@ -0,0 +1,953 @@
|
||||
{
|
||||
"attackers": [
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 92606407,
|
||||
"corporation_id": 98343297,
|
||||
"damage_done": 65236,
|
||||
"final_blow": false,
|
||||
"security_status": -6.4,
|
||||
"ship_type_id": 47271,
|
||||
"weapon_type_id": 47271
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95104060,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 56425,
|
||||
"final_blow": true,
|
||||
"security_status": -1.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"alliance_id": 1220922756,
|
||||
"character_id": 92793488,
|
||||
"corporation_id": 679468421,
|
||||
"damage_done": 55225,
|
||||
"final_blow": false,
|
||||
"security_status": -3.4,
|
||||
"ship_type_id": 47271,
|
||||
"weapon_type_id": 47271
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90376343,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 51941,
|
||||
"final_blow": false,
|
||||
"security_status": 0.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 676848606,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 45906,
|
||||
"final_blow": false,
|
||||
"security_status": -1.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96692394,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 44900,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96624133,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 44146,
|
||||
"final_blow": false,
|
||||
"security_status": -9.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"character_id": 95050100,
|
||||
"corporation_id": 98497860,
|
||||
"damage_done": 41517,
|
||||
"final_blow": false,
|
||||
"security_status": -3,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 458944878,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 39888,
|
||||
"final_blow": false,
|
||||
"security_status": -6.5,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96029663,
|
||||
"corporation_id": 98433294,
|
||||
"damage_done": 39406,
|
||||
"final_blow": false,
|
||||
"security_status": -6.7,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90626300,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 37808,
|
||||
"final_blow": false,
|
||||
"security_status": -0.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90740848,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 36342,
|
||||
"final_blow": false,
|
||||
"security_status": -1.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 1105550086,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 35971,
|
||||
"final_blow": false,
|
||||
"security_status": -2.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99003581,
|
||||
"character_id": 94727582,
|
||||
"corporation_id": 98514029,
|
||||
"damage_done": 33501,
|
||||
"final_blow": false,
|
||||
"security_status": 3.2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90368224,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 32116,
|
||||
"final_blow": false,
|
||||
"security_status": -2.4,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90001595,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 31387,
|
||||
"final_blow": false,
|
||||
"security_status": -0.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2456
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95278082,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 31250,
|
||||
"final_blow": false,
|
||||
"security_status": 1.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 91971344,
|
||||
"corporation_id": 98217414,
|
||||
"damage_done": 31247,
|
||||
"final_blow": false,
|
||||
"security_status": -1,
|
||||
"ship_type_id": 29986,
|
||||
"weapon_type_id": 29986
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2113448089,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 30174,
|
||||
"final_blow": false,
|
||||
"security_status": -0.4,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115912819,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 29242,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115885290,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 28009,
|
||||
"final_blow": false,
|
||||
"security_status": -2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95746094,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 27565,
|
||||
"final_blow": false,
|
||||
"security_status": -7.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99003581,
|
||||
"character_id": 90345487,
|
||||
"corporation_id": 98514029,
|
||||
"damage_done": 26016,
|
||||
"final_blow": false,
|
||||
"security_status": 0.5,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115874625,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 25679,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115880975,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 23320,
|
||||
"final_blow": false,
|
||||
"security_status": -3.5,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96667534,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 21699,
|
||||
"final_blow": false,
|
||||
"security_status": -0.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115866658,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 20506,
|
||||
"final_blow": false,
|
||||
"security_status": -1.3,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 97105982,
|
||||
"corporation_id": 98217414,
|
||||
"damage_done": 19400,
|
||||
"final_blow": false,
|
||||
"security_status": 0,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99008704,
|
||||
"character_id": 96110151,
|
||||
"corporation_id": 98614116,
|
||||
"damage_done": 17547,
|
||||
"final_blow": false,
|
||||
"security_status": -5.5,
|
||||
"ship_type_id": 17740,
|
||||
"weapon_type_id": 17740
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 90526637,
|
||||
"corporation_id": 98343297,
|
||||
"damage_done": 16791,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 33157,
|
||||
"weapon_type_id": 21640
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2112972140,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 16749,
|
||||
"final_blow": false,
|
||||
"security_status": -1.2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115879470,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 14402,
|
||||
"final_blow": false,
|
||||
"security_status": -3.9,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95698217,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 11546,
|
||||
"final_blow": false,
|
||||
"security_status": -5.2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 353190170,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 10896,
|
||||
"final_blow": false,
|
||||
"security_status": -4.7,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 91546798,
|
||||
"corporation_id": 98217414,
|
||||
"damage_done": 9872,
|
||||
"final_blow": false,
|
||||
"security_status": -0.7,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 91578428,
|
||||
"corporation_id": 302750157,
|
||||
"damage_done": 7699,
|
||||
"final_blow": false,
|
||||
"security_status": -8.7,
|
||||
"ship_type_id": 17920,
|
||||
"weapon_type_id": 2185
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 94105463,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 5265,
|
||||
"final_blow": false,
|
||||
"security_status": -3.3,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 2488
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95526304,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 3967,
|
||||
"final_blow": false,
|
||||
"security_status": -8.7,
|
||||
"ship_type_id": 22474,
|
||||
"weapon_type_id": 2488
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90331727,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 2940,
|
||||
"final_blow": false,
|
||||
"security_status": -1.2,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 2185
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115880459,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 2301,
|
||||
"final_blow": false,
|
||||
"security_status": 4.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009547,
|
||||
"character_id": 1832436128,
|
||||
"corporation_id": 98618666,
|
||||
"damage_done": 937,
|
||||
"final_blow": false,
|
||||
"security_status": -8.1,
|
||||
"ship_type_id": 17740,
|
||||
"weapon_type_id": 3186
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009547,
|
||||
"character_id": 96632877,
|
||||
"corporation_id": 98618666,
|
||||
"damage_done": 430,
|
||||
"final_blow": false,
|
||||
"security_status": -5.4,
|
||||
"ship_type_id": 17740,
|
||||
"weapon_type_id": 3186
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96146444,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 126,
|
||||
"final_blow": false,
|
||||
"security_status": -1.2,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 49713
|
||||
},
|
||||
{
|
||||
"character_id": 2116393370,
|
||||
"corporation_id": 98593091,
|
||||
"damage_done": 111,
|
||||
"final_blow": false,
|
||||
"security_status": 0,
|
||||
"ship_type_id": 602,
|
||||
"weapon_type_id": 27321
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 93745147,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 6,
|
||||
"final_blow": false,
|
||||
"security_status": -1.6,
|
||||
"ship_type_id": 12021,
|
||||
"weapon_type_id": 2873
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95610468,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 4,
|
||||
"final_blow": false,
|
||||
"security_status": -2.2,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 484
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 92304254,
|
||||
"corporation_id": 98343297,
|
||||
"damage_done": 1,
|
||||
"final_blow": false,
|
||||
"security_status": -9.3,
|
||||
"ship_type_id": 22474,
|
||||
"weapon_type_id": 22474
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96624034,
|
||||
"corporation_id": 98493618,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 37611
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95388762,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": 0.4,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 3001
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 1290463210,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 23707
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009547,
|
||||
"character_id": 95748579,
|
||||
"corporation_id": 98618666,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -7.9,
|
||||
"ship_type_id": 643,
|
||||
"weapon_type_id": 16497
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2114899882,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -1.7,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 484
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95624225,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -9.2,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 37608
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 93452185,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 22474,
|
||||
"weapon_type_id": 7537
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2114109824,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 484
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2113100583,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -0.2,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 484
|
||||
}
|
||||
],
|
||||
"killmail_id": 81973979,
|
||||
"killmail_time": "2020-03-01T13:10:55Z",
|
||||
"solar_system_id": 30002537,
|
||||
"victim": {
|
||||
"alliance_id": 99009333,
|
||||
"character_id": 93330670,
|
||||
"corporation_id": 98267621,
|
||||
"damage_taken": 1127412,
|
||||
"items": [
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41332,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41332,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 27,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 33474,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 18,
|
||||
"item_type_id": 2048,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 28,
|
||||
"item_type_id": 4292,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 29001,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 22,
|
||||
"item_type_id": 41218,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 133,
|
||||
"item_type_id": 16275,
|
||||
"quantity_destroyed": 1125,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41330,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41330,
|
||||
"quantity_dropped": 4,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 94,
|
||||
"item_type_id": 31452,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20028,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20028,
|
||||
"quantity_dropped": 2,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 41489,
|
||||
"quantity_dropped": 48,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 13,
|
||||
"item_type_id": 18708,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 21,
|
||||
"item_type_id": 1978,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20026,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 29,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 21,
|
||||
"item_type_id": 29001,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 16275,
|
||||
"quantity_dropped": 1250,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 16299,
|
||||
"quantity_destroyed": 6,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 16299,
|
||||
"quantity_dropped": 2,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41489,
|
||||
"quantity_dropped": 12,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 29,
|
||||
"item_type_id": 37298,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 17,
|
||||
"item_type_id": 40351,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 19,
|
||||
"item_type_id": 41491,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 12,
|
||||
"item_type_id": 2364,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20030,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 14,
|
||||
"item_type_id": 18708,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 28999,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 93,
|
||||
"item_type_id": 30993,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 11,
|
||||
"item_type_id": 2364,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 20,
|
||||
"item_type_id": 29001,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 15,
|
||||
"item_type_id": 40351,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 19,
|
||||
"item_type_id": 41489,
|
||||
"quantity_dropped": 4,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 21254,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 21254,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20032,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20032,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 16,
|
||||
"item_type_id": 40351,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20843,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 31,
|
||||
"item_type_id": 37298,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20845,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 92,
|
||||
"item_type_id": 30993,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 31,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 133,
|
||||
"item_type_id": 16274,
|
||||
"quantity_dropped": 141666,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 20,
|
||||
"item_type_id": 1978,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 6,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20847,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 21246,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 27,
|
||||
"item_type_id": 37298,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 90,
|
||||
"item_type_id": 585,
|
||||
"items": [
|
||||
{
|
||||
"flag": 94,
|
||||
"item_type_id": 31159,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 20,
|
||||
"item_type_id": 3831,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 93,
|
||||
"item_type_id": 31159,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 22,
|
||||
"item_type_id": 9568,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 12,
|
||||
"item_type_id": 1405,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 92,
|
||||
"item_type_id": 31159,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 21,
|
||||
"item_type_id": 2553,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 11,
|
||||
"item_type_id": 1405,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 19,
|
||||
"item_type_id": 5971,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
}
|
||||
],
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"x": 55026869426.47358,
|
||||
"y": 7310382040.828209,
|
||||
"z": -163690355689.8978
|
||||
},
|
||||
"ship_type_id": 19720
|
||||
}
|
||||
}
|
||||
15
allianceauth/srp/tests/testdata/zkillboard_killmail_api_81973979.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"killmail_id": 81973979,
|
||||
"zkb": {
|
||||
"locationID": 60004816,
|
||||
"hash": "e88a5fa7f342fa658ebe74a055b7679e28b05628",
|
||||
"fittedValue": 1532365686.21,
|
||||
"totalValue": 3177859026.86,
|
||||
"points": 1,
|
||||
"npc": false,
|
||||
"solo": false,
|
||||
"awox": false
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -8,7 +8,7 @@ from django.contrib.humanize.templatetags.humanize import intcomma
|
||||
from django.http import JsonResponse, Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models import Sum
|
||||
from allianceauth.authentication.decorators import permissions_required
|
||||
from allianceauth.eveonline.providers import provider
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
ReferrerUrl=https://github.com/ErikKalkoken/filterDropDown/blob/master/js/filterDropDown.min.js
|
||||
HostUrl=https://raw.githubusercontent.com/ErikKalkoken/filterDropDown/master/js/filterDropDown.min.js
|
||||
@@ -89,7 +89,7 @@
|
||||
{% if task_queue_length < 0 %}
|
||||
{% trans "Error retrieving task queue length" %}
|
||||
{% else %}
|
||||
{% blocktrans count tasks=task_queue_length %}
|
||||
{% blocktrans trimmed count tasks=task_queue_length %}
|
||||
{{ tasks }} task
|
||||
{% plural %}
|
||||
{{ tasks }} tasks
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% trans "Help" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
|
||||
<h1 class="page-header text-center">{% trans "Help" %}</h1>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<iframe class="embed-responsive-item" src="https://allianceauth.readthedocs.io/en/latest/features/"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -26,15 +26,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% menu_items %}
|
||||
|
||||
{% if user.is_superuser %}
|
||||
<li>
|
||||
<a class="{% navactive request 'authentication:help' %}"
|
||||
href="{% url 'authentication:help' %}">
|
||||
<i class="fa fa-question fa-fw"></i> {% trans "Help" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,13 @@
|
||||
{% else %}
|
||||
<li><a href="{% url 'authentication:login' %}">{% trans "Login" %}</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_superuser %}
|
||||
<li>
|
||||
<a class="navbar-brand" style="margin-left: -4px;" href="https://allianceauth.readthedocs.io/" target="_blank">
|
||||
<i class="fa fa-question-circle fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
from allianceauth.authentication.models import UserProfile, State, get_guest_state
|
||||
from allianceauth.authentication.signals import state_member_alliances_changed, state_member_characters_changed, \
|
||||
state_member_corporations_changed, state_saved
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.db.models.signals import m2m_changed, pre_save, post_save
|
||||
from django.test import TestCase
|
||||
from esi.models import Token
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
from allianceauth.services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, \
|
||||
m2m_changed_state_permissions
|
||||
from allianceauth.services.signals import m2m_changed_user_groups, disable_services_on_inactive
|
||||
from esi.models import Token
|
||||
|
||||
from allianceauth.authentication.models import (
|
||||
UserProfile, State, get_guest_state
|
||||
)
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from allianceauth.authentication.signals import (
|
||||
state_member_alliances_changed,
|
||||
state_member_characters_changed,
|
||||
state_member_corporations_changed,
|
||||
state_saved,
|
||||
reassess_on_profile_save,
|
||||
assign_state_on_active_change,
|
||||
check_state_on_character_update
|
||||
)
|
||||
from allianceauth.services.signals import (
|
||||
m2m_changed_group_permissions,
|
||||
m2m_changed_user_permissions,
|
||||
m2m_changed_state_permissions,
|
||||
m2m_changed_user_groups, disable_services_on_inactive
|
||||
)
|
||||
|
||||
|
||||
class AuthUtils:
|
||||
def __init__(self):
|
||||
pass
|
||||
"""Utilities for making it easier to create tests for Alliance Auth"""
|
||||
|
||||
@staticmethod
|
||||
def _create_user(username):
|
||||
def _create_user(username):
|
||||
return User.objects.create(username=username)
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, username, disconnect_signals=False):
|
||||
"""create a new user
|
||||
|
||||
username: Name of the user
|
||||
|
||||
disconnect_signals: whether to run process without signals
|
||||
"""
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
@@ -95,6 +113,11 @@ class AuthUtils:
|
||||
m2m_changed.disconnect(state_member_characters_changed, sender=State.member_characters.through)
|
||||
m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through)
|
||||
post_save.disconnect(state_saved, sender=State)
|
||||
post_save.disconnect(reassess_on_profile_save, sender=UserProfile)
|
||||
pre_save.disconnect(assign_state_on_active_change, sender=User)
|
||||
post_save.disconnect(
|
||||
check_state_on_character_update, sender=EveCharacter
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def connect_signals(cls):
|
||||
@@ -107,6 +130,9 @@ class AuthUtils:
|
||||
m2m_changed.connect(state_member_characters_changed, sender=State.member_characters.through)
|
||||
m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through)
|
||||
post_save.connect(state_saved, sender=State)
|
||||
post_save.connect(reassess_on_profile_save, sender=UserProfile)
|
||||
pre_save.connect(assign_state_on_active_change, sender=User)
|
||||
post_save.connect(check_state_on_character_update, sender=EveCharacter)
|
||||
|
||||
@classmethod
|
||||
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='',
|
||||
@@ -122,6 +148,40 @@ class AuthUtils:
|
||||
)
|
||||
UserProfile.objects.update_or_create(user=user, defaults={'main_character': char})
|
||||
|
||||
@classmethod
|
||||
def add_main_character_2(
|
||||
cls,
|
||||
user,
|
||||
name,
|
||||
character_id,
|
||||
corp_id='',
|
||||
corp_name='',
|
||||
corp_ticker='',
|
||||
alliance_id='',
|
||||
alliance_name='',
|
||||
disconnect_signals=False
|
||||
):
|
||||
"""new version that works in all cases"""
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
|
||||
char = EveCharacter.objects.create(
|
||||
character_id=character_id,
|
||||
character_name=name,
|
||||
corporation_id=corp_id,
|
||||
corporation_name=corp_name,
|
||||
corporation_ticker=corp_ticker,
|
||||
alliance_id=alliance_id,
|
||||
alliance_name=alliance_name,
|
||||
)
|
||||
user.profile.main_character = char
|
||||
user.profile.save()
|
||||
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
|
||||
return char
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
|
||||
if disconnect_signals:
|
||||
@@ -130,14 +190,67 @@ class AuthUtils:
|
||||
for group in groups:
|
||||
for perm in perms:
|
||||
group.permissions.add(perm)
|
||||
group = Group.objects.get(pk=group.pk) # reload permission cache
|
||||
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_state(cls, perms, states, disconnect_signals=True):
|
||||
return cls.add_permissions_to_groups(perms, states, disconnect_signals=disconnect_signals)
|
||||
return cls.add_permissions_to_groups(
|
||||
perms, states, disconnect_signals=disconnect_signals
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_user(cls, perms, user, disconnect_signals=True):
|
||||
"""add list of permissions to user
|
||||
|
||||
perms: list of Permission objects
|
||||
|
||||
user: user object
|
||||
|
||||
disconnect_signals: whether to run process without signals
|
||||
"""
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
|
||||
for perm in perms:
|
||||
user.user_permissions.add(perm)
|
||||
|
||||
user = User.objects.get(pk=user.pk) # reload permission cache
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
|
||||
@classmethod
|
||||
def add_permission_to_user_by_name(
|
||||
cls, perm, user, disconnect_signals=True
|
||||
):
|
||||
"""returns permission specified by qualified name
|
||||
|
||||
perm: Permission name as 'app_label.codename'
|
||||
|
||||
user: user object
|
||||
|
||||
disconnect_signals: whether to run process without signals
|
||||
"""
|
||||
p = cls.get_permission_by_name(perm)
|
||||
cls.add_permissions_to_user([p], user, disconnect_signals)
|
||||
|
||||
@staticmethod
|
||||
def get_permission_by_name(perm: str) -> Permission:
|
||||
"""returns permission specified by qualified name
|
||||
|
||||
perm: Permission name as 'app_label.codename'
|
||||
|
||||
Returns: Permission object or throws exception if not found
|
||||
"""
|
||||
perm_parts = perm.split('.')
|
||||
if len(perm_parts) != 2:
|
||||
raise ValueError('Invalid format for permission name')
|
||||
|
||||
return Permission.objects.get(
|
||||
content_type__app_label=perm_parts[0], codename=perm_parts[1]
|
||||
)
|
||||
|
||||
class BaseViewTestCase(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
60
allianceauth/tests/test_auth_utils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
)
|
||||
|
||||
from .auth_utils import AuthUtils
|
||||
|
||||
|
||||
class TestAuthUtils(TestCase):
|
||||
|
||||
def test_can_create_user(self):
|
||||
user = AuthUtils.create_user('Bruce Wayne')
|
||||
self.assertTrue(User.objects.filter(username='Bruce Wayne').exists())
|
||||
|
||||
def test_can_add_main_character_2(self):
|
||||
user = AuthUtils.create_user('Bruce Wayne')
|
||||
character = AuthUtils.add_main_character_2(
|
||||
user,
|
||||
name='Bruce Wayne',
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name='Wayne Technologies',
|
||||
corp_ticker='WYT',
|
||||
alliance_id=3001,
|
||||
alliance_name='Wayne Enterprises'
|
||||
)
|
||||
expected = character
|
||||
self.assertEqual(user.profile.main_character, expected)
|
||||
|
||||
def test_can_add_permission_to_group(self):
|
||||
group = Group.objects.create(name='Dummy Group')
|
||||
p = AuthUtils.get_permission_by_name('auth.group_management')
|
||||
AuthUtils.add_permissions_to_groups([p], [group])
|
||||
self.assertTrue(group.permissions.filter(pk=p.pk).exists())
|
||||
|
||||
def test_can_add_permission_to_user_by_name(self):
|
||||
user = AuthUtils.create_user('Bruce Wayne')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.timer_management', user
|
||||
)
|
||||
self.assertTrue(user.has_perm('auth.timer_management'))
|
||||
|
||||
|
||||
class TestGetPermissionByName(TestCase):
|
||||
|
||||
def test_can_get_permission_by_name(self):
|
||||
expected = Permission.objects.get(
|
||||
content_type__app_label='auth', codename='timer_management'
|
||||
)
|
||||
self.assertEqual(
|
||||
AuthUtils.get_permission_by_name('auth.timer_management'), expected
|
||||
)
|
||||
|
||||
def test_raises_exception_on_invalid_permission_format(self):
|
||||
with self.assertRaises(ValueError):
|
||||
AuthUtils.get_permission_by_name('timer_management')
|
||||
@@ -1,13 +1,15 @@
|
||||
from django.views.generic.base import View
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
class NightModeRedirectView(View):
|
||||
SESSION_VAR = 'NIGHT_MODE'
|
||||
SESSION_VAR = "NIGHT_MODE"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
request.session[self.SESSION_VAR] = not self.night_mode_state(request)
|
||||
return HttpResponseRedirect(request.GET.get('next', '/'))
|
||||
return HttpResponseRedirect(request.GET.get("next", "/"))
|
||||
|
||||
@classmethod
|
||||
def night_mode_state(cls, request):
|
||||
@@ -17,3 +19,39 @@ class NightModeRedirectView(View):
|
||||
# Session is middleware
|
||||
# Sometimes request wont have a session attribute
|
||||
return False
|
||||
|
||||
|
||||
def Generic500Redirect(request):
|
||||
messages.error(
|
||||
request,
|
||||
"Auth encountered an error processing your request, please try again. "
|
||||
"If the error persists, please contact the administrators. (500 Internal Server Error)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
def Generic404Redirect(request, exception):
|
||||
messages.error(
|
||||
request,
|
||||
"Page does not exist. If you believe this is in error please contact the administrators. "
|
||||
"(404 Page Not Found)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
def Generic403Redirect(request, exception):
|
||||
messages.error(
|
||||
request,
|
||||
"You do not have permission to access the requested page. "
|
||||
"If you believe this is in error please contact the administrators. (403 Permission Denied)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
def Generic400Redirect(request, exception):
|
||||
messages.error(
|
||||
request,
|
||||
"Auth encountered an error processing your request, please try again. "
|
||||
"If the error persists, please contact the administrators. (400 Bad Request)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
BIN
docs/_static/images/development/aa_core.png
vendored
Normal file
|
After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 903 B |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |