Compare commits

...

55 Commits

Author SHA1 Message Date
Ariel Rin
321af5ec87 Version Bump v2.6.4 2020-04-17 06:56:40 +00:00
Ariel Rin
9ccf340b3d Merge branch 'error-redirects' into 'master'
Add 500 and 400, 403, 404 error redirects back to dashboard with basic message

See merge request allianceauth/allianceauth!1152
2020-04-17 06:45:01 +00:00
Aaron Kable
d7dcacb899 Add 500 and 400, 403, 404 error redirects back to dashboard with basic message 2020-04-17 06:45:01 +00:00
Ariel Rin
8addd483c2 Merge branch 'issue_1221' into 'master'
Remove support for Python 3.5

Closes #1224 and #1221

See merge request allianceauth/allianceauth!1182
2020-04-15 11:25:44 +00:00
Ariel Rin
4d27e5ac9b Merge branch 'improve_help_icon' into 'master'
Improve style of help icon

See merge request allianceauth/allianceauth!1192
2020-04-15 11:24:55 +00:00
ErikKalkoken
31290f6e80 Improve style of help icon 2020-04-11 20:23:41 +02:00
Ariel Rin
c31cc4dbee Merge branch 'mumble_displaynames' into 'master'
Mumble Display Names

See merge request allianceauth/allianceauth!1185
2020-04-06 02:19:53 +00:00
Aaron Kable
cc1f94cf61 Mumble Display Names 2020-04-06 02:19:53 +00:00
Ariel Rin
a9132b8d50 Merge branch 'fix_dev_docs' into 'master'
Add config file for readthedocs

See merge request allianceauth/allianceauth!1191
2020-04-03 13:33:30 +00:00
ErikKalkoken
7b4a9891aa Add config file for readthedocs 2020-04-03 15:18:45 +02:00
Ariel Rin
dcaaf38ecc Merge branch 'esi_update' into 'master'
Update swagger files and remove swagger file dependency from srp package

See merge request allianceauth/allianceauth!1187
2020-04-03 12:39:34 +00:00
Ariel Rin
653a8aa850 Merge branch 'improve_docu_link' into 'master'
Move docu link to top menu and open in new window

See merge request allianceauth/allianceauth!1189
2020-04-03 12:04:06 +00:00
Ariel Rin
274af11385 Merge branch 'docu_devs_update' into 'master'
Extend developer docs

See merge request allianceauth/allianceauth!1188
2020-04-03 12:03:34 +00:00
Erik Kalkoken
170b246901 Extend developer docs 2020-04-03 12:03:34 +00:00
Ariel Rin
5250432ce3 Version Bump v2.6.3 2020-04-02 03:51:59 +00:00
Ariel Rin
53d6e973eb Merge branch 'i18n-chinese' into 'master'
Update Translations from Transifex

See merge request allianceauth/allianceauth!1190
2020-04-02 03:32:39 +00:00
Ariel Rin
c9bdd62d53 Update Translations from Transifex 2020-04-02 03:32:39 +00:00
Ariel Rin
7eb98af528 Merge branch 'issue_1225' into 'master'
Fix broken link and remove outdated migrations for services name formatter

Closes #1225

See merge request allianceauth/allianceauth!1183
2020-04-02 03:04:55 +00:00
Ariel Rin
385e3e21b3 Merge branch 'improve_groups_view' into 'master'
Add sorting to group view and add tests to group management

See merge request allianceauth/allianceauth!1172
2020-04-02 03:01:27 +00:00
Erik Kalkoken
127ec63d76 Add sorting to group view and add tests to group management 2020-04-02 03:01:27 +00:00
Ariel Rin
4988b5f260 Merge branch 'common_logger' into 'master'
Extensions Logging

See merge request allianceauth/allianceauth!1180
2020-04-02 02:59:55 +00:00
Ariel Rin
f28a50f92c Merge branch 'fix_translations_3' into 'master'
Add missing translations

See merge request allianceauth/allianceauth!1186
2020-03-26 03:06:11 +00:00
Ariel Rin
e8efe8e609 Merge branch 'i18n-chinese' into 'master'
Add Korean and Russian, Update from Transifex

See merge request allianceauth/allianceauth!1179
2020-03-26 02:50:25 +00:00
Ariel Rin
d7e7457bc5 Add Korean and Russian, Update from Transifex 2020-03-26 02:50:25 +00:00
Ariel Rin
daff927811 Merge branch 'prioritize_celery' into 'master'
Add Celery Priorities

See merge request allianceauth/allianceauth!1181
2020-03-26 02:20:02 +00:00
Aaron Kable
8861ec0a61 Add Celery Priorities 2020-03-26 02:20:02 +00:00
Ariel Rin
bd4321f61a Merge branch 'docs_update' into 'master'
Docs only: Harmonize gunicorn config, add localization feature

See merge request allianceauth/allianceauth!1184
2020-03-26 01:55:03 +00:00
Erik Kalkoken
d831482fe0 Docs only: Harmonize gunicorn config, add localization feature 2020-03-26 01:55:03 +00:00
ErikKalkoken
9ea79ea389 Move docu link to top menu and open in new window 2020-03-26 00:10:30 +01:00
ErikKalkoken
b6fdf840ef Update swagger files and remove swagger fle dependency from srp package 2020-03-25 18:00:23 +01:00
ErikKalkoken
73f262ce4b Add missing translations 2020-03-24 20:21:35 +01:00
ErikKalkoken
f63434adc3 Fix broken link and remove outdated migrations for services name formatter 2020-03-21 14:41:45 +01:00
ErikKalkoken
42948386ec Remove support for Python 3.5 2020-03-21 13:16:42 +01:00
Ariel Rin
32e0621b0a Merge branch 'improve_install_docu' into 'master'
Docs: Add python upgrade guide, remove old AA 1.15 upgrade guide, improve install guide

See merge request allianceauth/allianceauth!1177
2020-03-15 12:21:21 +00:00
Erik Kalkoken
78e05b84e9 Docs: Add python upgrade guide, remove old AA 1.15 upgrade guide, improve install guide 2020-03-15 12:21:21 +00:00
Col Crunch
76ebd21163 Add function to services.hooks to provide a concise way for creating loggers for extensions/plugins. Revise basic documentation to use this function. 2020-03-13 15:21:15 -04:00
Col Crunch
38aaf545c6 Add some very basic docs for logging changes 2020-03-13 14:42:09 -04:00
Col Crunch
527d7ef671 Change level of extension_file handler, rename the logger from allianceauth.extensions to extensions and remove propagate from the logger. 2020-03-13 04:42:09 -04:00
Col Crunch
e54b80e061 Add a common logger (and specific log file) for extensions to utilize. 2020-03-13 00:33:35 -04:00
Col Crunch
27f95a8b2c Remove Zone.Indentifier files. 2020-03-12 23:55:34 -04:00
Ariel Rin
a1e8903128 Version Bump v2.6.2 2020-03-09 15:53:51 +00:00
Ariel Rin
b00ac2aef4 Merge branch 'i18n-chinese' into 'master'
Update German and Spanish Locales, Add Chinese Simplified with Transifex

See merge request allianceauth/allianceauth!1174
2020-03-09 15:51:12 +00:00
Ariel Rin
8865d15ed9 Update German and Spanish Locales, Add Chinese Simplified with Transifex 2020-03-09 15:51:12 +00:00
Ariel Rin
fc3d4b7f33 Merge branch 'hotfix_groups' into 'master'
HOTFIX GroupManager for Groups as Group Leads

See merge request allianceauth/allianceauth!1178
2020-03-09 08:17:41 +00:00
Ariel Rin
934cc44540 Merge branch 'fix_dashboard_sorting' into 'master'
Improve groups and character lists on dashboard

See merge request allianceauth/allianceauth!1171
2020-03-09 08:14:26 +00:00
AaronKable
106de3dd4c HOTFIX Group manager for Groups as Group Leads 2020-03-09 15:48:30 +08:00
Ariel Rin
9b55cfcbe3 Merge branch 'issue_1214' into 'master'
Documentation overhaul

Closes #1216 and #1214

See merge request allianceauth/allianceauth!1166
2020-03-05 02:23:58 +00:00
Erik Kalkoken
8137f1023a Documentation overhaul 2020-03-05 02:23:58 +00:00
Ariel Rin
d670e33b6f Merge branch 'fix_translation_strings_2' into 'master'
fix broken translation strings (part 2)

See merge request allianceauth/allianceauth!1176
2020-03-04 23:00:08 +00:00
ErikKalkoken
3d3bb8fc94 fix broken translation strings 2020-03-03 13:53:55 +01:00
Ariel Rin
9c880eae8a Merge branch 'fix_translation_string_bugs' into 'master'
Fix translation string bugs

See merge request allianceauth/allianceauth!1175
2020-03-03 00:52:18 +00:00
ErikKalkoken
54a71630f1 Fix translation string bugs 2020-02-29 15:55:42 +01:00
Ariel Rin
923a8453cc Merge branch 'fix_coverage_for_core' into 'master'
Remove coverage output from core tests

See merge request allianceauth/allianceauth!1173
2020-02-22 15:57:48 +00:00
ErikKalkoken
00447ca819 Remove coverage output from core tests 2020-02-22 16:50:04 +01:00
ErikKalkoken
81af610c11 Add sorting to characters and groups and remove auto groups 2020-02-19 01:29:14 +01:00
188 changed files with 14702 additions and 3467 deletions

6
.gitignore vendored
View File

@@ -69,3 +69,9 @@ celerybeat-schedule
#gitlab configs #gitlab configs
.gitlab/ .gitlab/
#transifex
.tx/
#other
.flake8

View File

@@ -6,11 +6,6 @@ before_script:
- python -V - python -V
- pip install wheel tox - pip install wheel tox
test-3.5-core:
image: python:3.5-buster
script:
- tox -e py35-core
test-3.6-core: test-3.6-core:
image: python:3.6-buster image: python:3.6-buster
script: script:
@@ -26,11 +21,6 @@ test-3.8-core:
script: script:
- tox -e py38-core - tox -e py38-core
test-3.5-all:
image: python:3.5-buster
script:
- tox -e py35-all
test-3.6-all: test-3.6-all:
image: python:3.6-buster image: python:3.6-buster
script: script:

27
.readthedocs.yml Normal file
View 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

View File

@@ -11,32 +11,34 @@
An auth system for EVE Online to help in-game organizations manage online service access. An auth system for EVE Online to help in-game organizations manage online service access.
## Contens ## Content
- [Overview](#overview) - [Overview](#overview)
- [Documentation](http://allianceauth.rtfd.io) - [Documentation](http://allianceauth.rtfd.io)
- [Support](#support) - [Support](#support)
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases) - [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
- [Devloper Team](#developer-team) - [Developer Team](#developer-team)
- [Contributing](#contributing) - [Contributing](#contributing)
## Overview ## 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: 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. - 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 ## Screenshot

View File

@@ -1,6 +1,6 @@
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
__version__ = '2.6.1' __version__ = '2.6.4'
NAME = 'Alliance Auth v%s' % __version__ NAME = 'Alliance Auth v%s' % __version__
default_app_config = 'allianceauth.apps.AllianceAuthConfig' default_app_config = 'allianceauth.apps.AllianceAuthConfig'

View File

@@ -506,7 +506,7 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
'character', 'character',
) )
search_fields = ( search_fields = (
'user__user', 'user__username',
'character__character_name', 'character__character_name',
'character__corporation_name', 'character__corporation_name',
'character__alliance_name' 'character__alliance_name'

View File

@@ -94,12 +94,12 @@
<div class="col-sm-6 text-center"> <div class="col-sm-6 text-center">
<div class="panel panel-success" style="height:100%"> <div class="panel panel-success" style="height:100%">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans "Groups" %}</h3> <h3 class="panel-title">{% trans "Group Memberships" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;"> <div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<table class="table table-aa"> <table class="table table-aa">
{% for group in user.groups.all %} {% for group in groups %}
<tr> <tr>
<td>{{ group.name }}</td> <td>{{ group.name }}</td>
</tr> </tr>
@@ -128,16 +128,14 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for ownership in request.user.character_ownerships.all %} {% for char in characters %}
{% with ownership.character as char %} <tr>
<tr> <td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}"> </td>
</td> <td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.character_name }}</td> <td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td> <td class="text-center">{{ char.alliance_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td> </tr>
</tr>
{% endwith %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@@ -1,5 +1,5 @@
{% load i18n %}{% autoescape off %} {% 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 %} user account.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %} {% trans "Please go to the following page and choose a new password:" %}

View File

@@ -2,10 +2,17 @@ from django.urls import reverse
def get_admin_change_view_url(obj: object) -> str: def get_admin_change_view_url(obj: object) -> str:
"""returns URL to admin change view for given object"""
return reverse( return reverse(
'admin:{}_{}_change'.format( 'admin:{}_{}_change'.format(
obj._meta.app_label, obj._meta.app_label, type(obj).__name__.lower()
type(obj).__name__.lower()
), ),
args=(obj.pk,) 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()
) )

View File

@@ -1,3 +1,4 @@
from urllib.parse import quote
from unittest.mock import patch from unittest.mock import patch
from django.conf import settings 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.contrib.auth.models import User as BaseUser, Group
from django.test import TestCase, RequestFactory, Client from django.test import TestCase, RequestFactory, Client
from allianceauth.authentication.models import CharacterOwnership, State, \ from allianceauth.authentication.models import (
OwnershipRecord CharacterOwnership, State, OwnershipRecord
)
from allianceauth.eveonline.models import ( from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo EveCharacter, EveCorporationInfo, EveAllianceInfo
) )
@@ -28,7 +30,7 @@ from ..admin import (
user_username, user_username,
update_main_character_model 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: if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True _has_auto_groups = True
@@ -175,6 +177,17 @@ def create_test_data():
return user_1, user_2, user_3, group_1, group_2 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): class TestCharacterOwnershipAdmin(TestCase):
@classmethod @classmethod
@@ -197,6 +210,14 @@ class TestCharacterOwnershipAdmin(TestCase):
response = c.get(get_admin_change_view_url(ownership)) response = c.get(get_admin_change_view_url(ownership))
self.assertEqual(response.status_code, 200) 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): class TestOwnershipRecordAdmin(TestCase):
@@ -222,6 +243,12 @@ class TestOwnershipRecordAdmin(TestCase):
response = c.get(get_admin_change_view_url(ownership_record)) response = c.get(get_admin_change_view_url(ownership_record))
self.assertEqual(response.status_code, 200) 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): class TestStateAdmin(TestCase):
@@ -250,6 +277,11 @@ class TestStateAdmin(TestCase):
response = c.get(get_admin_change_view_url(member_state)) response = c.get(get_admin_change_view_url(member_state))
self.assertEqual(response.status_code, 200) 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): class TestUserAdmin(TestCase):
@@ -541,3 +573,9 @@ class TestUserAdmin(TestCase):
c.login(username='superuser', password='secret') c.login(username='superuser', password='secret')
response = c.get(get_admin_change_view_url(self.user_1)) response = c.get(get_admin_change_view_url(self.user_1))
self.assertEqual(response.status_code, 200) 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)

View File

@@ -7,11 +7,21 @@ from . import views
app_name = 'authentication' app_name = 'authentication'
urlpatterns = [ urlpatterns = [
url(r'^$', login_required(TemplateView.as_view(template_name='authentication/dashboard.html')),), url(r'^$', views.index, name='index'),
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='login'), url(
url(r'^account/characters/main/$', views.main_character_change, name='change_main_character'), r'^account/login/$',
url(r'^account/characters/add/$', views.add_character, name='add_character'), TemplateView.as_view(template_name='public/login.html'),
url(r'^help/$', login_required(TemplateView.as_view(template_name='allianceauth/help.html')), name='help'), name='login'
url(r'^dashboard/$', ),
login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'), 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'),
] ]

View File

@@ -7,20 +7,58 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import signing from django.core import signing
from django.urls import reverse from django.urls import reverse
from django.shortcuts import redirect from django.shortcuts import redirect, render
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter
from esi.decorators import token_required from esi.decorators import token_required
from esi.models import Token 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 registration.signals import user_registered
from .models import CharacterOwnership from .models import CharacterOwnership
from .forms import RegistrationForm 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__) 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 @login_required
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES) @token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
def main_character_change(request, token): 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(): if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
co = CharacterOwnership.objects.create_by_token(token) co = CharacterOwnership.objects.create_by_token(token)
else: 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 co = None
if co: if co:
request.user.profile.main_character = co.character request.user.profile.main_character = co.character

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,7 @@ from .models import EveCorporationInfo
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TASK_PRIORITY = 7
@shared_task @shared_task
def update_corp(corp_id): def update_corp(corp_id):
@@ -27,11 +28,12 @@ def update_character(character_id):
def run_model_update(): def run_model_update():
# update existing corp models # update existing corp models
for corp in EveCorporationInfo.objects.all().values('corporation_id'): 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 # update existing alliance models
for alliance in EveAllianceInfo.objects.all().values('alliance_id'): 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'): 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)

View File

@@ -80,28 +80,28 @@ class TestTasks(TestCase):
character_name='character.name', character_name='character.name',
corporation_id='character.corp.id', corporation_id='character.corp.id',
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='c.c.t', # max 5 chars
alliance_id='character.alliance.id', alliance_id='character.alliance.id',
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
run_model_update() run_model_update()
self.assertEqual(mock_update_corp.delay.call_count, 1) self.assertEqual(mock_update_corp.apply_async.call_count, 1)
self.assertEqual( self.assertEqual(
int(mock_update_corp.delay.call_args[0][0]), int(mock_update_corp.apply_async.call_args[1]['args'][0]),
2345 2345
) )
self.assertEqual(mock_update_alliance.delay.call_count, 1) self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
self.assertEqual( self.assertEqual(
int(mock_update_alliance.delay.call_args[0][0]), int(mock_update_alliance.apply_async.call_args[1]['args'][0]),
3456 3456
) )
self.assertEqual(mock_update_character.delay.call_count, 1) self.assertEqual(mock_update_character.apply_async.call_count, 1)
self.assertEqual( self.assertEqual(
int(mock_update_character.delay.call_args[0][0]), int(mock_update_character.apply_async.call_args[1]['args'][0]),
1234 1234
) )

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,13 @@
</div> </div>
{% endif %} {% endif %}
</h1> </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"> <table class="table table-responsive">
<tr> <tr>
<th class="col-md-2 text-center">{% trans "Ship" %}</th> <th class="col-md-2 text-center">{% trans "Ship" %}</th>
@@ -29,7 +35,13 @@
{% endfor %} {% endfor %}
</table> </table>
{% if created_fats %} {% 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 %} {% if created_fats %}
<table class="table"> <table class="table">
<tr> <tr>

View File

@@ -1,27 +1,53 @@
from django.contrib.auth.models import Group import logging
from django.db.models import Q
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: 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 @staticmethod
def get_joinable_groups(state): def get_joinable_groups(state: State) -> QuerySet:
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)\ """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)) .filter(Q(authgroup__states=state) | Q(authgroup__states=None))
@staticmethod @staticmethod
def get_all_non_internal_groups(): def get_all_non_internal_groups() -> QuerySet:
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True) """get groups that are not internal"""
return Group.objects\
.select_related('authgroup')\
.exclude(authgroup__internal=True)
@staticmethod @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]) | \ 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()) Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
@staticmethod @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. Check if a group is a user/state joinable group, i.e.
not an internal group for Corp, Alliance, Members etc, not an internal group for Corp, Alliance, Members etc,
@@ -30,12 +56,15 @@ class GroupManager:
:param state: allianceauth.authentication.State object :param state: allianceauth.authentication.State object
:return: bool True if its joinable, False otherwise :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 False
return not group.authgroup.internal else:
return not group.authgroup.internal
@staticmethod @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 Check if a group is auditable, i.e not an internal group
:param group: django.contrib.auth.models.Group object :param group: django.contrib.auth.models.Group object
@@ -44,20 +73,11 @@ class GroupManager:
return not group.authgroup.internal return not group.authgroup.internal
@staticmethod @staticmethod
def check_internal_group(group): def has_management_permission(user: User) -> bool:
"""
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):
return user.has_perm('auth.group_management') return user.has_perm('auth.group_management')
@classmethod @classmethod
def can_manage_groups(cls, user): def can_manage_groups(cls, user:User ) -> bool:
""" """
For use with user_passes_test decorator. For use with user_passes_test decorator.
Check if the user can manage groups. Either has the Check if the user can manage groups. Either has the
@@ -71,7 +91,7 @@ class GroupManager:
return False return False
@classmethod @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 Check user has permission to manage the given group
:param user: User object to test permission of :param user: User object to test permission of
@@ -79,5 +99,5 @@ class GroupManager:
:return: True if the user can manage the group :return: True if the user can manage the group
""" """
if user.is_authenticated: 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 return False

View File

@@ -15,7 +15,7 @@
<div class="panel-body"> <div class="panel-body">
<p> <p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> <a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
Back {% trans "Back" %}
</a> </a>
</p> </p>
{% if entries %} {% if entries %}

View File

@@ -17,7 +17,7 @@
<div class="panel-body"> <div class="panel-body">
<p> <p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> <a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
Back {% trans "Back" %}
</a> </a>
</p> </p>
{% if group.user_set %} {% if group.user_set %}

View File

@@ -11,7 +11,7 @@
{% include 'groupmanagement/menu.html' %} {% include 'groupmanagement/menu.html' %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
Groups {% trans "Groups" %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if groups %} {% if groups %}

View File

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

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

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

View 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

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

View File

@@ -1,5 +1,6 @@
import logging import logging
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test 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.http import Http404
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .managers import GroupManager
from .models import GroupRequest, RequestLog
from allianceauth.notifications import notify from allianceauth.notifications import notify
from django.conf import settings from .managers import GroupManager
from .models import GroupRequest, RequestLog
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -33,7 +34,8 @@ def group_management(request):
group_requests = base_group_query.all() group_requests = base_group_query.all()
else: else:
# Group specific leader # 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: for grouprequest in group_requests:
if grouprequest.leave_request: if grouprequest.leave_request:
@@ -234,7 +236,7 @@ def group_reject_request(request, group_request_id):
raise p raise p
except: 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}) 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)) request.user, group_request_id))
pass pass
@@ -268,9 +270,9 @@ def group_leave_accept_request(request, group_request_id):
(request.user, group_request_id)) (request.user, group_request_id))
raise p raise p
except: 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}) "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)) request.user, group_request_id))
pass pass
@@ -302,9 +304,9 @@ def group_leave_reject_request(request, group_request_id):
(request.user, group_request_id)) (request.user, group_request_id))
raise p raise p
except: 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}) "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)) request.user, group_request_id))
pass pass
@@ -314,24 +316,23 @@ def group_leave_reject_request(request, group_request_id):
@login_required @login_required
def groups_view(request): def groups_view(request):
logger.debug("groups_view called by user %s" % request.user) 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 = [] 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) context = {'groups': groups}
return render(request, 'groupmanagement/groups.html', context=context)
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)
@login_required @login_required
@@ -348,13 +349,13 @@ def group_request_add(request, group_id):
# User is already a member of this group. # User is already a member of this group.
logger.warning("User %s attempted to join group id %s but they are already a member." % logger.warning("User %s attempted to join group id %s but they are already a member." %
(request.user, group_id)) (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') return redirect('groupmanagement:groups')
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public: 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 # 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" % logger.warning("User %s attempted to join group id %s but it is not a public group" %
(request.user, group_id)) (request.user, group_id))
messages.warning(request, "You cannot join that group") messages.warning(request, _("You cannot join that group"))
return redirect('groupmanagement:groups') return redirect('groupmanagement:groups')
if group.authgroup.open: if group.authgroup.open:
logger.info("%s joining %s as is an open group" % (request.user, group)) 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) req = GroupRequest.objects.filter(user=request.user, group=group)
if len(req) > 0: if len(req) > 0:
logger.info("%s attempted to join %s but already has an open application" % (request.user, group)) 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") return redirect("groupmanagement:groups")
grouprequest = GroupRequest() grouprequest = GroupRequest()
grouprequest.status = _('Pending') grouprequest.status = _('Pending')
@@ -397,7 +398,7 @@ def group_request_leave(request, group_id):
req = GroupRequest.objects.filter(user=request.user, group=group) req = GroupRequest.objects.filter(user=request.user, group=group)
if len(req) > 0: if len(req) > 0:
logger.info("%s attempted to leave %s but already has an pending leave request." % (request.user, group)) 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") return redirect("groupmanagement:groups")
if getattr(settings, 'AUTO_LEAVE', False): if getattr(settings, 'AUTO_LEAVE', False):
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group)) logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,15 @@ app = Celery('{{ project_name }}')
# Using a string here means the worker don't have to serialize # Using a string here means the worker don't have to serialize
# the configuration object to child processes. # the configuration object to child processes.
app.config_from_object('django.conf:settings') 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 = { app.conf.ONCE = {
'backend': 'allianceauth.services.tasks.DjangoBackend', 'backend': 'allianceauth.services.tasks.DjangoBackend',
'settings': {} 'settings': {}

View File

@@ -83,6 +83,9 @@ LANGUAGES = (
('en', ugettext('English')), ('en', ugettext('English')),
('de', ugettext('German')), ('de', ugettext('German')),
('es', ugettext('Spanish')), ('es', ugettext('Spanish')),
('zh-hans', ugettext('Chinese Simplified')),
('ru', ugettext('Russian')),
('ko', ugettext('Korean')),
) )
TEMPLATES = [ TEMPLATES = [
@@ -217,6 +220,14 @@ LOGGING = {
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size 'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
'backupCount': 5, # edit this line to change number of log backups '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': { 'console': {
'level': 'DEBUG', # edit this line to change logging level to console 'level': 'DEBUG', # edit this line to change logging level to console
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
@@ -233,6 +244,10 @@ LOGGING = {
'handlers': ['log_file', 'console', 'notifications'], 'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG', 'level': 'DEBUG',
}, },
'extensions': {
'handlers': ['extension_file', 'console'],
'level': 'DEBUG',
},
'django': { 'django': {
'handlers': ['log_file', 'console'], 'handlers': ['log_file', 'console'],
'level': 'ERROR', 'level': 'ERROR',

View File

@@ -4,3 +4,8 @@ from allianceauth import urls
urlpatterns = [ urlpatterns = [
url(r'', include(urls)), url(r'', include(urls)),
] ]
handler500 = 'allianceauth.views.Generic500Redirect'
handler404 = 'allianceauth.views.Generic404Redirect'
handler403 = 'allianceauth.views.Generic403Redirect'
handler400 = 'allianceauth.views.Generic400Redirect'

View File

@@ -17,6 +17,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render, Http404, redirect from django.shortcuts import render, Http404, redirect
from django.utils.translation import gettext_lazy as _
from .forms import ServicePasswordModelForm from .forms import ServicePasswordModelForm
@@ -68,7 +69,7 @@ class BaseCreatePasswordServiceAccountView(BaseServiceView, ServiceCredentialsVi
try: try:
svc_obj = self.model.objects.create(user=request.user) svc_obj = self.model.objects.create(user=request.user)
except IntegrityError: except IntegrityError:
messages.error(request, "That service account already exists") messages.error(request, _("That service account already exists"))
return redirect(self.index_redirect) return redirect(self.index_redirect)
return render(request, self.template_name, return render(request, self.template_name,
@@ -100,7 +101,7 @@ class BaseSetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, Upda
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs) result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs)
if self.get_form().is_valid(): 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 return result

View File

@@ -1,4 +1,6 @@
from django.utils.translation import gettext_lazy as _
from allianceauth import hooks from allianceauth import hooks
from .hooks import MenuItemHook from .hooks import MenuItemHook
from .hooks import ServicesHook from .hooks import ServicesHook
@@ -6,7 +8,7 @@ from .hooks import ServicesHook
class Services(MenuItemHook): class Services(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(self,
'Services', _('Services'),
'fa fa-cogs fa-fw', 'fa fa-cogs fa-fw',
'services:services', 100) 'services:services', 100)

View File

@@ -9,6 +9,29 @@ from allianceauth.hooks import get_hooks
from .models import NameFormatConfig 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: class ServicesHook:
""" """
Abstract base class for creating a compatible services Abstract base class for creating a compatible services

View File

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

View File

@@ -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',
),
]

View File

@@ -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',
),
]

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

View File

@@ -4,14 +4,30 @@ from allianceauth.authentication.models import State
class NameFormatConfig(models.Model): class NameFormatConfig(models.Model):
service_name = models.CharField(max_length=100, blank=False, null=False) 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_username = models.BooleanField(
"default to using their Auth username instead.") default=True,
format = models.CharField(max_length=100, blank=False, null=False, help_text=
help_text='For information on constructing name formats, please see the ' 'If a user has no main_character, '
'<a href="https://allianceauth.readthedocs.io/en/latest/features/nameformats">' 'default to using their Auth username instead.'
'name format documentation</a>') )
states = models.ManyToManyField(State, help_text="States to apply this format to. You should only have one " format = models.CharField(
"formatter for each state for each service.") 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()])
)

View File

@@ -36,7 +36,7 @@ class DiscordService(ServicesHook):
def sync_nickname(self, user): def sync_nickname(self, user):
logger.debug('Syncing %s nickname for user %s' % (self.name, 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): def update_all_groups(self):
logger.debug('Update all %s groups called' % self.name) logger.debug('Update all %s groups called' % self.name)

View File

@@ -27,7 +27,12 @@ class DiscordViewsTestCase(WebTest):
self.login() self.login()
manager.generate_oauth_redirect_url.return_value = '/example.com/oauth/' manager.generate_oauth_redirect_url.return_value = '/example.com/oauth/'
response = self.app.get('/discord/activate/', auto_follow=False) 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') @mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager')
def test_callback(self, manager): def test_callback(self, manager):

View File

@@ -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 permission_required
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from allianceauth.services.views import superuser_test from allianceauth.services.views import superuser_test
from .manager import DiscordOAuthManager from .manager import DiscordOAuthManager
from .tasks import DiscordTasks from .tasks import DiscordTasks
@@ -21,10 +23,10 @@ def deactivate_discord(request):
logger.debug("deactivate_discord called by user %s" % request.user) logger.debug("deactivate_discord called by user %s" % request.user)
if DiscordTasks.delete_user(request.user): if DiscordTasks.delete_user(request.user):
logger.info("Successfully deactivated discord for user %s" % 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: else:
logger.error("Unsuccessful attempt to deactivate discord for user %s" % request.user) 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") 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) logger.info("Successfully deleted discord user for user %s - forwarding to discord activation." % request.user)
return redirect("discord:activate") return redirect("discord:activate")
logger.error("Unsuccessful attempt to reset discord for user %s" % request.user) 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") return redirect("services:services")
@@ -57,10 +59,10 @@ def discord_callback(request):
return redirect("services:services") return redirect("services:services")
if DiscordTasks.add_user(request.user, code): if DiscordTasks.add_user(request.user, code):
logger.info("Successfully activated Discord for user %s" % request.user) logger.info("Successfully activated Discord for user %s" % request.user)
messages.success(request, 'Activated Discord account.') messages.success(request, _('Activated Discord account.'))
else: else:
logger.error("Failed to activate Discord for user %s" % request.user) 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") return redirect("services:services")

View File

@@ -2,6 +2,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from .manager import DiscourseManager from .manager import DiscourseManager
from .tasks import DiscourseTasks from .tasks import DiscourseTasks
@@ -33,12 +34,12 @@ def discourse_sso(request):
# Check if user has access # Check if user has access
if not request.user.has_perm(ACCESS_PERM): 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) logger.warning('User %s attempted to access Discourse but does not have permission.' % request.user)
return redirect('authentication:dashboard') return redirect('authentication:dashboard')
if not request.user.profile.main_character: 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) logger.warning('User %s attempted to access Discourse but does not have a main character.' % request.user)
return redirect('authentication:characters') return redirect('authentication:characters')
@@ -48,7 +49,7 @@ def discourse_sso(request):
signature = request.GET.get('sig') signature = request.GET.get('sig')
if None in [payload, signature]: 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') return redirect('authentication:dashboard')
# Validate the payload # Validate the payload
@@ -58,7 +59,7 @@ def discourse_sso(request):
assert 'nonce' in decoded assert 'nonce' in decoded
assert len(payload) > 0 assert len(payload) > 0
except AssertionError: 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') return redirect('authentication:dashboard')
key = str(settings.DISCOURSE_SSO_SECRET).encode('utf-8') key = str(settings.DISCOURSE_SSO_SECRET).encode('utf-8')
@@ -66,7 +67,7 @@ def discourse_sso(request):
this_signature = h.hexdigest() this_signature = h.hexdigest()
if this_signature != signature: 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') return redirect('authentication:dashboard')
## Build the return payload ## Build the return payload

View File

@@ -3,6 +3,7 @@ import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from allianceauth.services.forms import ServicePasswordForm from allianceauth.services.forms import ServicePasswordForm
from .manager import Ips4Manager from .manager import Ips4Manager
@@ -27,7 +28,7 @@ def activate_ips4(request):
logger.debug("Updated authserviceinfo for user %s with IPS4 credentials." % request.user) logger.debug("Updated authserviceinfo for user %s with IPS4 credentials." % request.user)
# update_ips4_groups.delay(request.user.pk) # update_ips4_groups.delay(request.user.pk)
logger.info("Successfully activated IPS4 for user %s" % request.user) logger.info("Successfully activated IPS4 for user %s" % request.user)
messages.success(request, 'Activated IPSuite4 account.') messages.success(request, _('Activated IPSuite4 account.'))
credentials = { credentials = {
'username': result[0], 'username': result[0],
'password': result[1], 'password': result[1],
@@ -36,7 +37,7 @@ def activate_ips4(request):
context={'credentials': credentials, 'service': 'IPSuite4'}) context={'credentials': credentials, 'service': 'IPSuite4'})
else: else:
logger.error("Unsuccessful attempt to activate IPS4 for user %s" % request.user) 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") return redirect("services:services")
@@ -49,7 +50,7 @@ def reset_ips4_password(request):
# false we failed # false we failed
if result != "": if result != "":
logger.info("Successfully reset IPS4 password for user %s" % request.user) logger.info("Successfully reset IPS4 password for user %s" % request.user)
messages.success(request, 'Reset IPSuite4 password.') messages.success(request, _('Reset IPSuite4 password.'))
credentials = { credentials = {
'username': request.user.ips4.username, 'username': request.user.ips4.username,
'password': result, 'password': result,
@@ -58,7 +59,7 @@ def reset_ips4_password(request):
context={'credentials': credentials, 'service': 'IPSuite4'}) context={'credentials': credentials, 'service': 'IPSuite4'})
logger.error("Unsuccessful attempt to reset IPS4 password for user %s" % request.user) 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") 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) result = Ips4Manager.update_custom_password(request.user.ips4.username, plain_password=password)
if result != "": if result != "":
logger.info("Successfully set IPS4 password for user %s" % request.user) logger.info("Successfully set IPS4 password for user %s" % request.user)
messages.success(request, 'Set IPSuite4 password.') messages.success(request, _('Set IPSuite4 password.'))
else: else:
logger.error("Failed to install custom IPS4 password for user %s" % request.user) 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') return redirect('services:services')
else: else:
logger.debug("Request is not type POST - providing empty form.") 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) logger.debug("deactivate_ips4 called by user %s" % request.user)
if Ips4Tasks.delete_user(request.user): if Ips4Tasks.delete_user(request.user):
logger.info("Successfully deactivated IPS4 for user %s" % 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: else:
logger.error("Unsuccessful attempt to deactivate IPS4 for user %s" % request.user) 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") return redirect("services:services")

View File

@@ -40,6 +40,11 @@ class MumbleService(ServicesHook):
if MumbleTasks.has_account(user): if MumbleTasks.has_account(user):
MumbleTasks.update_groups.delay(user.pk) 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): def validate_user(self, user):
if MumbleTasks.has_account(user) and not self.service_active_for_user(user): if MumbleTasks.has_account(user) and not self.service_active_for_user(user):
self.delete_user(user, notify_user=True) self.delete_user(user, notify_user=True)

View File

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

View File

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

View File

@@ -15,10 +15,14 @@ class MumbleManager(models.Manager):
HASH_FN = 'bcrypt-sha256' HASH_FN = 'bcrypt-sha256'
@staticmethod @staticmethod
def get_username(user): def get_display_name(user):
from .auth_hooks import MumbleService from .auth_hooks import MumbleService
return NameFormatter(MumbleService(), user).format_name() 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 @staticmethod
def sanitise_username(username): def sanitise_username(username):
return username.replace(" ", "_") return username.replace(" ", "_")
@@ -32,20 +36,26 @@ class MumbleManager(models.Manager):
return bcrypt_sha256.encrypt(password.encode('utf-8')) return bcrypt_sha256.encrypt(password.encode('utf-8'))
def create(self, user): def create(self, user):
username = self.get_username(user) try:
logger.debug("Creating mumble user with username {}".format(username)) username = self.get_username(user)
username_clean = self.sanitise_username(username) logger.debug("Creating mumble user with username {}".format(username))
password = self.generate_random_pass() username_clean = self.sanitise_username(username)
pwhash = self.gen_pwhash(password) display_name = self.get_display_name(user)
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format( password = self.generate_random_pass()
username_clean, pwhash[0:5])) pwhash = self.gen_pwhash(password)
logger.info("Creating mumble user {}".format(username_clean)) 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, result = super(MumbleManager, self).create(user=user, username=username_clean,
pwhash=pwhash, hashfn=self.HASH_FN) pwhash=pwhash, hashfn=self.HASH_FN,
result.update_groups() display_name=display_name)
result.credentials.update({'username': result.username, 'password': password}) result.update_groups()
return result 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): def user_exists(self, username):
return self.filter(username=username).exists() return self.filter(username=username).exists()
@@ -59,6 +69,8 @@ class MumbleUser(AbstractServiceModel):
objects = MumbleManager() objects = MumbleManager()
display_name = models.CharField(max_length=254, unique=True)
def __str__(self): def __str__(self):
return self.username return self.username
@@ -91,6 +103,12 @@ class MumbleUser(AbstractServiceModel):
self.save() self.save()
return True 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: class Meta:
permissions = ( permissions = (
("access_mumble", u"Can access the Mumble service"), ("access_mumble", u"Can access the Mumble service"),

View File

@@ -45,9 +45,37 @@ class MumbleTasks:
logger.debug("User %s does not have a mumble account, skipping" % user) logger.debug("User %s does not have a mumble account, skipping" % user)
return False 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 @staticmethod
@shared_task(name="mumble.update_all_groups") @shared_task(name="mumble.update_all_groups")
def update_all_groups(): def update_all_groups():
logger.debug("Updating ALL mumble groups") logger.debug("Updating ALL mumble groups")
for mumble_user in MumbleUser.objects.exclude(username__exact=''): for mumble_user in MumbleUser.objects.exclude(username__exact=''):
MumbleTasks.update_groups.delay(mumble_user.user.pk) 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)

View File

@@ -25,6 +25,9 @@ class MumbleHooksTestCase(TestCase):
def setUp(self): def setUp(self):
self.member = 'member_user' self.member = 'member_user'
member = AuthUtils.create_member(self.member) 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) MumbleUser.objects.create(user=member)
self.none_user = 'none_user' self.none_user = 'none_user'
none_user = AuthUtils.create_user(self.none_user) none_user = AuthUtils.create_user(self.none_user)
@@ -122,23 +125,45 @@ class MumbleViewsTestCase(TestCase):
self.member.save() self.member.save()
AuthUtils.add_main_character(self.member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation', AuthUtils.add_main_character(self.member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation',
corp_ticker='TESTR') corp_ticker='TESTR')
self.member = User.objects.get(pk=self.member.pk)
add_permissions() add_permissions()
def login(self): def login(self):
self.client.force_login(self.member) self.client.force_login(self.member)
def test_activate(self): def test_activate_update(self):
self.login() 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) response = self.client.get(urls.reverse('mumble:activate'), follow=False)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, expected_username) self.assertContains(response, expected_username)
# create
mumble_user = MumbleUser.objects.get(user=self.member) mumble_user = MumbleUser.objects.get(user=self.member)
self.assertEqual(mumble_user.username, expected_username) 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.assertTrue(mumble_user.pwhash)
self.assertIn('Guest', mumble_user.groups) self.assertIn('Guest', mumble_user.groups)
self.assertIn('Member', mumble_user.groups) self.assertIn('Member', mumble_user.groups)
self.assertIn(',', 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): def test_deactivate_post(self):
self.login() self.login()
@@ -171,7 +196,6 @@ class MumbleViewsTestCase(TestCase):
self.assertTemplateUsed(response, 'services/service_credentials.html') self.assertTemplateUsed(response, 'services/service_credentials.html')
self.assertContains(response, 'auth_member') self.assertContains(response, 'auth_member')
class MumbleManagerTestCase(TestCase): class MumbleManagerTestCase(TestCase):
def setUp(self): def setUp(self):
from .models import MumbleManager from .models import MumbleManager

View File

@@ -2,6 +2,7 @@ import logging
from django.conf import settings from django.conf import settings
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from allianceauth import hooks from allianceauth import hooks
from allianceauth.services.hooks import ServicesHook, MenuItemHook from allianceauth.services.hooks import ServicesHook, MenuItemHook
@@ -22,7 +23,7 @@ class OpenfireService(ServicesHook):
@property @property
def title(self): def title(self):
return "Jabber" return _("Jabber")
def delete_user(self, user, notify_user=False): def delete_user(self, user, notify_user=False):
logger.debug('Deleting user %s %s account' % (user, self.name)) logger.debug('Deleting user %s %s account' % (user, self.name))
@@ -74,7 +75,7 @@ def register_service():
class JabberBroadcast(MenuItemHook): class JabberBroadcast(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(self,
'Jabber Broadcast', _('Jabber Broadcast'),
'fa fa-lock fa-fw fa-bullhorn', 'fa fa-lock fa-fw fa-bullhorn',
'openfire:broadcast') 'openfire:broadcast')
@@ -87,7 +88,7 @@ class JabberBroadcast(MenuItemHook):
class FleetBroadcastFormatter(MenuItemHook): class FleetBroadcastFormatter(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(self,
'Fleet Broadcast Formatter', _('Fleet Broadcast Formatter'),
'fa fa-lock fa-fw fa-space-shuttle', 'fa fa-lock fa-fw fa-space-shuttle',
'services:fleet_format_tool') 'services:fleet_format_tool')

View File

@@ -5,8 +5,10 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from allianceauth.services.forms import ServicePasswordForm from allianceauth.services.forms import ServicePasswordForm
from .forms import JabberBroadcastForm from .forms import JabberBroadcastForm
from .manager import OpenfireManager, PingBotException from .manager import OpenfireManager, PingBotException
from .models import OpenfireUser 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) logger.debug("Updated authserviceinfo for user %s with jabber credentials. Updating groups." % request.user)
OpenfireTasks.update_groups.delay(request.user.pk) OpenfireTasks.update_groups.delay(request.user.pk)
logger.info("Successfully activated jabber for user %s" % request.user) logger.info("Successfully activated jabber for user %s" % request.user)
messages.success(request, 'Activated jabber account.') messages.success(request, _('Activated jabber account.'))
credentials = { credentials = {
'username': info[0], 'username': info[0],
'password': info[1], 'password': info[1],
@@ -39,7 +41,7 @@ def activate_jabber(request):
context={'credentials': credentials, 'service': 'Jabber'}) context={'credentials': credentials, 'service': 'Jabber'})
else: else:
logger.error("Unsuccessful attempt to activate jabber for user %s" % request.user) 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") return redirect("services:services")
@@ -52,7 +54,7 @@ def deactivate_jabber(request):
messages.success(request, 'Deactivated jabber account.') messages.success(request, 'Deactivated jabber account.')
else: else:
logger.error("Unsuccessful attempt to deactivate jabber for user %s" % request.user) 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") return redirect("services:services")
@@ -65,7 +67,7 @@ def reset_jabber_password(request):
# If our username is blank means we failed # If our username is blank means we failed
if result != "": if result != "":
logger.info("Successfully reset jabber password for user %s" % request.user) logger.info("Successfully reset jabber password for user %s" % request.user)
messages.success(request, 'Reset jabber password.') messages.success(request, _('Reset jabber password.'))
credentials = { credentials = {
'username': request.user.openfire.username, 'username': request.user.openfire.username,
'password': result, 'password': result,
@@ -73,7 +75,7 @@ def reset_jabber_password(request):
return render(request, 'services/service_credentials.html', return render(request, 'services/service_credentials.html',
context={'credentials': credentials, 'service': 'Jabber'}) context={'credentials': credentials, 'service': 'Jabber'})
logger.error("Unsuccessful attempt to reset jabber for user %s" % request.user) 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") return redirect("services:services")
@@ -114,7 +116,7 @@ def jabber_broadcast_view(request):
OpenfireManager.send_broadcast_message(group_to_send, message_to_send) 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) logger.info("Sent jabber broadcast on behalf of user %s" % request.user)
except PingBotException as e: except PingBotException as e:
messages.error(request, 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) result = OpenfireManager.update_user_pass(request.user.openfire.username, password=password)
if result != "": if result != "":
logger.info("Successfully set jabber password for user %s" % request.user) logger.info("Successfully set jabber password for user %s" % request.user)
messages.success(request, 'Set jabber password.') messages.success(request, _('Set jabber password.'))
else: else:
logger.error("Failed to install custom jabber password for user %s" % request.user) 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") return redirect("services:services")
else: else:
logger.debug("Request is not type POST - providing empty form.") logger.debug("Request is not type POST - providing empty form.")

View File

@@ -3,8 +3,10 @@ import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from allianceauth.services.forms import ServicePasswordForm from allianceauth.services.forms import ServicePasswordForm
from .manager import Phpbb3Manager from .manager import Phpbb3Manager
from .models import Phpbb3User from .models import Phpbb3User
from .tasks import Phpbb3Tasks 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) logger.debug("Updated authserviceinfo for user %s with forum credentials. Updating groups." % request.user)
Phpbb3Tasks.update_groups.delay(request.user.pk) Phpbb3Tasks.update_groups.delay(request.user.pk)
logger.info("Successfully activated forum for user %s" % request.user) logger.info("Successfully activated forum for user %s" % request.user)
messages.success(request, 'Activated forum account.') messages.success(request, _('Activated forum account.'))
credentials = { credentials = {
'username': result[0], 'username': result[0],
'password': result[1], 'password': result[1],
@@ -38,7 +40,7 @@ def activate_forum(request):
context={'credentials': credentials, 'service': 'Forum'}) context={'credentials': credentials, 'service': 'Forum'})
else: else:
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user) 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") return redirect("services:services")
@@ -49,10 +51,10 @@ def deactivate_forum(request):
# false we failed # false we failed
if Phpbb3Tasks.delete_user(request.user): if Phpbb3Tasks.delete_user(request.user):
logger.info("Successfully deactivated forum for user %s" % 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: else:
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user) 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") return redirect("services:services")
@@ -66,7 +68,7 @@ def reset_forum_password(request):
# false we failed # false we failed
if result != "": if result != "":
logger.info("Successfully reset forum password for user %s" % request.user) logger.info("Successfully reset forum password for user %s" % request.user)
messages.success(request, 'Reset forum password.') messages.success(request, _('Reset forum password.'))
credentials = { credentials = {
'username': request.user.phpbb3.username, 'username': request.user.phpbb3.username,
'password': result, 'password': result,
@@ -75,7 +77,7 @@ def reset_forum_password(request):
context={'credentials': credentials, 'service': 'Forum'}) context={'credentials': credentials, 'service': 'Forum'})
logger.error("Unsuccessful attempt to reset forum password for user %s" % request.user) 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") return redirect("services:services")
@@ -95,10 +97,10 @@ def set_forum_password(request):
password=password) password=password)
if result != "": if result != "":
logger.info("Successfully set forum password for user %s" % request.user) logger.info("Successfully set forum password for user %s" % request.user)
messages.success(request, 'Set forum password.') messages.success(request, _('Set forum password.'))
else: else:
logger.error("Failed to install custom forum password for user %s" % request.user) 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") return redirect("services:services")
else: else:
logger.debug("Request is not type POST - providing empty form.") logger.debug("Request is not type POST - providing empty form.")

View File

@@ -3,8 +3,10 @@ import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from allianceauth.services.forms import ServicePasswordForm from allianceauth.services.forms import ServicePasswordForm
from .manager import SmfManager from .manager import SmfManager
from .models import SmfUser from .models import SmfUser
from .tasks import SmfTasks 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) logger.debug("Updated authserviceinfo for user %s with smf credentials. Updating groups." % request.user)
SmfTasks.update_groups.delay(request.user.pk) SmfTasks.update_groups.delay(request.user.pk)
logger.info("Successfully activated smf for user %s" % request.user) logger.info("Successfully activated smf for user %s" % request.user)
messages.success(request, 'Activated SMF account.') messages.success(request, _('Activated SMF account.'))
credentials = { credentials = {
'username': result[0], 'username': result[0],
'password': result[1], 'password': result[1],
@@ -38,7 +40,7 @@ def activate_smf(request):
context={'credentials': credentials, 'service': 'SMF'}) context={'credentials': credentials, 'service': 'SMF'})
else: else:
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user) 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") return redirect("services:services")
@@ -50,10 +52,10 @@ def deactivate_smf(request):
# false we failed # false we failed
if result: if result:
logger.info("Successfully deactivated smf for user %s" % request.user) logger.info("Successfully deactivated smf for user %s" % request.user)
messages.success(request, 'Deactivated SMF account.') messages.success(request, _('Deactivated SMF account.'))
else: else:
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user) 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") return redirect("services:services")
@@ -67,7 +69,7 @@ def reset_smf_password(request):
# false we failed # false we failed
if result != "": if result != "":
logger.info("Successfully reset smf password for user %s" % request.user) logger.info("Successfully reset smf password for user %s" % request.user)
messages.success(request, 'Reset SMF password.') messages.success(request, _('Reset SMF password.'))
credentials = { credentials = {
'username': request.user.smf.username, 'username': request.user.smf.username,
'password': result, 'password': result,
@@ -75,7 +77,7 @@ def reset_smf_password(request):
return render(request, 'services/service_credentials.html', return render(request, 'services/service_credentials.html',
context={'credentials': credentials, 'service': 'SMF'}) context={'credentials': credentials, 'service': 'SMF'})
logger.error("Unsuccessful attempt to reset smf password for user %s" % request.user) 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") return redirect("services:services")
@@ -95,10 +97,10 @@ def set_smf_password(request):
password=password) password=password)
if result != "": if result != "":
logger.info("Successfully set smf password for user %s" % request.user) logger.info("Successfully set smf password for user %s" % request.user)
messages.success(request, 'Set SMF password.') messages.success(request, _('Set SMF password.'))
else: else:
logger.error("Failed to install custom smf password for user %s" % request.user) 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") return redirect("services:services")
else: else:
logger.debug("Request is not type POST - providing empty form.") logger.debug("Request is not type POST - providing empty form.")

View File

@@ -4,6 +4,8 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext_lazy as _
from .manager import Teamspeak3Manager from .manager import Teamspeak3Manager
from .forms import TeamspeakJoinForm from .forms import TeamspeakJoinForm
from .models import Teamspeak3User 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]}) 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.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
logger.info("Successfully activated TS3 for user %s" % 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") return redirect("teamspeak3:verify")
logger.error("Unsuccessful attempt to activate TS3 for user %s" % request.user) 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") return redirect("services:services")
@@ -66,10 +68,10 @@ def deactivate_teamspeak3(request):
logger.debug("deactivate_teamspeak3 called by user %s" % request.user) logger.debug("deactivate_teamspeak3 called by user %s" % request.user)
if Teamspeak3Tasks.has_account(request.user) and Teamspeak3Tasks.delete_user(request.user): if Teamspeak3Tasks.has_account(request.user) and Teamspeak3Tasks.delete_user(request.user):
logger.info("Successfully deactivated TS3 for user %s" % 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: else:
logger.error("Unsuccessful attempt to deactivate TS3 for user %s" % request.user) 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") 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) logger.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
Teamspeak3Tasks.update_groups.delay(request.user.pk) Teamspeak3Tasks.update_groups.delay(request.user.pk)
logger.info("Successfully reset TS3 permission key for user %s" % request.user) 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: else:
logger.error("Unsuccessful attempt to reset TS3 permission key for user %s" % request.user) 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") return redirect("services:services")

View File

@@ -3,8 +3,10 @@ import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.utils.translation import gettext_lazy as _
from allianceauth.services.forms import ServicePasswordForm from allianceauth.services.forms import ServicePasswordForm
from .manager import XenForoManager from .manager import XenForoManager
from .models import XenforoUser from .models import XenforoUser
from .tasks import XenforoTasks from .tasks import XenforoTasks
@@ -25,7 +27,7 @@ def activate_xenforo_forum(request):
if result['response']['status_code'] == 200: if result['response']['status_code'] == 200:
XenforoUser.objects.update_or_create(user=request.user, defaults={'username': result['username']}) XenforoUser.objects.update_or_create(user=request.user, defaults={'username': result['username']})
logger.info("Updated user %s with XenForo credentials. Updating groups." % request.user) 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 = { credentials = {
'username': result['username'], 'username': result['username'],
'password': result['password'], 'password': result['password'],
@@ -35,7 +37,7 @@ def activate_xenforo_forum(request):
else: else:
logger.error("Unsuccessful attempt to activate xenforo for user %s" % request.user) 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") return redirect("services:services")
@@ -45,9 +47,9 @@ def deactivate_xenforo_forum(request):
logger.debug("deactivate_xenforo_forum called by user %s" % request.user) logger.debug("deactivate_xenforo_forum called by user %s" % request.user)
if XenforoTasks.delete_user(request.user): if XenforoTasks.delete_user(request.user):
logger.info("Successfully deactivated XenForo for user %s" % 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: 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") return redirect("services:services")
@@ -60,7 +62,7 @@ def reset_xenforo_password(request):
# Based on XenAPI's response codes # Based on XenAPI's response codes
if result['response']['status_code'] == 200: if result['response']['status_code'] == 200:
logger.info("Successfully reset XenForo password for user %s" % request.user) 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 = { credentials = {
'username': request.user.xenforo.username, 'username': request.user.xenforo.username,
'password': result['password'], 'password': result['password'],
@@ -68,7 +70,7 @@ def reset_xenforo_password(request):
return render(request, 'services/service_credentials.html', return render(request, 'services/service_credentials.html',
context={'credentials': credentials, 'service': 'XenForo'}) context={'credentials': credentials, 'service': 'XenForo'})
logger.error("Unsuccessful attempt to reset XenForo password for user %s" % request.user) 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") return redirect("services:services")
@@ -86,10 +88,10 @@ def set_xenforo_password(request):
result = XenForoManager.update_user_password(request.user.xenforo.username, password) result = XenForoManager.update_user_password(request.user.xenforo.username, password)
if result['response']['status_code'] == 200: if result['response']['status_code'] == 200:
logger.info("Successfully reset XenForo password for user %s" % request.user) logger.info("Successfully reset XenForo password for user %s" % request.user)
messages.success(request, 'Changed XenForo password.') messages.success(request, _('Changed XenForo password.'))
else: else:
logger.error("Failed to install custom XenForo password for user %s" % request.user) 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') return redirect('services:services')
else: else:
logger.debug("Request is not type POST - providing empty form.") logger.debug("Request is not type POST - providing empty form.")

View File

@@ -1,6 +1,7 @@
import logging import logging
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction from django.db import transaction
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.db.models.signals import pre_delete 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.models import State, UserProfile
from allianceauth.authentication.signals import state_changed from allianceauth.authentication.signals import state_changed
from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -157,14 +159,45 @@ def disable_services_on_inactive(sender, instance, *args, **kwargs):
@receiver(pre_save, sender=UserProfile) @receiver(pre_save, sender=UserProfile)
def disable_services_on_no_main(sender, instance, *args, **kwargs): def process_main_character_change(sender, instance, *args, **kwargs):
if not instance.pk:
if not instance.pk: # ignore
# new model being created # new model being created
return return
try: try:
old_instance = UserProfile.objects.get(pk=instance.pk) 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)) logger.info("Disabling services due to loss of main character for user {0}".format(instance.user))
disable_user(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: except UserProfile.DoesNotExist:
pass 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

View File

@@ -2,7 +2,6 @@
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
{% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %} {% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %}
{% endblock page_title %} {% endblock page_title %}
@@ -18,8 +17,8 @@
<form action="" method="post"> <form action="" method="post">
{% csrf_token %} {% csrf_token %}
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed with service_name=view.service_name|title %}
Are you sure you want to delete your {{ view.service_name }} account {{ object }}? Are you sure you want to delete your {{ service_name }} account {{ object }}?
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<input class="btn btn-danger btn-block" type="submit" value="Confirm" /> <input class="btn btn-danger btn-block" type="submit" value="Confirm" />

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

View File

@@ -1,21 +1,17 @@
from allianceauth import NAME
from esi.clients import esi_client_factory
import requests
import logging import logging
import os import os
import requests
from allianceauth import NAME
from allianceauth.eveonline.providers import provider
logger = logging.getLogger(__name__) 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: class SRPManager:
def __init__(self):
pass
@staticmethod @staticmethod
def get_kill_id(killboard_link): def get_kill_id(killboard_link):
num_set = '0123456789' num_set = '0123456789'
@@ -34,18 +30,23 @@ class SRPManager:
if result: if result:
killmail_id = result['killmail_id'] killmail_id = result['killmail_id']
killmail_hash = result['zkb']['hash'] killmail_hash = result['zkb']['hash']
c = esi_client_factory(spec_file=SWAGGER_SPEC_PATH) c = provider.client
km = c.Killmails.get_killmails_killmail_id_killmail_hash(killmail_id=killmail_id, km = c.Killmails.get_killmails_killmail_id_killmail_hash(
killmail_hash=killmail_hash).result() killmail_id=killmail_id,
killmail_hash=killmail_hash
).result()
else: else:
raise ValueError("Invalid Kill ID") raise ValueError("Invalid Kill ID")
if km: if km:
ship_type = km['victim']['ship_type_id'] 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'] 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'] victim_id = km['victim']['character_id']
return ship_type, ship_value, victim_id return ship_type, ship_value, victim_id
else: else:
raise ValueError("Invalid Kill ID or Hash.") raise ValueError("Invalid Kill ID or Hash.")

File diff suppressed because one or more lines are too long

View File

@@ -90,10 +90,9 @@
<th class="text-center">{% trans "Ship Type" %}</th> <th class="text-center">{% trans "Ship Type" %}</th>
<th class="text-center">{% trans "Killboard Loss Amt" %}</th> <th class="text-center">{% trans "Killboard Loss Amt" %}</th>
<th class="text-center">{% trans "SRP ISK Cost" %} <th class="text-center">{% trans "SRP ISK Cost" %}
{% blocktrans %}<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Click value to edit <i class="glyphicon glyphicon-question-sign" rel="tooltip" title="{% blocktrans trimmed %}Click value to edit
Enter to save&next Enter to save & next
ESC to cancel" ESC to cancel{% endblocktrans %}"id="blah"></i></th>
id="blah"></i></th>{% endblocktrans %}
<th class="text-center">{% trans "Post Time" %}</th> <th class="text-center">{% trans "Post Time" %}</th>
<th class="text-center">{% trans "Status" %}</th> <th class="text-center">{% trans "Status" %}</th>
{% if perms.auth.srp_management %} {% if perms.auth.srp_management %}

View File

@@ -1 +0,0 @@
# Create your tests here.

View File

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

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

View 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
}
}
]

View File

@@ -8,7 +8,7 @@ from django.contrib.humanize.templatetags.humanize import intcomma
from django.http import JsonResponse, Http404 from django.http import JsonResponse, Http404
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone 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 django.db.models import Sum
from allianceauth.authentication.decorators import permissions_required from allianceauth.authentication.decorators import permissions_required
from allianceauth.eveonline.providers import provider from allianceauth.eveonline.providers import provider

View File

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

View File

@@ -89,7 +89,7 @@
{% if task_queue_length < 0 %} {% if task_queue_length < 0 %}
{% trans "Error retrieving task queue length" %} {% trans "Error retrieving task queue length" %}
{% else %} {% else %}
{% blocktrans count tasks=task_queue_length %} {% blocktrans trimmed count tasks=task_queue_length %}
{{ tasks }} task {{ tasks }} task
{% plural %} {% plural %}
{{ tasks }} tasks {{ tasks }} tasks

View File

@@ -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 %}

View File

@@ -26,15 +26,7 @@
{% endif %} {% endif %}
{% menu_items %} {% 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> </ul>
</div> </div>
</div> </div>

View File

@@ -39,6 +39,13 @@
{% else %} {% else %}
<li><a href="{% url 'authentication:login' %}">{% trans "Login" %}</a></li> <li><a href="{% url 'authentication:login' %}">{% trans "Login" %}</a></li>
{% endif %} {% 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> </ul>
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post"> <form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
{% csrf_token %} {% csrf_token %}

View File

@@ -1,27 +1,45 @@
from allianceauth.authentication.models import UserProfile, State, get_guest_state from django.contrib.auth.models import User, Group, Permission
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.db.models.signals import m2m_changed, pre_save, post_save from django.db.models.signals import m2m_changed, pre_save, post_save
from django.test import TestCase 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, \ from esi.models import Token
m2m_changed_state_permissions
from allianceauth.services.signals import m2m_changed_user_groups, disable_services_on_inactive 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: class AuthUtils:
def __init__(self): """Utilities for making it easier to create tests for Alliance Auth"""
pass
@staticmethod @staticmethod
def _create_user(username): def _create_user(username):
return User.objects.create(username=username) return User.objects.create(username=username)
@classmethod @classmethod
def create_user(cls, username, disconnect_signals=False): 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: if disconnect_signals:
cls.disconnect_signals() cls.disconnect_signals()
user = cls._create_user(username) 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_characters_changed, sender=State.member_characters.through)
m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through) m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through)
post_save.disconnect(state_saved, sender=State) 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 @classmethod
def connect_signals(cls): 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_characters_changed, sender=State.member_characters.through)
m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through) m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through)
post_save.connect(state_saved, sender=State) 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 @classmethod
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='', 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}) 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 @classmethod
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True): def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
if disconnect_signals: if disconnect_signals:
@@ -130,14 +190,67 @@ class AuthUtils:
for group in groups: for group in groups:
for perm in perms: for perm in perms:
group.permissions.add(perm) group.permissions.add(perm)
group = Group.objects.get(pk=group.pk) # reload permission cache
if disconnect_signals: if disconnect_signals:
cls.connect_signals() cls.connect_signals()
@classmethod @classmethod
def add_permissions_to_state(cls, perms, states, disconnect_signals=True): 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): class BaseViewTestCase(TestCase):
def setUp(self): def setUp(self):

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

View File

@@ -1,13 +1,15 @@
from django.views.generic.base import View from django.views.generic.base import View
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.contrib import messages
class NightModeRedirectView(View): class NightModeRedirectView(View):
SESSION_VAR = 'NIGHT_MODE' SESSION_VAR = "NIGHT_MODE"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
request.session[self.SESSION_VAR] = not self.night_mode_state(request) request.session[self.SESSION_VAR] = not self.night_mode_state(request)
return HttpResponseRedirect(request.GET.get('next', '/')) return HttpResponseRedirect(request.GET.get("next", "/"))
@classmethod @classmethod
def night_mode_state(cls, request): def night_mode_state(cls, request):
@@ -17,3 +19,39 @@ class NightModeRedirectView(View):
# Session is middleware # Session is middleware
# Sometimes request wont have a session attribute # Sometimes request wont have a session attribute
return False 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")

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 903 B

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

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