mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80efdec5d9 | ||
|
|
d49687400a | ||
|
|
e6e03b50da | ||
|
|
899988c7c2 | ||
|
|
2f48dd449b | ||
|
|
2b09ca240d | ||
|
|
0626ff84ad | ||
|
|
62ec746ee3 | ||
|
|
d0f12d7d56 | ||
|
|
b806a69604 | ||
|
|
a609d6360b | ||
|
|
dafbfc8644 | ||
|
|
55413eea19 | ||
|
|
5247c181af | ||
|
|
321af5ec87 | ||
|
|
9ccf340b3d | ||
|
|
d7dcacb899 | ||
|
|
8addd483c2 | ||
|
|
4d27e5ac9b | ||
|
|
31290f6e80 | ||
|
|
c31cc4dbee | ||
|
|
cc1f94cf61 | ||
|
|
a9132b8d50 | ||
|
|
7b4a9891aa | ||
|
|
dcaaf38ecc | ||
|
|
653a8aa850 | ||
|
|
274af11385 | ||
|
|
170b246901 | ||
|
|
5250432ce3 | ||
|
|
53d6e973eb | ||
|
|
c9bdd62d53 | ||
|
|
7eb98af528 | ||
|
|
385e3e21b3 | ||
|
|
127ec63d76 | ||
|
|
4988b5f260 | ||
|
|
f28a50f92c | ||
|
|
e8efe8e609 | ||
|
|
d7e7457bc5 | ||
|
|
daff927811 | ||
|
|
8861ec0a61 | ||
|
|
bd4321f61a | ||
|
|
d831482fe0 | ||
|
|
9ea79ea389 | ||
|
|
b6fdf840ef | ||
|
|
73f262ce4b | ||
|
|
f63434adc3 | ||
|
|
42948386ec | ||
|
|
32e0621b0a | ||
|
|
78e05b84e9 | ||
|
|
76ebd21163 | ||
|
|
38aaf545c6 | ||
|
|
527d7ef671 | ||
|
|
e54b80e061 | ||
|
|
27f95a8b2c |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -72,3 +72,6 @@ celerybeat-schedule
|
||||
|
||||
#transifex
|
||||
.tx/
|
||||
|
||||
#other
|
||||
.flake8
|
||||
|
||||
@@ -6,11 +6,6 @@ before_script:
|
||||
- python -V
|
||||
- pip install wheel tox
|
||||
|
||||
test-3.5-core:
|
||||
image: python:3.5-buster
|
||||
script:
|
||||
- tox -e py35-core
|
||||
|
||||
test-3.6-core:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
@@ -26,11 +21,6 @@ test-3.8-core:
|
||||
script:
|
||||
- tox -e py38-core
|
||||
|
||||
test-3.5-all:
|
||||
image: python:3.5-buster
|
||||
script:
|
||||
- tox -e py35-all
|
||||
|
||||
test-3.6-all:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
|
||||
27
.readthedocs.yml
Normal file
27
.readthedocs.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Build documentation with MkDocs
|
||||
#mkdocs:
|
||||
# configuration: mkdocs.yml
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- testing
|
||||
system_packages: true
|
||||
18
README.md
18
README.md
@@ -11,32 +11,34 @@
|
||||
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
## Contens
|
||||
## Content
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Documentation](http://allianceauth.rtfd.io)
|
||||
- [Support](#support)
|
||||
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
|
||||
- [Devloper Team](#developer-team)
|
||||
- [Developer Team](#developer-team)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Overview
|
||||
|
||||
Alliance Auth (AA) is a web application that helps Eve Online organizations efficiently manage access to their applications and services.
|
||||
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
|
||||
|
||||
Main features:
|
||||
|
||||
- Automatically grants or revokes user access to external applications / services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/groups/)
|
||||
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
|
||||
|
||||
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
|
||||
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/installation/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
|
||||
- Includes a set of web apps called ["plug-in apps"](https://allianceauth.readthedocs.io/en/latest/features/) which add many useful functions: fleet schedule, timer board, SRP request management, fleet activity tracker and character application management
|
||||
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
|
||||
|
||||
- Can be easily extended with new services and plugin-apps. Many additional services and plugin-apps are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [offical documentation](http://allianceauth.rtfd.io).
|
||||
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
|
||||
## Screenshot
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '2.6.2'
|
||||
__version__ = '2.6.5'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||
|
||||
@@ -506,7 +506,7 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||
'character',
|
||||
)
|
||||
search_fields = (
|
||||
'user__user',
|
||||
'user__username',
|
||||
'character__character_name',
|
||||
'character__corporation_name',
|
||||
'character__alliance_name'
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||
|
||||
{% include 'allianceauth/icons.html' %}
|
||||
|
||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
||||
|
||||
@@ -2,10 +2,17 @@ from django.urls import reverse
|
||||
|
||||
|
||||
def get_admin_change_view_url(obj: object) -> str:
|
||||
"""returns URL to admin change view for given object"""
|
||||
return reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label,
|
||||
type(obj).__name__.lower()
|
||||
obj._meta.app_label, type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
|
||||
def get_admin_search_url(ModelClass: type) -> str:
|
||||
"""returns URL to search URL for model of given object"""
|
||||
return '{}{}/'.format(
|
||||
reverse('admin:app_list', args=(ModelClass._meta.app_label,)),
|
||||
ModelClass.__name__.lower()
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
from urllib.parse import quote
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
@@ -6,8 +7,9 @@ from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.auth.models import User as BaseUser, Group
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership, State, \
|
||||
OwnershipRecord
|
||||
from allianceauth.authentication.models import (
|
||||
CharacterOwnership, State, OwnershipRecord
|
||||
)
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
)
|
||||
@@ -28,7 +30,7 @@ from ..admin import (
|
||||
user_username,
|
||||
update_main_character_model
|
||||
)
|
||||
from . import get_admin_change_view_url
|
||||
from . import get_admin_change_view_url, get_admin_search_url
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
@@ -175,6 +177,17 @@ def create_test_data():
|
||||
return user_1, user_2, user_3, group_1, group_2
|
||||
|
||||
|
||||
def make_generic_search_request(ModelClass: type, search_term: str):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
return c.get(
|
||||
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
|
||||
)
|
||||
|
||||
|
||||
class TestCharacterOwnershipAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
@@ -197,6 +210,14 @@ class TestCharacterOwnershipAdmin(TestCase):
|
||||
response = c.get(get_admin_change_view_url(ownership))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = CharacterOwnership.objects\
|
||||
.filter(user=self.user_1)\
|
||||
.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestOwnershipRecordAdmin(TestCase):
|
||||
|
||||
@@ -222,6 +243,12 @@ class TestOwnershipRecordAdmin(TestCase):
|
||||
response = c.get(get_admin_change_view_url(ownership_record))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = OwnershipRecord.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestStateAdmin(TestCase):
|
||||
|
||||
@@ -250,6 +277,11 @@ class TestStateAdmin(TestCase):
|
||||
response = c.get(get_admin_change_view_url(member_state))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = State.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.name)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
class TestUserAdmin(TestCase):
|
||||
|
||||
@@ -541,3 +573,9 @@ class TestUserAdmin(TestCase):
|
||||
c.login(username='superuser', password='secret')
|
||||
response = c.get(get_admin_change_view_url(self.user_1))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = User.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
@@ -22,13 +22,6 @@ urlpatterns = [
|
||||
r'^account/characters/add/$',
|
||||
views.add_character,
|
||||
name='add_character'
|
||||
),
|
||||
url(
|
||||
r'^help/$',
|
||||
login_required(
|
||||
TemplateView.as_view(template_name='allianceauth/help.html')
|
||||
),
|
||||
name='help'
|
||||
),
|
||||
),
|
||||
url(r'^dashboard/$', views.dashboard, name='dashboard'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.contrib.auth.models import User
|
||||
from django.core import signing
|
||||
from django.urls import reverse
|
||||
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
|
||||
@@ -69,7 +69,10 @@ def main_character_change(request, token):
|
||||
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
else:
|
||||
messages.error(request, 'Cannot change main character to %(char)s: character owned by a different account.' % ({'char': token.character_name}))
|
||||
messages.error(
|
||||
request,
|
||||
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
|
||||
)
|
||||
co = None
|
||||
if co:
|
||||
request.user.profile.main_character = co.character
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ from .models import EveCorporationInfo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TASK_PRIORITY = 7
|
||||
|
||||
@shared_task
|
||||
def update_corp(corp_id):
|
||||
@@ -27,11 +28,12 @@ def update_character(character_id):
|
||||
def run_model_update():
|
||||
# update existing corp models
|
||||
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
|
||||
update_corp.delay(corp['corporation_id'])
|
||||
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
|
||||
|
||||
# update existing alliance models
|
||||
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
|
||||
update_alliance.delay(alliance['alliance_id'])
|
||||
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
|
||||
|
||||
#update existing character models
|
||||
for character in EveCharacter.objects.all().values('character_id'):
|
||||
update_character.delay(character['character_id'])
|
||||
update_character.apply_async(args=[character['character_id']], priority=TASK_PRIORITY)
|
||||
|
||||
@@ -80,28 +80,28 @@ class TestTasks(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='character.corp.id',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='c.c.t', # max 5 chars
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
run_model_update()
|
||||
|
||||
self.assertEqual(mock_update_corp.delay.call_count, 1)
|
||||
self.assertEqual(mock_update_corp.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_corp.delay.call_args[0][0]),
|
||||
int(mock_update_corp.apply_async.call_args[1]['args'][0]),
|
||||
2345
|
||||
)
|
||||
|
||||
self.assertEqual(mock_update_alliance.delay.call_count, 1)
|
||||
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_alliance.delay.call_args[0][0]),
|
||||
int(mock_update_alliance.apply_async.call_args[1]['args'][0]),
|
||||
3456
|
||||
)
|
||||
|
||||
self.assertEqual(mock_update_character.delay.call_count, 1)
|
||||
self.assertEqual(mock_update_character.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_character.delay.call_args[0][0]),
|
||||
int(mock_update_character.apply_async.call_args[1]['args'][0]),
|
||||
1234
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,27 +1,53 @@
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db.models import Q
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.db.models import Q, QuerySet
|
||||
|
||||
from allianceauth.authentication.models import State
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GroupManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_joinable_groups_for_user(
|
||||
cls, user: User, include_hidden = True
|
||||
) -> QuerySet:
|
||||
"""get groups a user could join incl. groups already joined"""
|
||||
groups_qs = cls.get_joinable_groups(user.profile.state)
|
||||
|
||||
if not user.has_perm('groupmanagement.request_groups'):
|
||||
groups_qs = groups_qs.filter(authgroup__public=True)
|
||||
|
||||
if not include_hidden:
|
||||
groups_qs = groups_qs.filter(authgroup__hidden=False)
|
||||
|
||||
return groups_qs
|
||||
|
||||
@staticmethod
|
||||
def get_joinable_groups(state):
|
||||
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)\
|
||||
def get_joinable_groups(state: State) -> QuerySet:
|
||||
"""get groups that can be joined by user with given state"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)\
|
||||
.filter(Q(authgroup__states=state) | Q(authgroup__states=None))
|
||||
|
||||
@staticmethod
|
||||
def get_all_non_internal_groups():
|
||||
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)
|
||||
def get_all_non_internal_groups() -> QuerySet:
|
||||
"""get groups that are not internal"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)
|
||||
|
||||
@staticmethod
|
||||
def get_group_leaders_groups(user):
|
||||
def get_group_leaders_groups(user: User):
|
||||
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \
|
||||
Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
|
||||
|
||||
@staticmethod
|
||||
def joinable_group(group, state):
|
||||
def joinable_group(group: Group, state: State) -> bool:
|
||||
"""
|
||||
Check if a group is a user/state joinable group, i.e.
|
||||
not an internal group for Corp, Alliance, Members etc,
|
||||
@@ -30,12 +56,15 @@ class GroupManager:
|
||||
:param state: allianceauth.authentication.State object
|
||||
:return: bool True if its joinable, False otherwise
|
||||
"""
|
||||
if len(group.authgroup.states.all()) != 0 and state not in group.authgroup.states.all():
|
||||
if (len(group.authgroup.states.all()) != 0
|
||||
and state not in group.authgroup.states.all()
|
||||
):
|
||||
return False
|
||||
return not group.authgroup.internal
|
||||
else:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def check_internal_group(group):
|
||||
def check_internal_group(group: Group) -> bool:
|
||||
"""
|
||||
Check if a group is auditable, i.e not an internal group
|
||||
:param group: django.contrib.auth.models.Group object
|
||||
@@ -44,20 +73,11 @@ class GroupManager:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def check_internal_group(group):
|
||||
"""
|
||||
Check if a group is auditable, i.e not an internal group
|
||||
:param group: django.contrib.auth.models.Group object
|
||||
:return: bool True if it is auditable, false otherwise
|
||||
"""
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def has_management_permission(user):
|
||||
def has_management_permission(user: User) -> bool:
|
||||
return user.has_perm('auth.group_management')
|
||||
|
||||
@classmethod
|
||||
def can_manage_groups(cls, user):
|
||||
def can_manage_groups(cls, user:User ) -> bool:
|
||||
"""
|
||||
For use with user_passes_test decorator.
|
||||
Check if the user can manage groups. Either has the
|
||||
@@ -71,7 +91,7 @@ class GroupManager:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_manage_group(cls, user, group):
|
||||
def can_manage_group(cls, user: User, group: Group) -> bool:
|
||||
"""
|
||||
Check user has permission to manage the given group
|
||||
:param user: User object to test permission of
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
Back
|
||||
{% trans "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if entries %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
Back
|
||||
{% trans "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if group.user_set %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
Groups
|
||||
{% trans "Groups" %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if groups %}
|
||||
@@ -53,6 +53,10 @@
|
||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
|
||||
<i class="glyphicon glyphicon-list-alt"></i>
|
||||
</a>
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% trans "Copy Direct Join Link" %}">
|
||||
<i class="glyphicon glyphicon-copy"></i>
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -68,3 +72,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block extra_javascript %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js"></script>
|
||||
<script>
|
||||
new ClipboardJS('#clipboard-copy');
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,107 +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_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)
|
||||
|
||||
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
|
||||
|
||||
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 GroupManagementStateTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
||||
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
|
||||
cls.state_group = Group.objects.create(name='state_group')
|
||||
cls.open_group = Group.objects.create(name='open_group')
|
||||
cls.state = AuthUtils.create_state('test state', 500)
|
||||
cls.state_group.authgroup.states.add(cls.state)
|
||||
cls.state_group.authgroup.internal = False
|
||||
cls.state_group.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
self.state.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
def _refresh_test_group(self):
|
||||
self.state_group = Group.objects.get(pk=self.state_group.pk)
|
||||
|
||||
def test_drop_state_group(self):
|
||||
self.user.groups.add(self.open_group)
|
||||
self.user.groups.add(self.state_group)
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
|
||||
self.state.member_corporations.add(self.corp)
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state, self.state)
|
||||
groups = self.user.groups.all()
|
||||
self.assertIn(self.state_group, groups) #keeps group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
|
||||
self.state.member_corporations.clear()
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
groups = self.user.groups.all()
|
||||
self.assertNotIn(self.state_group, groups) #looses group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
337
allianceauth/groupmanagement/tests/test_managers.py
Normal file
337
allianceauth/groupmanagement/tests/test_managers.py
Normal file
@@ -0,0 +1,337 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..models import AuthGroup
|
||||
from ..managers import GroupManager
|
||||
|
||||
|
||||
class MockUserNotAuthenticated():
|
||||
def __init__(self):
|
||||
self.is_authenticated = False
|
||||
|
||||
class GroupManagementVisibilityTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(
|
||||
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
|
||||
)
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
||||
cls.corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
|
||||
)
|
||||
cls.group1 = Group.objects.create(name='group1')
|
||||
cls.group2 = Group.objects.create(name='group2')
|
||||
cls.group3 = Group.objects.create(name='group3')
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
|
||||
def test_get_group_leaders_groups(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
|
||||
def test_can_manage_group(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
|
||||
self.assertTrue(GroupManager.can_manage_group(self.user, self.group1))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group2))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
|
||||
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self.group1.authgroup.group_leaders.remove(self.user)
|
||||
self._refresh_user()
|
||||
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group1))
|
||||
self.assertTrue(GroupManager.can_manage_group(self.user, self.group2))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
|
||||
|
||||
|
||||
class TestGroupManager(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# group 1
|
||||
cls.group_default = Group.objects.create(name='default')
|
||||
cls.group_default.authgroup.description = 'Default Group'
|
||||
cls.group_default.authgroup.internal = False
|
||||
cls.group_default.authgroup.hidden = False
|
||||
cls.group_default.authgroup.save()
|
||||
|
||||
# group 2
|
||||
cls.group_internal = Group.objects.create(name='internal')
|
||||
cls.group_internal.authgroup.description = 'Internal Group'
|
||||
cls.group_internal.authgroup.internal = True
|
||||
cls.group_internal.authgroup.save()
|
||||
|
||||
# group 3
|
||||
cls.group_hidden = Group.objects.create(name='hidden')
|
||||
cls.group_hidden.authgroup.description = 'Hidden Group'
|
||||
cls.group_hidden.authgroup.internal = False
|
||||
cls.group_hidden.authgroup.hidden = True
|
||||
cls.group_hidden.authgroup.save()
|
||||
|
||||
# group 4
|
||||
cls.group_open = Group.objects.create(name='open')
|
||||
cls.group_open.authgroup.description = 'Open Group'
|
||||
cls.group_open.authgroup.internal = False
|
||||
cls.group_open.authgroup.hidden = False
|
||||
cls.group_open.authgroup.open = True
|
||||
cls.group_open.authgroup.save()
|
||||
|
||||
# group 5
|
||||
cls.group_public_1 = Group.objects.create(name='public 1')
|
||||
cls.group_public_1.authgroup.description = 'Public Group 1'
|
||||
cls.group_public_1.authgroup.internal = False
|
||||
cls.group_public_1.authgroup.hidden = False
|
||||
cls.group_public_1.authgroup.public = True
|
||||
cls.group_public_1.authgroup.save()
|
||||
|
||||
# group 6
|
||||
cls.group_public_2 = Group.objects.create(name='public 2')
|
||||
cls.group_public_2.authgroup.description = 'Public Group 2'
|
||||
cls.group_public_2.authgroup.internal = False
|
||||
cls.group_public_2.authgroup.hidden = True
|
||||
cls.group_public_2.authgroup.open = True
|
||||
cls.group_public_2.authgroup.public = True
|
||||
cls.group_public_2.authgroup.save()
|
||||
|
||||
# group 7
|
||||
cls.group_default_member = Group.objects.create(name='default members')
|
||||
cls.group_default_member.authgroup.description = \
|
||||
'Default Group for members only'
|
||||
cls.group_default_member.authgroup.internal = False
|
||||
cls.group_default_member.authgroup.hidden = False
|
||||
cls.group_default_member.authgroup.open = False
|
||||
cls.group_default_member.authgroup.public = False
|
||||
cls.group_default_member.authgroup.states.add(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
cls.group_default_member.authgroup.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_get_joinable_group_member(self):
|
||||
result = GroupManager.get_joinable_groups(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_group_guest(self):
|
||||
result = GroupManager.get_joinable_groups(
|
||||
AuthUtils.get_guest_state()
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_joinable_group_member(self):
|
||||
member_state = AuthUtils.get_member_state()
|
||||
for x in [
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
]:
|
||||
self.assertTrue(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
for x in [
|
||||
self.group_internal,
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
|
||||
def test_joinable_group_guest(self):
|
||||
guest_state = AuthUtils.get_guest_state()
|
||||
for x in [
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
]:
|
||||
self.assertTrue(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
for x in [
|
||||
self.group_internal,
|
||||
self.group_default_member
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
|
||||
def test_get_all_non_internal_groups(self):
|
||||
result = GroupManager.get_all_non_internal_groups()
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_check_internal_group(self):
|
||||
self.assertTrue(
|
||||
GroupManager.check_internal_group(self.group_default)
|
||||
)
|
||||
self.assertFalse(
|
||||
GroupManager.check_internal_group(self.group_internal)
|
||||
)
|
||||
|
||||
def test_get_joinable_groups_for_user_no_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected= {self.group_public_1, self.group_public_2}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_guest_w_permission_(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission_no_hidden(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(
|
||||
self.user, include_hidden=False
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_has_management_permission(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.has_management_permission(user))
|
||||
|
||||
def test_can_manage_groups_no_perm_no_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.assertFalse(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_user_not_authenticated(self):
|
||||
user = MockUserNotAuthenticated()
|
||||
self.assertFalse(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_no_perm_leads_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_default.authgroup.group_leaders.add(user)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_group_no_perm_no_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_no_perm_leads_correct_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_default.authgroup.group_leaders.add(user)
|
||||
self.assertTrue(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_no_perm_leads_other_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_hidden.authgroup.group_leaders.add(user)
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_user_not_authenticated(self):
|
||||
user = MockUserNotAuthenticated()
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
167
allianceauth/groupmanagement/tests/test_models.py
Normal file
167
allianceauth/groupmanagement/tests/test_models.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
)
|
||||
|
||||
from ..models import GroupRequest, RequestLog
|
||||
|
||||
|
||||
def create_testdata():
|
||||
# clear DB
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
EveCharacter.objects.all().delete()
|
||||
EveCorporationInfo.objects.all().delete()
|
||||
EveAllianceInfo.objects.all().delete()
|
||||
|
||||
# group 1
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
# user 1
|
||||
user_1 = AuthUtils.create_user('Bruce Wayne')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_1,
|
||||
name='Bruce Wayne',
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name='Wayne Technologies'
|
||||
)
|
||||
user_1.groups.add(group)
|
||||
group.authgroup.group_leaders.add(user_1)
|
||||
|
||||
# user 2
|
||||
user_2 = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_2,
|
||||
name='Clark Kent',
|
||||
character_id=1002,
|
||||
corp_id=2002,
|
||||
corp_name='Wayne Technologies'
|
||||
)
|
||||
return group, user_1, user_2
|
||||
|
||||
|
||||
|
||||
class TestGroupRequest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, _ = create_testdata()
|
||||
|
||||
def test_main_char(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
status='Pending',
|
||||
user=self.user_1,
|
||||
group=self.group
|
||||
)
|
||||
expected = self.user_1.profile.main_character
|
||||
self.assertEqual(group_request.main_char, expected)
|
||||
|
||||
def test_str(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
status='Pending',
|
||||
user=self.user_1,
|
||||
group=self.group
|
||||
)
|
||||
expected = 'Bruce Wayne:Superheros'
|
||||
self.assertEqual(str(group_request), expected)
|
||||
|
||||
|
||||
class TestRequestLog(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, cls.user_2 = create_testdata()
|
||||
|
||||
def test_requestor(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Clark Kent'
|
||||
self.assertEqual(request_log.requestor(), expected)
|
||||
|
||||
def test_type_to_str_removed(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=None,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Removed'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_type_to_str_leave(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=True,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Leave'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_type_to_str_join(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=False,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Join'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_action_to_str_accept(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = True
|
||||
)
|
||||
expected = 'Accept'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
|
||||
def test_action_to_str_reject(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
)
|
||||
expected = 'Reject'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
|
||||
def test_req_char(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
)
|
||||
expected = self.user_2.profile.main_character
|
||||
self.assertEqual(request_log.req_char(), expected)
|
||||
|
||||
|
||||
class TestAuthGroup(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
expected = 'Superheros'
|
||||
self.assertEqual(str(group.authgroup), expected)
|
||||
61
allianceauth/groupmanagement/tests/test_signals.py
Normal file
61
allianceauth/groupmanagement/tests/test_signals.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..signals import check_groups_on_state_change
|
||||
|
||||
|
||||
class GroupManagementStateTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(
|
||||
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
|
||||
)
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2'
|
||||
)
|
||||
cls.corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
|
||||
)
|
||||
cls.state_group = Group.objects.create(name='state_group')
|
||||
cls.open_group = Group.objects.create(name='open_group')
|
||||
cls.state = AuthUtils.create_state('test state', 500)
|
||||
cls.state_group.authgroup.states.add(cls.state)
|
||||
cls.state_group.authgroup.internal = False
|
||||
cls.state_group.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
self.state.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
def _refresh_test_group(self):
|
||||
self.state_group = Group.objects.get(pk=self.state_group.pk)
|
||||
|
||||
def test_drop_state_group(self):
|
||||
self.user.groups.add(self.open_group)
|
||||
self.user.groups.add(self.state_group)
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
|
||||
self.state.member_corporations.add(self.corp)
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state, self.state)
|
||||
groups = self.user.groups.all()
|
||||
self.assertIn(self.state_group, groups) #keeps group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
|
||||
self.state.member_corporations.clear()
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
groups = self.user.groups.all()
|
||||
self.assertNotIn(self.state_group, groups) #looses group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
22
allianceauth/groupmanagement/tests/test_views.py
Normal file
22
allianceauth/groupmanagement/tests/test_views.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from esi.models import Token
|
||||
|
||||
from .. import views
|
||||
|
||||
|
||||
class TestViews(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_groups_view_can_load(self):
|
||||
request = self.factory.get(reverse('groupmanagement:groups'))
|
||||
request.user = self.user
|
||||
response = views.groups_view(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
@@ -10,12 +11,12 @@ from django.db.models import Count
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from .managers import GroupManager
|
||||
from .models import GroupRequest, RequestLog
|
||||
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
from django.conf import settings
|
||||
from .managers import GroupManager
|
||||
from .models import GroupRequest, RequestLog
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -235,7 +236,7 @@ def group_reject_request(request, group_request_id):
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to reject group request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to reject group request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -269,9 +270,9 @@ def group_leave_accept_request(request, group_request_id):
|
||||
(request.user, group_request_id))
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to accept group leave request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to accept group leave request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -303,9 +304,9 @@ def group_leave_reject_request(request, group_request_id):
|
||||
(request.user, group_request_id))
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to reject group leave request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to reject group leave request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -315,24 +316,23 @@ def group_leave_reject_request(request, group_request_id):
|
||||
@login_required
|
||||
def groups_view(request):
|
||||
logger.debug("groups_view called by user %s" % request.user)
|
||||
|
||||
groups_qs = GroupManager.get_joinable_groups_for_user(
|
||||
request.user, include_hidden=False
|
||||
)
|
||||
groups_qs = groups_qs.order_by('name')
|
||||
groups = []
|
||||
for group in groups_qs:
|
||||
group_request = GroupRequest.objects\
|
||||
.filter(user=request.user)\
|
||||
.filter(group=group)
|
||||
groups.append({
|
||||
'group': group,
|
||||
'request': group_request[0] if group_request else None
|
||||
})
|
||||
|
||||
group_query = GroupManager.get_joinable_groups(request.user.profile.state)
|
||||
|
||||
if not request.user.has_perm('groupmanagement.request_groups'):
|
||||
# Filter down to public groups only for non-members
|
||||
group_query = group_query.filter(authgroup__public=True)
|
||||
logger.debug("Not a member, only public groups will be available")
|
||||
|
||||
for group in group_query:
|
||||
# Exclude hidden
|
||||
if not group.authgroup.hidden:
|
||||
group_request = GroupRequest.objects.filter(user=request.user).filter(group=group)
|
||||
|
||||
groups.append({'group': group, 'request': group_request[0] if group_request else None})
|
||||
|
||||
render_items = {'groups': groups}
|
||||
return render(request, 'groupmanagement/groups.html', context=render_items)
|
||||
context = {'groups': groups}
|
||||
return render(request, 'groupmanagement/groups.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -349,13 +349,13 @@ def group_request_add(request, group_id):
|
||||
# User is already a member of this group.
|
||||
logger.warning("User %s attempted to join group id %s but they are already a member." %
|
||||
(request.user, group_id))
|
||||
messages.warning(request, "You are already a member of that group.")
|
||||
messages.warning(request, _("You are already a member of that group."))
|
||||
return redirect('groupmanagement:groups')
|
||||
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
||||
# Does not have the required permission, trying to join a non-public group
|
||||
logger.warning("User %s attempted to join group id %s but it is not a public group" %
|
||||
(request.user, group_id))
|
||||
messages.warning(request, "You cannot join that group")
|
||||
messages.warning(request, _("You cannot join that group"))
|
||||
return redirect('groupmanagement:groups')
|
||||
if group.authgroup.open:
|
||||
logger.info("%s joining %s as is an open group" % (request.user, group))
|
||||
@@ -364,7 +364,7 @@ def group_request_add(request, group_id):
|
||||
req = GroupRequest.objects.filter(user=request.user, group=group)
|
||||
if len(req) > 0:
|
||||
logger.info("%s attempted to join %s but already has an open application" % (request.user, group))
|
||||
messages.warning(request, "You already have a pending application for that group.")
|
||||
messages.warning(request, _("You already have a pending application for that group."))
|
||||
return redirect("groupmanagement:groups")
|
||||
grouprequest = GroupRequest()
|
||||
grouprequest.status = _('Pending')
|
||||
@@ -398,7 +398,7 @@ def group_request_leave(request, group_id):
|
||||
req = GroupRequest.objects.filter(user=request.user, group=group)
|
||||
if len(req) > 0:
|
||||
logger.info("%s attempted to leave %s but already has an pending leave request." % (request.user, group))
|
||||
messages.warning(request, "You already have a pending leave request for that group.")
|
||||
messages.warning(request, _("You already have a pending leave request for that group."))
|
||||
return redirect("groupmanagement:groups")
|
||||
if getattr(settings, 'AUTO_LEAVE', False):
|
||||
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))
|
||||
|
||||
Binary file not shown.
@@ -4,18 +4,18 @@
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Rounon Dax <rounon.dax@terra-nanotech.de>, 2020
|
||||
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2020
|
||||
# Joel Falknau <ozirascal@gmail.com>, 2020
|
||||
# Rounon Dax <rounon.dax@terra-nanotech.de>, 2020
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-03-04 23:04+0000\n"
|
||||
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||
"Last-Translator: Joel Falknau <ozirascal@gmail.com>, 2020\n"
|
||||
"Last-Translator: Rounon Dax <rounon.dax@terra-nanotech.de>, 2020\n"
|
||||
"Language-Team: German (https://www.transifex.com/alliance-auth/teams/107430/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -71,9 +71,7 @@ msgid "Change Main"
|
||||
msgstr "Hauptcharakter ändern"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgid "Group Memberships"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
||||
@@ -140,32 +138,41 @@ msgstr "Euer IT Team"
|
||||
msgid "Submit"
|
||||
msgstr "Absenden"
|
||||
|
||||
#: allianceauth/authentication/views.py:39
|
||||
#: allianceauth/authentication/views.py:74
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot change main character to %(char)s: character owned by a different "
|
||||
"account."
|
||||
msgstr ""
|
||||
"Der Haputcharakter kann nicht zu %(char)s geändert werden. Dieser Charakter "
|
||||
"gehört zu einem anderen Konto."
|
||||
|
||||
#: allianceauth/authentication/views.py:80
|
||||
#, python-format
|
||||
msgid "Changed main character to %(char)s"
|
||||
msgstr "Haupcharakter geändert zu %(char)s"
|
||||
|
||||
#: allianceauth/authentication/views.py:48
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#, python-format
|
||||
msgid "Added %(name)s to your account."
|
||||
msgstr "%(name)s zu Deinem Konto hinzugefügt."
|
||||
|
||||
#: allianceauth/authentication/views.py:50
|
||||
#: allianceauth/authentication/views.py:91
|
||||
#, python-format
|
||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||
msgstr ""
|
||||
"Es ist nicht möglich %(name)s zu Deinem Konto hinzu zu fügen: Dieser hat "
|
||||
"bereits ein eigenes Konto."
|
||||
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#: allianceauth/authentication/views.py:130
|
||||
msgid "Unable to authenticate as the selected character."
|
||||
msgstr "Authentifizierung mit dem ausgewählten Charakter nicht möglich."
|
||||
|
||||
#: allianceauth/authentication/views.py:107
|
||||
#: allianceauth/authentication/views.py:148
|
||||
msgid "Registration token has expired."
|
||||
msgstr "Token zur Registrierung ist abgelaufen."
|
||||
|
||||
#: allianceauth/authentication/views.py:159
|
||||
#: allianceauth/authentication/views.py:200
|
||||
msgid ""
|
||||
"Sent confirmation email. Please follow the link to confirm your email "
|
||||
"address."
|
||||
@@ -173,12 +180,12 @@ msgstr ""
|
||||
"Bestätigungsmail gesendet. Bitte folge dem Link in der E-Mail zur "
|
||||
"Bestätigung."
|
||||
|
||||
#: allianceauth/authentication/views.py:164
|
||||
#: allianceauth/authentication/views.py:205
|
||||
msgid "Confirmed your email address. Please login to continue."
|
||||
msgstr ""
|
||||
"Deine E-Mail Adresse wurde bestätigt. Bitte log Dich ein um fortzufahren."
|
||||
|
||||
#: allianceauth/authentication/views.py:169
|
||||
#: allianceauth/authentication/views.py:210
|
||||
msgid "Registraion of new accounts it not allowed at this time."
|
||||
msgstr "Registrierung von neuen Konten ist zur Zeit nicht erlaubt."
|
||||
|
||||
@@ -540,6 +547,12 @@ msgstr "FAT-Link ist abgelaufen."
|
||||
msgid "Audit Log"
|
||||
msgstr "Protokoll"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
|
||||
msgid "Date/Time"
|
||||
msgstr "Datum/Uhrzeit"
|
||||
@@ -599,6 +612,12 @@ msgstr "Keine Gruppenmitglieder vorhanden."
|
||||
msgid "Groups Membership"
|
||||
msgstr "Gruppenmitgliedschaft"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
|
||||
msgid "Description"
|
||||
@@ -638,7 +657,11 @@ msgstr "Mitglieder ansehen"
|
||||
msgid "Audit Members"
|
||||
msgstr "Mitglieder Protokoll"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||
msgid "Copy Direrct Join Link"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||
msgid "No groups to list."
|
||||
msgstr "Keine Gruppen vorhanden."
|
||||
|
||||
@@ -718,26 +741,26 @@ msgstr "Gruppenanfragen"
|
||||
msgid "Group Membership"
|
||||
msgstr "Gruppenmitgliedschaft"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:164
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#, python-format
|
||||
msgid "Removed user %(user)s from group %(group)s."
|
||||
msgstr "Benutzer %(user)s von Gruppe %(group)s entfernt."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#: allianceauth/groupmanagement/views.py:168
|
||||
msgid "User does not exist in that group"
|
||||
msgstr "Benutzer existiert nicht in dieser Gruppe"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:169
|
||||
#: allianceauth/groupmanagement/views.py:171
|
||||
msgid "Group does not exist"
|
||||
msgstr "Gruppe existiert nicht"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:196
|
||||
#: allianceauth/groupmanagement/views.py:198
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s zugestimmt."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:203
|
||||
#: allianceauth/groupmanagement/views.py:236
|
||||
#: allianceauth/groupmanagement/views.py:205
|
||||
#: allianceauth/groupmanagement/views.py:238
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occurred while processing the application from "
|
||||
@@ -746,37 +769,46 @@ msgstr ""
|
||||
"Bei der Bearbeitung des Beitrittsgesuchs von %(mainchar)s zur Gruppe "
|
||||
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:229
|
||||
#: allianceauth/groupmanagement/views.py:231
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s abgelehnt."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:265
|
||||
#: allianceauth/groupmanagement/views.py:267
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s akzeptiert."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:271
|
||||
#: allianceauth/groupmanagement/views.py:305
|
||||
#: allianceauth/groupmanagement/views.py:273
|
||||
#: allianceauth/groupmanagement/views.py:307
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occured while processing the application from "
|
||||
"An unhandled error occurred while processing the application from "
|
||||
"%(mainchar)s to leave %(group)s."
|
||||
msgstr ""
|
||||
"Bei der Bearbeitung des Austrittsgesuchs von %(mainchar)s für Gruppe "
|
||||
"%(group)s ist ein unbehandelter Fehler aufgetreten."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:298
|
||||
#: allianceauth/groupmanagement/views.py:300
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s abgelehnt."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:345
|
||||
#: allianceauth/groupmanagement/views.py:346
|
||||
#: allianceauth/groupmanagement/views.py:358
|
||||
msgid "You cannot join that group"
|
||||
msgstr "Du kannst dieser Gruppe nicht beitreten"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:369
|
||||
#: allianceauth/groupmanagement/views.py:407
|
||||
#: allianceauth/groupmanagement/views.py:352
|
||||
msgid "You are already a member of that group."
|
||||
msgstr "Du bist bereits Mitglied dieser Gruppe."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:367
|
||||
msgid "You already have a pending application for that group."
|
||||
msgstr "Du hast Dich bereits für diese Gruppe beworben."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:370
|
||||
#: allianceauth/groupmanagement/views.py:408
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||
@@ -788,20 +820,24 @@ msgstr "Du kannst dieser Gruppe nicht beitreten"
|
||||
msgid "Pending"
|
||||
msgstr "Beantragt"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:375
|
||||
#: allianceauth/groupmanagement/views.py:376
|
||||
#, python-format
|
||||
msgid "Applied to group %(group)s."
|
||||
msgstr "Beitritt zur Gruppe %(group)s beantragt."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:386
|
||||
#: allianceauth/groupmanagement/views.py:387
|
||||
msgid "You cannot leave that group"
|
||||
msgstr "Du kannst diese Gruppe nicht verlassen"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:391
|
||||
#: allianceauth/groupmanagement/views.py:392
|
||||
msgid "You are not a member of that group"
|
||||
msgstr "Du bist kein Mitglied dieser Gruppe"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:413
|
||||
#: allianceauth/groupmanagement/views.py:401
|
||||
msgid "You already have a pending leave request for that group."
|
||||
msgstr "Du hast bereits ein ausstehendes Austrittsgesuch für diese Gruppe."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:414
|
||||
#, python-format
|
||||
msgid "Applied to leave group %(group)s."
|
||||
msgstr "Austrittsgesuch für Gruppe %(group)s gesendet."
|
||||
@@ -1145,11 +1181,7 @@ msgstr "Änderungen für Operation timer %(opname)s gespeichert."
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:6
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:10
|
||||
msgid "Permissions Audit"
|
||||
msgstr "Berechtigungsprüfung"
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
msgstr "Berechtigungsübersicht"
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
||||
msgid "User / Character"
|
||||
@@ -1196,6 +1228,18 @@ msgstr "Nutzer"
|
||||
msgid "States"
|
||||
msgstr "Status"
|
||||
|
||||
#: allianceauth/services/abstract.py:72
|
||||
msgid "That service account already exists"
|
||||
msgstr "Dieses Dienstkonto existiert bereits"
|
||||
|
||||
#: allianceauth/services/abstract.py:104
|
||||
msgid "Successfully set your {} password"
|
||||
msgstr "Dein {} Passwort wurde erfolgreich gesetzt"
|
||||
|
||||
#: allianceauth/services/auth_hooks.py:11
|
||||
msgid "Services"
|
||||
msgstr "Dienste"
|
||||
|
||||
#: allianceauth/services/forms.py:6
|
||||
msgid "Name of Fleet:"
|
||||
msgstr "SRP Flotte erstellen:"
|
||||
@@ -1260,15 +1304,85 @@ msgstr "Passwort muss mindestens 8 Zeichen lang sein"
|
||||
msgid "Link Discord Server"
|
||||
msgstr "Verbinde Discord Server"
|
||||
|
||||
#: allianceauth/services/modules/openfire/forms.py:7
|
||||
msgid "Message"
|
||||
msgstr "Nachricht"
|
||||
#: allianceauth/services/modules/discord/views.py:26
|
||||
msgid "Deactivated Discord account."
|
||||
msgstr "Discord Konto deaktiviert."
|
||||
|
||||
#: allianceauth/services/modules/discord/views.py:29
|
||||
#: allianceauth/services/modules/discord/views.py:41
|
||||
#: allianceauth/services/modules/discord/views.py:65
|
||||
msgid "An error occurred while processing your Discord account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Discord Kontos."
|
||||
|
||||
#: allianceauth/services/modules/discord/views.py:62
|
||||
msgid "Activated Discord account."
|
||||
msgstr "Discord Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:37
|
||||
msgid "You are not authorized to access Discourse."
|
||||
msgstr "Du bist nicht autorisiert auf Discorse zuzugreifen."
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:42
|
||||
msgid "You must have a main character set to access Discourse."
|
||||
msgstr ""
|
||||
"Du musst einen Hauptcharakter gesetzt haben um auf Discourse zuzugreifen."
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:52
|
||||
msgid ""
|
||||
"No SSO payload or signature. Please contact support if this problem "
|
||||
"persists."
|
||||
msgstr ""
|
||||
"Keine SSO-Nutzdaten oder Signaturen. Bitte wenden Sie sich an den Support, "
|
||||
"wenn das Problem weiterhin besteht."
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:62
|
||||
#: allianceauth/services/modules/discourse/views.py:70
|
||||
msgid "Invalid payload. Please contact support if this problem persists."
|
||||
msgstr ""
|
||||
"Ungültige Nutzdaten. Bitte wenden Sie sich an den Support, wenn das Problem "
|
||||
"weiterhin besteht."
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:31
|
||||
msgid "Activated IPSuite4 account."
|
||||
msgstr "IP4Suite Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:40
|
||||
#: allianceauth/services/modules/ips4/views.py:62
|
||||
#: allianceauth/services/modules/ips4/views.py:83
|
||||
#: allianceauth/services/modules/ips4/views.py:103
|
||||
msgid "An error occurred while processing your IPSuite4 account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines IPSuite4 Kontos."
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:53
|
||||
msgid "Reset IPSuite4 password."
|
||||
msgstr "IPSuite4 Passwort zurücksetzen."
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:80
|
||||
msgid "Set IPSuite4 password."
|
||||
msgstr "Setze IPSuite4 Passwort."
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:100
|
||||
msgid "Deactivated IPSuite4 account."
|
||||
msgstr "IP4Suite Konto deaktiviert."
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:26
|
||||
msgid "Jabber"
|
||||
msgstr "Jabber"
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:78
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:11
|
||||
msgid "Jabber Broadcast"
|
||||
msgstr "Jabber Übertragung"
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:91
|
||||
msgid "Fleet Broadcast Formatter"
|
||||
msgstr "Flotten Ping Formatierung"
|
||||
|
||||
#: allianceauth/services/modules/openfire/forms.py:7
|
||||
msgid "Message"
|
||||
msgstr "Nachricht"
|
||||
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:17
|
||||
msgid "Broadcast Sent!!"
|
||||
msgstr "Übertragung gesendet!!"
|
||||
@@ -1277,6 +1391,76 @@ msgstr "Übertragung gesendet!!"
|
||||
msgid "Broadcast"
|
||||
msgstr "Übertragungen"
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:35
|
||||
msgid "Activated jabber account."
|
||||
msgstr "Jabber Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:44
|
||||
#: allianceauth/services/modules/openfire/views.py:57
|
||||
#: allianceauth/services/modules/openfire/views.py:78
|
||||
#: allianceauth/services/modules/openfire/views.py:151
|
||||
msgid "An error occurred while processing your jabber account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Jabber Kontos."
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:70
|
||||
msgid "Reset jabber password."
|
||||
msgstr "Jabber Passwort zurücksetzen."
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:119
|
||||
#, python-format
|
||||
msgid "Sent jabber broadcast to %s"
|
||||
msgstr "Sende Jabber Durchsage an %s"
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:148
|
||||
msgid "Set jabber password."
|
||||
msgstr "Setze Jabber Passwort."
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||
msgid "Activated forum account."
|
||||
msgstr "Forum Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:43
|
||||
#: allianceauth/services/modules/phpbb3/views.py:57
|
||||
#: allianceauth/services/modules/phpbb3/views.py:80
|
||||
#: allianceauth/services/modules/phpbb3/views.py:103
|
||||
msgid "An error occurred while processing your forum account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Forum Kontos."
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:54
|
||||
msgid "Deactivated forum account."
|
||||
msgstr "Forum Konto deaktiviert."
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:71
|
||||
msgid "Reset forum password."
|
||||
msgstr "Forum Passwort zurücksetzen."
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:100
|
||||
msgid "Set forum password."
|
||||
msgstr "Setze Forum Passwort."
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:34
|
||||
msgid "Activated SMF account."
|
||||
msgstr "SMF Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:43
|
||||
#: allianceauth/services/modules/smf/views.py:58
|
||||
#: allianceauth/services/modules/smf/views.py:80
|
||||
#: allianceauth/services/modules/smf/views.py:103
|
||||
msgid "An error occurred while processing your SMF account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines SMF Kontos."
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:55
|
||||
msgid "Deactivated SMF account."
|
||||
msgstr "SMF Konto deaktiviert."
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:72
|
||||
msgid "Reset SMF password."
|
||||
msgstr "SMF Passwort zurücksetzen."
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:100
|
||||
msgid "Set SMF password."
|
||||
msgstr "Setze SMF Passwort."
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
||||
#, python-format
|
||||
msgid "Unable to locate user %s on server"
|
||||
@@ -1300,6 +1484,47 @@ msgstr "Server beitreten"
|
||||
msgid "Continue"
|
||||
msgstr "Fortsetzen"
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:34
|
||||
msgid "Activated TeamSpeak3 account."
|
||||
msgstr "TeamSpeak3 Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:37
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:74
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:100
|
||||
msgid "An error occurred while processing your TeamSpeak3 account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines TeamSpeak3 Kontos."
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:71
|
||||
msgid "Deactivated TeamSpeak3 account."
|
||||
msgstr "TeamSpeak3 Konto deaktiviert."
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:97
|
||||
msgid "Reset TeamSpeak3 permission key."
|
||||
msgstr "TeamSpeak3 Berechtigungsschlüssel zurücksetzen."
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:30
|
||||
msgid "Activated XenForo account."
|
||||
msgstr "XenForo Konto aktiviert."
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:40
|
||||
#: allianceauth/services/modules/xenforo/views.py:52
|
||||
#: allianceauth/services/modules/xenforo/views.py:73
|
||||
#: allianceauth/services/modules/xenforo/views.py:94
|
||||
msgid "An error occurred while processing your XenForo account."
|
||||
msgstr "Es gab einen Fehler bei der Verarbeitung Deines XenForo Kontos."
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:50
|
||||
msgid "Deactivated XenForo account."
|
||||
msgstr "XenForo Konto deaktiviert."
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:65
|
||||
msgid "Reset XenForo account password."
|
||||
msgstr "XenForo Passwort zurüclsetzen."
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:91
|
||||
msgid "Changed XenForo password."
|
||||
msgstr "Setze XenForo Passwort."
|
||||
|
||||
#: allianceauth/services/templates/services/fleetformattertool.html:6
|
||||
msgid "Fleet Formatter Tool"
|
||||
msgstr "Flottenformatierung"
|
||||
@@ -1691,12 +1916,6 @@ msgid_plural "%(tasks)s tasks"
|
||||
msgstr[0] "%(tasks)sAufgabe"
|
||||
msgstr[1] "%(tasks)sAufgaben"
|
||||
|
||||
#: allianceauth/templates/allianceauth/help.html:4
|
||||
#: allianceauth/templates/allianceauth/help.html:9
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:34
|
||||
msgid "Help"
|
||||
msgstr "Hilfe"
|
||||
|
||||
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
||||
msgid "Night"
|
||||
msgstr "Nacht"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-03-09 15:16+0000\n"
|
||||
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -65,9 +65,7 @@ msgid "Change Main"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgid "Group Memberships"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
||||
@@ -132,40 +130,47 @@ msgstr ""
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:39
|
||||
#: allianceauth/authentication/views.py:74
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot change main character to %(char)s: character owned by a different "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:80
|
||||
#, python-format
|
||||
msgid "Changed main character to %(char)s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:48
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#, python-format
|
||||
msgid "Added %(name)s to your account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:50
|
||||
#: allianceauth/authentication/views.py:91
|
||||
#, python-format
|
||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#: allianceauth/authentication/views.py:130
|
||||
msgid "Unable to authenticate as the selected character."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:107
|
||||
#: allianceauth/authentication/views.py:148
|
||||
msgid "Registration token has expired."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:159
|
||||
#: allianceauth/authentication/views.py:200
|
||||
msgid ""
|
||||
"Sent confirmation email. Please follow the link to confirm your email "
|
||||
"address."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:164
|
||||
#: allianceauth/authentication/views.py:205
|
||||
msgid "Confirmed your email address. Please login to continue."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:169
|
||||
#: allianceauth/authentication/views.py:210
|
||||
msgid "Registraion of new accounts it not allowed at this time."
|
||||
msgstr ""
|
||||
|
||||
@@ -527,6 +532,12 @@ msgstr ""
|
||||
msgid "Audit Log"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
|
||||
msgid "Date/Time"
|
||||
msgstr ""
|
||||
@@ -586,6 +597,12 @@ msgstr ""
|
||||
msgid "Groups Membership"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
|
||||
msgid "Description"
|
||||
@@ -625,7 +642,11 @@ msgstr ""
|
||||
msgid "Audit Members"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||
msgid "Copy Direrct Join Link"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||
msgid "No groups to list."
|
||||
msgstr ""
|
||||
|
||||
@@ -705,61 +726,70 @@ msgstr ""
|
||||
msgid "Group Membership"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:164
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#, python-format
|
||||
msgid "Removed user %(user)s from group %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#: allianceauth/groupmanagement/views.py:168
|
||||
msgid "User does not exist in that group"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:169
|
||||
#: allianceauth/groupmanagement/views.py:171
|
||||
msgid "Group does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:196
|
||||
#: allianceauth/groupmanagement/views.py:198
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:203
|
||||
#: allianceauth/groupmanagement/views.py:236
|
||||
#: allianceauth/groupmanagement/views.py:205
|
||||
#: allianceauth/groupmanagement/views.py:238
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occurred while processing the application from "
|
||||
"%(mainchar)s to %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:229
|
||||
#: allianceauth/groupmanagement/views.py:231
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:265
|
||||
#: allianceauth/groupmanagement/views.py:267
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:271
|
||||
#: allianceauth/groupmanagement/views.py:305
|
||||
#: allianceauth/groupmanagement/views.py:273
|
||||
#: allianceauth/groupmanagement/views.py:307
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occured while processing the application from "
|
||||
"An unhandled error occurred while processing the application from "
|
||||
"%(mainchar)s to leave %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:298
|
||||
#: allianceauth/groupmanagement/views.py:300
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:345
|
||||
#: allianceauth/groupmanagement/views.py:346
|
||||
#: allianceauth/groupmanagement/views.py:358
|
||||
msgid "You cannot join that group"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:369
|
||||
#: allianceauth/groupmanagement/views.py:407
|
||||
#: allianceauth/groupmanagement/views.py:352
|
||||
msgid "You are already a member of that group."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:367
|
||||
msgid "You already have a pending application for that group."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:370
|
||||
#: allianceauth/groupmanagement/views.py:408
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||
@@ -771,20 +801,24 @@ msgstr ""
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:375
|
||||
#: allianceauth/groupmanagement/views.py:376
|
||||
#, python-format
|
||||
msgid "Applied to group %(group)s."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:386
|
||||
#: allianceauth/groupmanagement/views.py:387
|
||||
msgid "You cannot leave that group"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:391
|
||||
#: allianceauth/groupmanagement/views.py:392
|
||||
msgid "You are not a member of that group"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:413
|
||||
#: allianceauth/groupmanagement/views.py:401
|
||||
msgid "You already have a pending leave request for that group."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:414
|
||||
#, python-format
|
||||
msgid "Applied to leave group %(group)s."
|
||||
msgstr ""
|
||||
@@ -1130,10 +1164,6 @@ msgstr ""
|
||||
msgid "Permissions Audit"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
||||
msgid "User / Character"
|
||||
msgstr ""
|
||||
@@ -1179,6 +1209,18 @@ msgstr ""
|
||||
msgid "States"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/abstract.py:72
|
||||
msgid "That service account already exists"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/abstract.py:104
|
||||
msgid "Successfully set your {} password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/auth_hooks.py:11
|
||||
msgid "Services"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/forms.py:6
|
||||
msgid "Name of Fleet:"
|
||||
msgstr ""
|
||||
@@ -1243,15 +1285,79 @@ msgstr ""
|
||||
msgid "Link Discord Server"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/forms.py:7
|
||||
msgid "Message"
|
||||
#: allianceauth/services/modules/discord/views.py:26
|
||||
msgid "Deactivated Discord account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/views.py:29
|
||||
#: allianceauth/services/modules/discord/views.py:41
|
||||
#: allianceauth/services/modules/discord/views.py:65
|
||||
msgid "An error occurred while processing your Discord account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/views.py:62
|
||||
msgid "Activated Discord account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:37
|
||||
msgid "You are not authorized to access Discourse."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:42
|
||||
msgid "You must have a main character set to access Discourse."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:52
|
||||
msgid ""
|
||||
"No SSO payload or signature. Please contact support if this problem persists."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:62
|
||||
#: allianceauth/services/modules/discourse/views.py:70
|
||||
msgid "Invalid payload. Please contact support if this problem persists."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:31
|
||||
msgid "Activated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:40
|
||||
#: allianceauth/services/modules/ips4/views.py:62
|
||||
#: allianceauth/services/modules/ips4/views.py:83
|
||||
#: allianceauth/services/modules/ips4/views.py:103
|
||||
msgid "An error occurred while processing your IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:53
|
||||
msgid "Reset IPSuite4 password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:80
|
||||
msgid "Set IPSuite4 password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:100
|
||||
msgid "Deactivated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:26
|
||||
msgid "Jabber"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:78
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:11
|
||||
msgid "Jabber Broadcast"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:91
|
||||
msgid "Fleet Broadcast Formatter"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/forms.py:7
|
||||
msgid "Message"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:17
|
||||
msgid "Broadcast Sent!!"
|
||||
msgstr ""
|
||||
@@ -1260,6 +1366,76 @@ msgstr ""
|
||||
msgid "Broadcast"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:35
|
||||
msgid "Activated jabber account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:44
|
||||
#: allianceauth/services/modules/openfire/views.py:57
|
||||
#: allianceauth/services/modules/openfire/views.py:78
|
||||
#: allianceauth/services/modules/openfire/views.py:151
|
||||
msgid "An error occurred while processing your jabber account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:70
|
||||
msgid "Reset jabber password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:119
|
||||
#, python-format
|
||||
msgid "Sent jabber broadcast to %s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:148
|
||||
msgid "Set jabber password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||
msgid "Activated forum account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:43
|
||||
#: allianceauth/services/modules/phpbb3/views.py:57
|
||||
#: allianceauth/services/modules/phpbb3/views.py:80
|
||||
#: allianceauth/services/modules/phpbb3/views.py:103
|
||||
msgid "An error occurred while processing your forum account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:54
|
||||
msgid "Deactivated forum account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:71
|
||||
msgid "Reset forum password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:100
|
||||
msgid "Set forum password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:34
|
||||
msgid "Activated SMF account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:43
|
||||
#: allianceauth/services/modules/smf/views.py:58
|
||||
#: allianceauth/services/modules/smf/views.py:80
|
||||
#: allianceauth/services/modules/smf/views.py:103
|
||||
msgid "An error occurred while processing your SMF account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:55
|
||||
msgid "Deactivated SMF account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:72
|
||||
msgid "Reset SMF password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:100
|
||||
msgid "Set SMF password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
||||
#, python-format
|
||||
msgid "Unable to locate user %s on server"
|
||||
@@ -1283,6 +1459,47 @@ msgstr ""
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:34
|
||||
msgid "Activated TeamSpeak3 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:37
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:74
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:100
|
||||
msgid "An error occurred while processing your TeamSpeak3 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:71
|
||||
msgid "Deactivated TeamSpeak3 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:97
|
||||
msgid "Reset TeamSpeak3 permission key."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:30
|
||||
msgid "Activated XenForo account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:40
|
||||
#: allianceauth/services/modules/xenforo/views.py:52
|
||||
#: allianceauth/services/modules/xenforo/views.py:73
|
||||
#: allianceauth/services/modules/xenforo/views.py:94
|
||||
msgid "An error occurred while processing your XenForo account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:50
|
||||
msgid "Deactivated XenForo account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:65
|
||||
msgid "Reset XenForo account password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:91
|
||||
msgid "Changed XenForo password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/templates/services/fleetformattertool.html:6
|
||||
msgid "Fleet Formatter Tool"
|
||||
msgstr ""
|
||||
@@ -1663,12 +1880,6 @@ msgid_plural "%(tasks)s tasks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/help.html:4
|
||||
#: allianceauth/templates/allianceauth/help.html:9
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:34
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
||||
msgid "Night"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-03-04 23:04+0000\n"
|
||||
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
|
||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||
"Last-Translator: frank1210 <francolopez_16@hotmail.com>, 2020\n"
|
||||
"Language-Team: Spanish (https://www.transifex.com/alliance-auth/teams/107430/es/)\n"
|
||||
@@ -70,10 +70,8 @@ msgid "Change Main"
|
||||
msgstr "Cambiar Personaje Principal"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgstr "Grupos"
|
||||
msgid "Group Memberships"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||
@@ -137,45 +135,52 @@ msgstr "Tu equipo de IT"
|
||||
msgid "Submit"
|
||||
msgstr "Enviar"
|
||||
|
||||
#: allianceauth/authentication/views.py:39
|
||||
#: allianceauth/authentication/views.py:74
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot change main character to %(char)s: character owned by a different "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/authentication/views.py:80
|
||||
#, python-format
|
||||
msgid "Changed main character to %(char)s"
|
||||
msgstr "Se ha cambiado tu personaje principal ha %(char)s"
|
||||
|
||||
#: allianceauth/authentication/views.py:48
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#, python-format
|
||||
msgid "Added %(name)s to your account."
|
||||
msgstr "Se ha agregado a %(name)s a tu cuenta"
|
||||
|
||||
#: allianceauth/authentication/views.py:50
|
||||
#: allianceauth/authentication/views.py:91
|
||||
#, python-format
|
||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||
msgstr ""
|
||||
"Se fallo en agregar a %(name)s a tu cuenta: Ya se encuentra registrado en "
|
||||
"otra cuenta."
|
||||
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#: allianceauth/authentication/views.py:130
|
||||
msgid "Unable to authenticate as the selected character."
|
||||
msgstr "Imposible validar con el personaje seleccionado."
|
||||
|
||||
#: allianceauth/authentication/views.py:107
|
||||
#: allianceauth/authentication/views.py:148
|
||||
msgid "Registration token has expired."
|
||||
msgstr "El token de registracion expiro."
|
||||
|
||||
#: allianceauth/authentication/views.py:159
|
||||
#: allianceauth/authentication/views.py:200
|
||||
msgid ""
|
||||
"Sent confirmation email. Please follow the link to confirm your email "
|
||||
"address."
|
||||
msgstr ""
|
||||
"Confirmacion de mail enviada. Por favor siga el enlace para confirmar "
|
||||
|
||||
#: allianceauth/authentication/views.py:164
|
||||
#: allianceauth/authentication/views.py:205
|
||||
msgid "Confirmed your email address. Please login to continue."
|
||||
msgstr ""
|
||||
"Se ha confirmado su direccion de mail. Por favor igrese su token para "
|
||||
"continuar."
|
||||
|
||||
#: allianceauth/authentication/views.py:169
|
||||
#: allianceauth/authentication/views.py:210
|
||||
msgid "Registraion of new accounts it not allowed at this time."
|
||||
msgstr "Registracion de nuevas cuentas no es permitido por el momento."
|
||||
|
||||
@@ -538,6 +543,12 @@ msgstr "Enlace de participacion expirado."
|
||||
msgid "Audit Log"
|
||||
msgstr "Log de Auditoria"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||
msgid "Back"
|
||||
msgstr "Volver"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
|
||||
msgid "Date/Time"
|
||||
msgstr "Fecha/Hora"
|
||||
@@ -597,6 +608,12 @@ msgstr "no hay miembros para listar."
|
||||
msgid "Groups Membership"
|
||||
msgstr "Membresia de grupos"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgstr "Grupos"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
|
||||
msgid "Description"
|
||||
@@ -636,7 +653,11 @@ msgstr "Ver Miembros"
|
||||
msgid "Audit Members"
|
||||
msgstr "Auditar Miembros"
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
|
||||
msgid "Copy Direrct Join Link"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
|
||||
msgid "No groups to list."
|
||||
msgstr "No hay grupos para listar"
|
||||
|
||||
@@ -716,26 +737,26 @@ msgstr "Solicitudes de Grupo"
|
||||
msgid "Group Membership"
|
||||
msgstr "Membresia de Grupo"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:164
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#, python-format
|
||||
msgid "Removed user %(user)s from group %(group)s."
|
||||
msgstr "El usuario %(user)s fue removido del grupo %(group)s"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#: allianceauth/groupmanagement/views.py:168
|
||||
msgid "User does not exist in that group"
|
||||
msgstr "El usuario no existe en ese grupos"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:169
|
||||
#: allianceauth/groupmanagement/views.py:171
|
||||
msgid "Group does not exist"
|
||||
msgstr "El grupo no existe"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:196
|
||||
#: allianceauth/groupmanagement/views.py:198
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||
msgstr "Solicitud aceptada de %(mainchar)s a %(group)s"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:203
|
||||
#: allianceauth/groupmanagement/views.py:236
|
||||
#: allianceauth/groupmanagement/views.py:205
|
||||
#: allianceauth/groupmanagement/views.py:238
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occurred while processing the application from "
|
||||
@@ -744,38 +765,45 @@ msgstr ""
|
||||
"Ocurrio un error cuando se intento procesar la informacion de %(mainchar)s "
|
||||
"al grupo %(group)s."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:229
|
||||
#: allianceauth/groupmanagement/views.py:231
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||
msgstr "Se rechazo la solicitud de %(mainchar)s al grupo %(group)s."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:265
|
||||
#: allianceauth/groupmanagement/views.py:267
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||
msgstr "Se acepto la solicitud de %(mainchar)s para dejar el grupo %(group)s."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:271
|
||||
#: allianceauth/groupmanagement/views.py:305
|
||||
#: allianceauth/groupmanagement/views.py:273
|
||||
#: allianceauth/groupmanagement/views.py:307
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occured while processing the application from "
|
||||
"An unhandled error occurred while processing the application from "
|
||||
"%(mainchar)s to leave %(group)s."
|
||||
msgstr ""
|
||||
"Ocurrio un error cuando se intento procesar la informacion de %(mainchar)s "
|
||||
"para dejar el grupo %(group)s."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:298
|
||||
#: allianceauth/groupmanagement/views.py:300
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||
msgstr ""
|
||||
"Se rechazo la solicitud de %(mainchar)s para dejar el grupo %(group)s."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:345
|
||||
#: allianceauth/groupmanagement/views.py:346
|
||||
#: allianceauth/groupmanagement/views.py:358
|
||||
msgid "You cannot join that group"
|
||||
msgstr "No puedes unirte a ese grupo"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:369
|
||||
#: allianceauth/groupmanagement/views.py:407
|
||||
#: allianceauth/groupmanagement/views.py:352
|
||||
msgid "You are already a member of that group."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:367
|
||||
msgid "You already have a pending application for that group."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:370
|
||||
#: allianceauth/groupmanagement/views.py:408
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||
@@ -787,20 +815,24 @@ msgstr "No puedes unirte a ese grupo"
|
||||
msgid "Pending"
|
||||
msgstr "Pendiente"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:375
|
||||
#: allianceauth/groupmanagement/views.py:376
|
||||
#, python-format
|
||||
msgid "Applied to group %(group)s."
|
||||
msgstr "Solicitud enviada al grupo %(group)s."
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:386
|
||||
#: allianceauth/groupmanagement/views.py:387
|
||||
msgid "You cannot leave that group"
|
||||
msgstr "No puedes dejar el grupos"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:391
|
||||
#: allianceauth/groupmanagement/views.py:392
|
||||
msgid "You are not a member of that group"
|
||||
msgstr "No eres miembro de ese grupo"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:413
|
||||
#: allianceauth/groupmanagement/views.py:401
|
||||
msgid "You already have a pending leave request for that group."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:414
|
||||
#, python-format
|
||||
msgid "Applied to leave group %(group)s."
|
||||
msgstr "Solicitaste dejar el grupo %(group)s."
|
||||
@@ -1146,10 +1178,6 @@ msgstr "Se guardaron los cambios para la operacion %(opname)s"
|
||||
msgid "Permissions Audit"
|
||||
msgstr "Auditar Permisos"
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
|
||||
msgid "Back"
|
||||
msgstr "Volver"
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:22
|
||||
msgid "User / Character"
|
||||
msgstr ""
|
||||
@@ -1195,6 +1223,18 @@ msgstr "Usuarios"
|
||||
msgid "States"
|
||||
msgstr "Estados"
|
||||
|
||||
#: allianceauth/services/abstract.py:72
|
||||
msgid "That service account already exists"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/abstract.py:104
|
||||
msgid "Successfully set your {} password"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/auth_hooks.py:11
|
||||
msgid "Services"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/forms.py:6
|
||||
msgid "Name of Fleet:"
|
||||
msgstr "Nombre de la flota:"
|
||||
@@ -1259,15 +1299,80 @@ msgstr "La contraseña tiene que tener 8 caracteres de largo minimo"
|
||||
msgid "Link Discord Server"
|
||||
msgstr "Enlace a servidor de Discord"
|
||||
|
||||
#: allianceauth/services/modules/openfire/forms.py:7
|
||||
msgid "Message"
|
||||
msgstr "Mensaje"
|
||||
#: allianceauth/services/modules/discord/views.py:26
|
||||
msgid "Deactivated Discord account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/views.py:29
|
||||
#: allianceauth/services/modules/discord/views.py:41
|
||||
#: allianceauth/services/modules/discord/views.py:65
|
||||
msgid "An error occurred while processing your Discord account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discord/views.py:62
|
||||
msgid "Activated Discord account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:37
|
||||
msgid "You are not authorized to access Discourse."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:42
|
||||
msgid "You must have a main character set to access Discourse."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:52
|
||||
msgid ""
|
||||
"No SSO payload or signature. Please contact support if this problem "
|
||||
"persists."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/discourse/views.py:62
|
||||
#: allianceauth/services/modules/discourse/views.py:70
|
||||
msgid "Invalid payload. Please contact support if this problem persists."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:31
|
||||
msgid "Activated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:40
|
||||
#: allianceauth/services/modules/ips4/views.py:62
|
||||
#: allianceauth/services/modules/ips4/views.py:83
|
||||
#: allianceauth/services/modules/ips4/views.py:103
|
||||
msgid "An error occurred while processing your IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:53
|
||||
msgid "Reset IPSuite4 password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:80
|
||||
msgid "Set IPSuite4 password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/ips4/views.py:100
|
||||
msgid "Deactivated IPSuite4 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:26
|
||||
msgid "Jabber"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:78
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:11
|
||||
msgid "Jabber Broadcast"
|
||||
msgstr "Anuncio por Jabber"
|
||||
|
||||
#: allianceauth/services/modules/openfire/auth_hooks.py:91
|
||||
msgid "Fleet Broadcast Formatter"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/forms.py:7
|
||||
msgid "Message"
|
||||
msgstr "Mensaje"
|
||||
|
||||
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:17
|
||||
msgid "Broadcast Sent!!"
|
||||
msgstr "Ping Enviado"
|
||||
@@ -1276,6 +1381,76 @@ msgstr "Ping Enviado"
|
||||
msgid "Broadcast"
|
||||
msgstr "Anuncio"
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:35
|
||||
msgid "Activated jabber account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:44
|
||||
#: allianceauth/services/modules/openfire/views.py:57
|
||||
#: allianceauth/services/modules/openfire/views.py:78
|
||||
#: allianceauth/services/modules/openfire/views.py:151
|
||||
msgid "An error occurred while processing your jabber account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:70
|
||||
msgid "Reset jabber password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:119
|
||||
#, python-format
|
||||
msgid "Sent jabber broadcast to %s"
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/openfire/views.py:148
|
||||
msgid "Set jabber password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:34
|
||||
msgid "Activated forum account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:43
|
||||
#: allianceauth/services/modules/phpbb3/views.py:57
|
||||
#: allianceauth/services/modules/phpbb3/views.py:80
|
||||
#: allianceauth/services/modules/phpbb3/views.py:103
|
||||
msgid "An error occurred while processing your forum account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:54
|
||||
msgid "Deactivated forum account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:71
|
||||
msgid "Reset forum password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/phpbb3/views.py:100
|
||||
msgid "Set forum password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:34
|
||||
msgid "Activated SMF account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:43
|
||||
#: allianceauth/services/modules/smf/views.py:58
|
||||
#: allianceauth/services/modules/smf/views.py:80
|
||||
#: allianceauth/services/modules/smf/views.py:103
|
||||
msgid "An error occurred while processing your SMF account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:55
|
||||
msgid "Deactivated SMF account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:72
|
||||
msgid "Reset SMF password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/smf/views.py:100
|
||||
msgid "Set SMF password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/forms.py:14
|
||||
#, python-format
|
||||
msgid "Unable to locate user %s on server"
|
||||
@@ -1299,6 +1474,47 @@ msgstr "Unirse al Servidor"
|
||||
msgid "Continue"
|
||||
msgstr "Continuar"
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:34
|
||||
msgid "Activated TeamSpeak3 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:37
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:74
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:100
|
||||
msgid "An error occurred while processing your TeamSpeak3 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:71
|
||||
msgid "Deactivated TeamSpeak3 account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/teamspeak3/views.py:97
|
||||
msgid "Reset TeamSpeak3 permission key."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:30
|
||||
msgid "Activated XenForo account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:40
|
||||
#: allianceauth/services/modules/xenforo/views.py:52
|
||||
#: allianceauth/services/modules/xenforo/views.py:73
|
||||
#: allianceauth/services/modules/xenforo/views.py:94
|
||||
msgid "An error occurred while processing your XenForo account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:50
|
||||
msgid "Deactivated XenForo account."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:65
|
||||
msgid "Reset XenForo account password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/modules/xenforo/views.py:91
|
||||
msgid "Changed XenForo password."
|
||||
msgstr ""
|
||||
|
||||
#: allianceauth/services/templates/services/fleetformattertool.html:6
|
||||
msgid "Fleet Formatter Tool"
|
||||
msgstr "Formato de Ping"
|
||||
@@ -1682,12 +1898,6 @@ msgid_plural "%(tasks)s tasks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: allianceauth/templates/allianceauth/help.html:4
|
||||
#: allianceauth/templates/allianceauth/help.html:9
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:34
|
||||
msgid "Help"
|
||||
msgstr "Ayuda"
|
||||
|
||||
#: allianceauth/templates/allianceauth/night-toggle.html:3
|
||||
msgid "Night"
|
||||
msgstr "Noche"
|
||||
|
||||
BIN
allianceauth/locale/ko_KR/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/ko_KR/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2023
allianceauth/locale/ko_KR/LC_MESSAGES/django.po
Normal file
2023
allianceauth/locale/ko_KR/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/ru/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/ru/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2036
allianceauth/locale/ru/LC_MESSAGES/django.po
Normal file
2036
allianceauth/locale/ru/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -5,15 +5,16 @@
|
||||
#
|
||||
# Translators:
|
||||
# Joel Falknau <ozirascal@gmail.com>, 2020
|
||||
# Jesse . <sgeine@hotmail.com>, 2020
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-03-04 23:04+0000\n"
|
||||
"POT-Creation-Date: 2020-03-10 01:32+0000\n"
|
||||
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
|
||||
"Last-Translator: Joel Falknau <ozirascal@gmail.com>, 2020\n"
|
||||
"Last-Translator: Jesse . <sgeine@hotmail.com>, 2020\n"
|
||||
"Language-Team: Chinese Simplified (https://www.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -68,10 +69,8 @@ msgid "Change Main"
|
||||
msgstr "修改主要角色"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:97
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgstr "群组"
|
||||
msgid "Group Memberships"
|
||||
msgstr "用户组成员"
|
||||
|
||||
#: allianceauth/authentication/templates/authentication/dashboard.html:117
|
||||
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
|
||||
@@ -135,40 +134,40 @@ msgstr "您的IT团队"
|
||||
msgid "Submit"
|
||||
msgstr "提交"
|
||||
|
||||
#: allianceauth/authentication/views.py:39
|
||||
#: allianceauth/authentication/views.py:77
|
||||
#, python-format
|
||||
msgid "Changed main character to %(char)s"
|
||||
msgstr "修改主要角色为%(char)s"
|
||||
|
||||
#: allianceauth/authentication/views.py:48
|
||||
#: allianceauth/authentication/views.py:86
|
||||
#, python-format
|
||||
msgid "Added %(name)s to your account."
|
||||
msgstr "添加%(name)s到您的账户"
|
||||
|
||||
#: allianceauth/authentication/views.py:50
|
||||
#: allianceauth/authentication/views.py:88
|
||||
#, python-format
|
||||
msgid "Failed to add %(name)s to your account: they already have an account."
|
||||
msgstr "添加%(name)s到您的账户失败:他们已经在一个账户中了"
|
||||
|
||||
#: allianceauth/authentication/views.py:89
|
||||
#: allianceauth/authentication/views.py:127
|
||||
msgid "Unable to authenticate as the selected character."
|
||||
msgstr "无法作为选定的角色进行身份验证"
|
||||
|
||||
#: allianceauth/authentication/views.py:107
|
||||
#: allianceauth/authentication/views.py:145
|
||||
msgid "Registration token has expired."
|
||||
msgstr "注册令牌过期。"
|
||||
|
||||
#: allianceauth/authentication/views.py:159
|
||||
#: allianceauth/authentication/views.py:197
|
||||
msgid ""
|
||||
"Sent confirmation email. Please follow the link to confirm your email "
|
||||
"address."
|
||||
msgstr "已经发送了确认邮件。请按照链接确定您的电邮地址"
|
||||
|
||||
#: allianceauth/authentication/views.py:164
|
||||
#: allianceauth/authentication/views.py:202
|
||||
msgid "Confirmed your email address. Please login to continue."
|
||||
msgstr "已确认您的电邮地址。请登录以继续"
|
||||
|
||||
#: allianceauth/authentication/views.py:169
|
||||
#: allianceauth/authentication/views.py:207
|
||||
msgid "Registraion of new accounts it not allowed at this time."
|
||||
msgstr "现在不允许注册新账户。"
|
||||
|
||||
@@ -706,61 +705,61 @@ msgstr "用户组请求"
|
||||
msgid "Group Membership"
|
||||
msgstr "用户组成员"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:164
|
||||
#: allianceauth/groupmanagement/views.py:165
|
||||
#, python-format
|
||||
msgid "Removed user %(user)s from group %(group)s."
|
||||
msgstr "已将用户%(user)s从用户组%(group)s中移除"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:166
|
||||
#: allianceauth/groupmanagement/views.py:167
|
||||
msgid "User does not exist in that group"
|
||||
msgstr "那个用户组中不存在这个用户"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:169
|
||||
#: allianceauth/groupmanagement/views.py:170
|
||||
msgid "Group does not exist"
|
||||
msgstr "用户组不存在"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:196
|
||||
#: allianceauth/groupmanagement/views.py:197
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to %(group)s."
|
||||
msgstr "已接受用户%(mainchar)s加入%(group)s的申请"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:203
|
||||
#: allianceauth/groupmanagement/views.py:236
|
||||
#: allianceauth/groupmanagement/views.py:204
|
||||
#: allianceauth/groupmanagement/views.py:237
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occurred while processing the application from "
|
||||
"%(mainchar)s to %(group)s."
|
||||
msgstr "在处理用户%(mainchar)s加入%(group)s的申请的过程中出现了一个搞不定的错误"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:229
|
||||
#: allianceauth/groupmanagement/views.py:230
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to %(group)s."
|
||||
msgstr "%(mainchar)s加入%(group)s的申请已拒绝"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:265
|
||||
#: allianceauth/groupmanagement/views.py:266
|
||||
#, python-format
|
||||
msgid "Accepted application from %(mainchar)s to leave %(group)s."
|
||||
msgstr "%(mainchar)s加入%(group)s的申请已通过"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:271
|
||||
#: allianceauth/groupmanagement/views.py:305
|
||||
#: allianceauth/groupmanagement/views.py:272
|
||||
#: allianceauth/groupmanagement/views.py:306
|
||||
#, python-format
|
||||
msgid ""
|
||||
"An unhandled error occured while processing the application from "
|
||||
"%(mainchar)s to leave %(group)s."
|
||||
msgstr "在处理%(mainchar)s离开%(group)s的请求时发生了搞不定的错误"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:298
|
||||
#: allianceauth/groupmanagement/views.py:299
|
||||
#, python-format
|
||||
msgid "Rejected application from %(mainchar)s to leave %(group)s."
|
||||
msgstr "%(mainchar)s离开%(group)s的请求已被拒绝"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:345
|
||||
#: allianceauth/groupmanagement/views.py:346
|
||||
msgid "You cannot join that group"
|
||||
msgstr "你无法加入那个用户组"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:369
|
||||
#: allianceauth/groupmanagement/views.py:407
|
||||
#: allianceauth/groupmanagement/views.py:370
|
||||
#: allianceauth/groupmanagement/views.py:408
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
|
||||
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
|
||||
@@ -772,20 +771,20 @@ msgstr "你无法加入那个用户组"
|
||||
msgid "Pending"
|
||||
msgstr "待定"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:375
|
||||
#: allianceauth/groupmanagement/views.py:376
|
||||
#, python-format
|
||||
msgid "Applied to group %(group)s."
|
||||
msgstr "修改已经应用到%(group)s啦"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:386
|
||||
#: allianceauth/groupmanagement/views.py:387
|
||||
msgid "You cannot leave that group"
|
||||
msgstr "你无法离开那个用户组"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:391
|
||||
#: allianceauth/groupmanagement/views.py:392
|
||||
msgid "You are not a member of that group"
|
||||
msgstr "你不是那个用户组的成员"
|
||||
|
||||
#: allianceauth/groupmanagement/views.py:413
|
||||
#: allianceauth/groupmanagement/views.py:414
|
||||
#, python-format
|
||||
msgid "Applied to leave group %(group)s."
|
||||
msgstr "已经离开群组%(group)s"
|
||||
@@ -1176,6 +1175,11 @@ msgstr "操作类型"
|
||||
msgid "Users"
|
||||
msgstr "用户"
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
|
||||
#: allianceauth/templates/allianceauth/side-menu.html:15
|
||||
msgid "Groups"
|
||||
msgstr "群组"
|
||||
|
||||
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:43
|
||||
msgid "States"
|
||||
msgstr "声望"
|
||||
|
||||
@@ -11,6 +11,15 @@ app = Celery('{{ project_name }}')
|
||||
# Using a string here means the worker don't have to serialize
|
||||
# the configuration object to child processes.
|
||||
app.config_from_object('django.conf:settings')
|
||||
|
||||
# setup priorities ( 0 Highest, 9 Lowest )
|
||||
app.conf.broker_transport_options = {
|
||||
'priority_steps': list(range(10)), # setup que to have 10 steps
|
||||
'queue_order_strategy': 'priority', # setup que to use prio sorting
|
||||
}
|
||||
app.conf.task_default_priority = 5 # anything called with the task.delay() will be given normal priority (5)
|
||||
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
|
||||
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'settings': {}
|
||||
|
||||
@@ -84,6 +84,8 @@ LANGUAGES = (
|
||||
('de', ugettext('German')),
|
||||
('es', ugettext('Spanish')),
|
||||
('zh-hans', ugettext('Chinese Simplified')),
|
||||
('ru', ugettext('Russian')),
|
||||
('ko', ugettext('Korean')),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
@@ -218,6 +220,14 @@ LOGGING = {
|
||||
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
|
||||
'backupCount': 5, # edit this line to change number of log backups
|
||||
},
|
||||
'extension_file': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(BASE_DIR, 'log/extensions.log'),
|
||||
'formatter': 'verbose',
|
||||
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
|
||||
'backupCount': 5, # edit this line to change number of log backups
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG', # edit this line to change logging level to console
|
||||
'class': 'logging.StreamHandler',
|
||||
@@ -234,6 +244,10 @@ LOGGING = {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'extensions': {
|
||||
'handlers': ['extension_file', 'console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['log_file', 'console'],
|
||||
'level': 'ERROR',
|
||||
|
||||
@@ -4,3 +4,8 @@ from allianceauth import urls
|
||||
urlpatterns = [
|
||||
url(r'', include(urls)),
|
||||
]
|
||||
|
||||
handler500 = 'allianceauth.views.Generic500Redirect'
|
||||
handler404 = 'allianceauth.views.Generic404Redirect'
|
||||
handler403 = 'allianceauth.views.Generic403Redirect'
|
||||
handler400 = 'allianceauth.views.Generic400Redirect'
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
|
||||
from django.db import models, IntegrityError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import render, Http404, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .forms import ServicePasswordModelForm
|
||||
|
||||
@@ -68,7 +69,7 @@ class BaseCreatePasswordServiceAccountView(BaseServiceView, ServiceCredentialsVi
|
||||
try:
|
||||
svc_obj = self.model.objects.create(user=request.user)
|
||||
except IntegrityError:
|
||||
messages.error(request, "That service account already exists")
|
||||
messages.error(request, _("That service account already exists"))
|
||||
return redirect(self.index_redirect)
|
||||
|
||||
return render(request, self.template_name,
|
||||
@@ -100,7 +101,7 @@ class BaseSetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, Upda
|
||||
def post(self, request, *args, **kwargs):
|
||||
result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs)
|
||||
if self.get_form().is_valid():
|
||||
messages.success(request, "Successfully set your {} password".format(self.service_name))
|
||||
messages.success(request, _("Successfully set your {} password".format(self.service_name)))
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
|
||||
from .hooks import MenuItemHook
|
||||
from .hooks import ServicesHook
|
||||
|
||||
@@ -6,7 +8,7 @@ from .hooks import ServicesHook
|
||||
class Services(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Services',
|
||||
_('Services'),
|
||||
'fa fa-cogs fa-fw',
|
||||
'services:services', 100)
|
||||
|
||||
|
||||
@@ -9,6 +9,29 @@ from allianceauth.hooks import get_hooks
|
||||
from .models import NameFormatConfig
|
||||
|
||||
|
||||
def get_extension_logger(name):
|
||||
"""
|
||||
Takes the name of a plugin/extension and generates a child logger of the extensions logger
|
||||
to be used by the extension to log events to the extensions logger.
|
||||
|
||||
The logging level is decided by whether or not DEBUG is set to true in the project settings. If
|
||||
DEBUG is set to false, then the logging level is set to INFO.
|
||||
|
||||
:param: name: the name of the extension doing the logging
|
||||
:return: an extensions child logger
|
||||
"""
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger('extensions.' + name)
|
||||
logger.name = name
|
||||
logger.level = logging.INFO
|
||||
if settings.DEBUG:
|
||||
logger.level = logging.DEBUG
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
class ServicesHook:
|
||||
"""
|
||||
Abstract base class for creating a compatible services
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-09-05 21:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DiscordAuthToken',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('email', models.CharField(max_length=254, unique=True)),
|
||||
('token', models.CharField(max_length=254)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GroupCache',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('groups', models.TextField(default={})),
|
||||
('service', models.CharField(choices=[(b'discourse', b'discourse'), (b'discord', b'discord')], max_length=254, unique=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-10-16 01:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='discordauthtoken',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DiscordAuthToken',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-09-02 06:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0002_auto_20161016_0135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='GroupCache',
|
||||
),
|
||||
]
|
||||
18
allianceauth/services/migrations/0003_remove_broken_link.py
Normal file
18
allianceauth/services/migrations/0003_remove_broken_link.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.10 on 2020-03-21 13:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0002_nameformatter'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='nameformatconfig',
|
||||
name='format',
|
||||
field=models.CharField(help_text='For information on constructing name formats please see the official documentation, topic "Services Name Formats".', max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -4,14 +4,30 @@ from allianceauth.authentication.models import State
|
||||
|
||||
|
||||
class NameFormatConfig(models.Model):
|
||||
service_name = models.CharField(max_length=100, blank=False, null=False)
|
||||
default_to_username = models.BooleanField(default=True, help_text="If a user has no main_character, "
|
||||
"default to using their Auth username instead.")
|
||||
format = models.CharField(max_length=100, blank=False, null=False,
|
||||
help_text='For information on constructing name formats, please see the '
|
||||
'<a href="https://allianceauth.readthedocs.io/en/latest/features/nameformats">'
|
||||
'name format documentation</a>')
|
||||
states = models.ManyToManyField(State, help_text="States to apply this format to. You should only have one "
|
||||
"formatter for each state for each service.")
|
||||
service_name = models.CharField(max_length=100, blank=False)
|
||||
default_to_username = models.BooleanField(
|
||||
default=True,
|
||||
help_text=
|
||||
'If a user has no main_character, '
|
||||
'default to using their Auth username instead.'
|
||||
)
|
||||
format = models.CharField(
|
||||
max_length=100,
|
||||
blank=False,
|
||||
help_text=
|
||||
'For information on constructing name formats '
|
||||
'please see the official documentation, '
|
||||
'topic "Services Name Formats".'
|
||||
)
|
||||
states = models.ManyToManyField(
|
||||
State,
|
||||
help_text=
|
||||
"States to apply this format to. You should only have one "
|
||||
"formatter for each state for each service."
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s' % (
|
||||
self.service_name, ', '.join([str(x) for x in self.states.all()])
|
||||
)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class DiscordService(ServicesHook):
|
||||
|
||||
def sync_nickname(self, user):
|
||||
logger.debug('Syncing %s nickname for user %s' % (self.name, user))
|
||||
DiscordTasks.update_nickname.delay(user.pk)
|
||||
DiscordTasks.update_nickname.apply_async(args=[user.pk], countdown=5)
|
||||
|
||||
def update_all_groups(self):
|
||||
logger.debug('Update all %s groups called' % self.name)
|
||||
|
||||
@@ -27,7 +27,12 @@ class DiscordViewsTestCase(WebTest):
|
||||
self.login()
|
||||
manager.generate_oauth_redirect_url.return_value = '/example.com/oauth/'
|
||||
response = self.app.get('/discord/activate/', auto_follow=False)
|
||||
self.assertRedirects(response, expected_url='/example.com/oauth/', target_status_code=404)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
expected_url="/example.com/oauth/",
|
||||
target_status_code=404,
|
||||
fetch_redirect_response=False,
|
||||
)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.DiscordOAuthManager')
|
||||
def test_callback(self, manager):
|
||||
|
||||
@@ -5,8 +5,10 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.views import superuser_test
|
||||
|
||||
from .manager import DiscordOAuthManager
|
||||
from .tasks import DiscordTasks
|
||||
|
||||
@@ -21,10 +23,10 @@ def deactivate_discord(request):
|
||||
logger.debug("deactivate_discord called by user %s" % request.user)
|
||||
if DiscordTasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated discord for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated Discord account.')
|
||||
messages.success(request, _('Deactivated Discord account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate discord for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Discord account.')
|
||||
messages.error(request, _('An error occurred while processing your Discord account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -36,7 +38,7 @@ def reset_discord(request):
|
||||
logger.info("Successfully deleted discord user for user %s - forwarding to discord activation." % request.user)
|
||||
return redirect("discord:activate")
|
||||
logger.error("Unsuccessful attempt to reset discord for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Discord account.')
|
||||
messages.error(request, _('An error occurred while processing your Discord account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -57,10 +59,10 @@ def discord_callback(request):
|
||||
return redirect("services:services")
|
||||
if DiscordTasks.add_user(request.user, code):
|
||||
logger.info("Successfully activated Discord for user %s" % request.user)
|
||||
messages.success(request, 'Activated Discord account.')
|
||||
messages.success(request, _('Activated Discord account.'))
|
||||
else:
|
||||
logger.error("Failed to activate Discord for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your Discord account.')
|
||||
messages.error(request, _('An error occurred while processing your Discord account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .manager import DiscourseManager
|
||||
from .tasks import DiscourseTasks
|
||||
@@ -33,12 +34,12 @@ def discourse_sso(request):
|
||||
|
||||
# Check if user has access
|
||||
if not request.user.has_perm(ACCESS_PERM):
|
||||
messages.error(request, 'You are not authorized to access Discourse.')
|
||||
messages.error(request, _('You are not authorized to access Discourse.'))
|
||||
logger.warning('User %s attempted to access Discourse but does not have permission.' % request.user)
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
if not request.user.profile.main_character:
|
||||
messages.error(request, "You must have a main character set to access Discourse.")
|
||||
messages.error(request, _("You must have a main character set to access Discourse."))
|
||||
logger.warning('User %s attempted to access Discourse but does not have a main character.' % request.user)
|
||||
return redirect('authentication:characters')
|
||||
|
||||
@@ -48,7 +49,7 @@ def discourse_sso(request):
|
||||
signature = request.GET.get('sig')
|
||||
|
||||
if None in [payload, signature]:
|
||||
messages.error(request, 'No SSO payload or signature. Please contact support if this problem persists.')
|
||||
messages.error(request, _('No SSO payload or signature. Please contact support if this problem persists.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
# Validate the payload
|
||||
@@ -58,7 +59,7 @@ def discourse_sso(request):
|
||||
assert 'nonce' in decoded
|
||||
assert len(payload) > 0
|
||||
except AssertionError:
|
||||
messages.error(request, 'Invalid payload. Please contact support if this problem persists.')
|
||||
messages.error(request, _('Invalid payload. Please contact support if this problem persists.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
key = str(settings.DISCOURSE_SSO_SECRET).encode('utf-8')
|
||||
@@ -66,7 +67,7 @@ def discourse_sso(request):
|
||||
this_signature = h.hexdigest()
|
||||
|
||||
if this_signature != signature:
|
||||
messages.error(request, 'Invalid payload. Please contact support if this problem persists.')
|
||||
messages.error(request, _('Invalid payload. Please contact support if this problem persists.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
## Build the return payload
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
from .manager import Ips4Manager
|
||||
@@ -27,7 +28,7 @@ def activate_ips4(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with IPS4 credentials." % request.user)
|
||||
# update_ips4_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated IPS4 for user %s" % request.user)
|
||||
messages.success(request, 'Activated IPSuite4 account.')
|
||||
messages.success(request, _('Activated IPSuite4 account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
@@ -36,7 +37,7 @@ def activate_ips4(request):
|
||||
context={'credentials': credentials, 'service': 'IPSuite4'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate IPS4 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -49,7 +50,7 @@ def reset_ips4_password(request):
|
||||
# false we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset IPS4 password for user %s" % request.user)
|
||||
messages.success(request, 'Reset IPSuite4 password.')
|
||||
messages.success(request, _('Reset IPSuite4 password.'))
|
||||
credentials = {
|
||||
'username': request.user.ips4.username,
|
||||
'password': result,
|
||||
@@ -58,7 +59,7 @@ def reset_ips4_password(request):
|
||||
context={'credentials': credentials, 'service': 'IPSuite4'})
|
||||
|
||||
logger.error("Unsuccessful attempt to reset IPS4 password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -76,10 +77,10 @@ def set_ips4_password(request):
|
||||
result = Ips4Manager.update_custom_password(request.user.ips4.username, plain_password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set IPS4 password for user %s" % request.user)
|
||||
messages.success(request, 'Set IPSuite4 password.')
|
||||
messages.success(request, _('Set IPSuite4 password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom IPS4 password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect('services:services')
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
@@ -96,9 +97,9 @@ def deactivate_ips4(request):
|
||||
logger.debug("deactivate_ips4 called by user %s" % request.user)
|
||||
if Ips4Tasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated IPS4 for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated IPSuite4 account.')
|
||||
messages.success(request, _('Deactivated IPSuite4 account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate IPS4 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your IPSuite4 account.')
|
||||
messages.error(request, _('An error occurred while processing your IPSuite4 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ class MumbleService(ServicesHook):
|
||||
if MumbleTasks.has_account(user):
|
||||
MumbleTasks.update_groups.delay(user.pk)
|
||||
|
||||
def sync_nickname(self, user):
|
||||
logger.debug("Updating %s nickname for %s" % (self.name, user))
|
||||
if MumbleTasks.has_account(user):
|
||||
MumbleTasks.update_display_name.apply_async(args=[user.pk], countdown=5) # cooldown on this task to ensure DB clean when syncing
|
||||
|
||||
def validate_user(self, user):
|
||||
if MumbleTasks.has_account(user) and not self.service_active_for_user(user):
|
||||
self.delete_user(user, notify_user=True)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2.9 on 2020-03-16 07:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mumble', '0007_not_null_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mumbleuser',
|
||||
name='display_name',
|
||||
field=models.CharField(max_length=254, null=True),
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
from django.db import migrations, models
|
||||
from ..auth_hooks import MumbleService
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
|
||||
def fwd_func(apps, schema_editor):
|
||||
MumbleUser = apps.get_model("mumble", "MumbleUser")
|
||||
db_alias = schema_editor.connection.alias
|
||||
all_users = MumbleUser.objects.using(db_alias).all()
|
||||
for user in all_users:
|
||||
display_name = NameFormatter(MumbleService(), user.user).format_name()
|
||||
user.display_name = display_name
|
||||
user.save()
|
||||
|
||||
def rev_func(apps, schema_editor):
|
||||
MumbleUser = apps.get_model("mumble", "MumbleUser")
|
||||
db_alias = schema_editor.connection.alias
|
||||
all_users = MumbleUser.objects.using(db_alias).all()
|
||||
for user in all_users:
|
||||
user.display_name = None
|
||||
user.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mumble', '0008_mumbleuser_display_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fwd_func, rev_func),
|
||||
migrations.AlterField(
|
||||
model_name='mumbleuser',
|
||||
name='display_name',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -15,10 +15,14 @@ class MumbleManager(models.Manager):
|
||||
HASH_FN = 'bcrypt-sha256'
|
||||
|
||||
@staticmethod
|
||||
def get_username(user):
|
||||
def get_display_name(user):
|
||||
from .auth_hooks import MumbleService
|
||||
return NameFormatter(MumbleService(), user).format_name()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_username(user):
|
||||
return user.profile.main_character.character_name # main character as the user.username may be incorect
|
||||
|
||||
@staticmethod
|
||||
def sanitise_username(username):
|
||||
return username.replace(" ", "_")
|
||||
@@ -32,20 +36,26 @@ class MumbleManager(models.Manager):
|
||||
return bcrypt_sha256.encrypt(password.encode('utf-8'))
|
||||
|
||||
def create(self, user):
|
||||
username = self.get_username(user)
|
||||
logger.debug("Creating mumble user with username {}".format(username))
|
||||
username_clean = self.sanitise_username(username)
|
||||
password = self.generate_random_pass()
|
||||
pwhash = self.gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
|
||||
username_clean, pwhash[0:5]))
|
||||
logger.info("Creating mumble user {}".format(username_clean))
|
||||
try:
|
||||
username = self.get_username(user)
|
||||
logger.debug("Creating mumble user with username {}".format(username))
|
||||
username_clean = self.sanitise_username(username)
|
||||
display_name = self.get_display_name(user)
|
||||
password = self.generate_random_pass()
|
||||
pwhash = self.gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username {}, pwhash starts with {}".format(
|
||||
username_clean, pwhash[0:5]))
|
||||
logger.info("Creating mumble user {}".format(username_clean))
|
||||
|
||||
result = super(MumbleManager, self).create(user=user, username=username_clean,
|
||||
pwhash=pwhash, hashfn=self.HASH_FN)
|
||||
result.update_groups()
|
||||
result.credentials.update({'username': result.username, 'password': password})
|
||||
return result
|
||||
result = super(MumbleManager, self).create(user=user, username=username_clean,
|
||||
pwhash=pwhash, hashfn=self.HASH_FN,
|
||||
display_name=display_name)
|
||||
result.update_groups()
|
||||
result.credentials.update({'username': result.username, 'password': password})
|
||||
return result
|
||||
except AttributeError: # No Main or similar errors
|
||||
return False
|
||||
return False
|
||||
|
||||
def user_exists(self, username):
|
||||
return self.filter(username=username).exists()
|
||||
@@ -59,6 +69,8 @@ class MumbleUser(AbstractServiceModel):
|
||||
|
||||
objects = MumbleManager()
|
||||
|
||||
display_name = models.CharField(max_length=254, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
@@ -91,6 +103,12 @@ class MumbleUser(AbstractServiceModel):
|
||||
self.save()
|
||||
return True
|
||||
|
||||
def update_display_name(self):
|
||||
logger.info("Updating mumble user {} display name".format(self.user))
|
||||
self.display_name = MumbleManager.get_display_name(self.user)
|
||||
self.save()
|
||||
return True
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("access_mumble", u"Can access the Mumble service"),
|
||||
|
||||
@@ -45,9 +45,37 @@ class MumbleTasks:
|
||||
logger.debug("User %s does not have a mumble account, skipping" % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name="mumble.update_display_name", base=QueueOnce)
|
||||
def update_display_name(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating mumble groups for user %s" % user)
|
||||
if MumbleTasks.has_account(user):
|
||||
try:
|
||||
if not user.mumble.update_display_name():
|
||||
raise Exception("Display Name Sync failed")
|
||||
logger.debug("Updated user %s mumble display name." % user)
|
||||
return True
|
||||
except MumbleUser.DoesNotExist:
|
||||
logger.info("Mumble display name sync failed for {}, user does not have a mumble account".format(user))
|
||||
except:
|
||||
logger.exception("Mumble display name sync failed for %s, retrying in 10 mins" % user)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
else:
|
||||
logger.debug("User %s does not have a mumble account, skipping" % user)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(name="mumble.update_all_groups")
|
||||
def update_all_groups():
|
||||
logger.debug("Updating ALL mumble groups")
|
||||
for mumble_user in MumbleUser.objects.exclude(username__exact=''):
|
||||
MumbleTasks.update_groups.delay(mumble_user.user.pk)
|
||||
|
||||
@staticmethod
|
||||
@shared_task(name="mumble.update_all_display_names")
|
||||
def update_all_display_names():
|
||||
logger.debug("Updating ALL mumble display names")
|
||||
for mumble_user in MumbleUser.objects.exclude(username__exact=''):
|
||||
MumbleTasks.update_display_name.delay(mumble_user.user.pk)
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ class MumbleHooksTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.member = 'member_user'
|
||||
member = AuthUtils.create_member(self.member)
|
||||
AuthUtils.add_main_character(member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation',
|
||||
corp_ticker='TESTR')
|
||||
member = User.objects.get(pk=member.pk)
|
||||
MumbleUser.objects.create(user=member)
|
||||
self.none_user = 'none_user'
|
||||
none_user = AuthUtils.create_user(self.none_user)
|
||||
@@ -122,23 +125,45 @@ class MumbleViewsTestCase(TestCase):
|
||||
self.member.save()
|
||||
AuthUtils.add_main_character(self.member, 'auth_member', '12345', corp_id='111', corp_name='Test Corporation',
|
||||
corp_ticker='TESTR')
|
||||
self.member = User.objects.get(pk=self.member.pk)
|
||||
add_permissions()
|
||||
|
||||
def login(self):
|
||||
self.client.force_login(self.member)
|
||||
|
||||
def test_activate(self):
|
||||
def test_activate_update(self):
|
||||
self.login()
|
||||
expected_username = '[TESTR]auth_member'
|
||||
expected_username = 'auth_member'
|
||||
expected_displayname = '[TESTR]auth_member'
|
||||
response = self.client.get(urls.reverse('mumble:activate'), follow=False)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, expected_username)
|
||||
# create
|
||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||
self.assertEqual(mumble_user.username, expected_username)
|
||||
self.assertTrue(MumbleUser.objects.user_exists(expected_username))
|
||||
self.assertEqual(str(mumble_user), expected_username)
|
||||
self.assertEqual(mumble_user.display_name, expected_displayname)
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertIn('Guest', mumble_user.groups)
|
||||
self.assertIn('Member', mumble_user.groups)
|
||||
self.assertIn(',', mumble_user.groups)
|
||||
# test update
|
||||
self.member.profile.main_character.character_name = "auth_member_updated"
|
||||
self.member.profile.main_character.corporation_ticker = "TESTU"
|
||||
self.member.profile.main_character.save()
|
||||
mumble_user.update_display_name()
|
||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||
expected_displayname = '[TESTU]auth_member_updated'
|
||||
self.assertEqual(mumble_user.username, expected_username)
|
||||
self.assertTrue(MumbleUser.objects.user_exists(expected_username))
|
||||
self.assertEqual(str(mumble_user), expected_username)
|
||||
self.assertEqual(mumble_user.display_name, expected_displayname)
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertIn('Guest', mumble_user.groups)
|
||||
self.assertIn('Member', mumble_user.groups)
|
||||
self.assertIn(',', mumble_user.groups)
|
||||
|
||||
|
||||
def test_deactivate_post(self):
|
||||
self.login()
|
||||
@@ -171,7 +196,6 @@ class MumbleViewsTestCase(TestCase):
|
||||
self.assertTemplateUsed(response, 'services/service_credentials.html')
|
||||
self.assertContains(response, 'auth_member')
|
||||
|
||||
|
||||
class MumbleManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
from .models import MumbleManager
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import ServicesHook, MenuItemHook
|
||||
@@ -22,7 +23,7 @@ class OpenfireService(ServicesHook):
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return "Jabber"
|
||||
return _("Jabber")
|
||||
|
||||
def delete_user(self, user, notify_user=False):
|
||||
logger.debug('Deleting user %s %s account' % (user, self.name))
|
||||
@@ -74,7 +75,7 @@ def register_service():
|
||||
class JabberBroadcast(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Jabber Broadcast',
|
||||
_('Jabber Broadcast'),
|
||||
'fa fa-lock fa-fw fa-bullhorn',
|
||||
'openfire:broadcast')
|
||||
|
||||
@@ -87,7 +88,7 @@ class JabberBroadcast(MenuItemHook):
|
||||
class FleetBroadcastFormatter(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Fleet Broadcast Formatter',
|
||||
_('Fleet Broadcast Formatter'),
|
||||
'fa fa-lock fa-fw fa-space-shuttle',
|
||||
'services:fleet_format_tool')
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib.auth.models import Group
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .forms import JabberBroadcastForm
|
||||
from .manager import OpenfireManager, PingBotException
|
||||
from .models import OpenfireUser
|
||||
@@ -30,7 +32,7 @@ def activate_jabber(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with jabber credentials. Updating groups." % request.user)
|
||||
OpenfireTasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated jabber for user %s" % request.user)
|
||||
messages.success(request, 'Activated jabber account.')
|
||||
messages.success(request, _('Activated jabber account.'))
|
||||
credentials = {
|
||||
'username': info[0],
|
||||
'password': info[1],
|
||||
@@ -39,7 +41,7 @@ def activate_jabber(request):
|
||||
context={'credentials': credentials, 'service': 'Jabber'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate jabber for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -52,7 +54,7 @@ def deactivate_jabber(request):
|
||||
messages.success(request, 'Deactivated jabber account.')
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate jabber for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -65,7 +67,7 @@ def reset_jabber_password(request):
|
||||
# If our username is blank means we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset jabber password for user %s" % request.user)
|
||||
messages.success(request, 'Reset jabber password.')
|
||||
messages.success(request, _('Reset jabber password.'))
|
||||
credentials = {
|
||||
'username': request.user.openfire.username,
|
||||
'password': result,
|
||||
@@ -73,7 +75,7 @@ def reset_jabber_password(request):
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'Jabber'})
|
||||
logger.error("Unsuccessful attempt to reset jabber for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -114,7 +116,7 @@ def jabber_broadcast_view(request):
|
||||
|
||||
OpenfireManager.send_broadcast_message(group_to_send, message_to_send)
|
||||
|
||||
messages.success(request, 'Sent jabber broadcast to %s' % group_to_send)
|
||||
messages.success(request, _('Sent jabber broadcast to %s' % group_to_send))
|
||||
logger.info("Sent jabber broadcast on behalf of user %s" % request.user)
|
||||
except PingBotException as e:
|
||||
messages.error(request, e)
|
||||
@@ -143,10 +145,10 @@ def set_jabber_password(request):
|
||||
result = OpenfireManager.update_user_pass(request.user.openfire.username, password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set jabber password for user %s" % request.user)
|
||||
messages.success(request, 'Set jabber password.')
|
||||
messages.success(request, _('Set jabber password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom jabber password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your jabber account.')
|
||||
messages.error(request, _('An error occurred while processing your jabber account.'))
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .manager import Phpbb3Manager
|
||||
from .models import Phpbb3User
|
||||
from .tasks import Phpbb3Tasks
|
||||
@@ -29,7 +31,7 @@ def activate_forum(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with forum credentials. Updating groups." % request.user)
|
||||
Phpbb3Tasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated forum for user %s" % request.user)
|
||||
messages.success(request, 'Activated forum account.')
|
||||
messages.success(request, _('Activated forum account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
@@ -38,7 +40,7 @@ def activate_forum(request):
|
||||
context={'credentials': credentials, 'service': 'Forum'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -49,10 +51,10 @@ def deactivate_forum(request):
|
||||
# false we failed
|
||||
if Phpbb3Tasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated forum for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated forum account.')
|
||||
messages.success(request, _('Deactivated forum account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -66,7 +68,7 @@ def reset_forum_password(request):
|
||||
# false we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset forum password for user %s" % request.user)
|
||||
messages.success(request, 'Reset forum password.')
|
||||
messages.success(request, _('Reset forum password.'))
|
||||
credentials = {
|
||||
'username': request.user.phpbb3.username,
|
||||
'password': result,
|
||||
@@ -75,7 +77,7 @@ def reset_forum_password(request):
|
||||
context={'credentials': credentials, 'service': 'Forum'})
|
||||
|
||||
logger.error("Unsuccessful attempt to reset forum password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -95,10 +97,10 @@ def set_forum_password(request):
|
||||
password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set forum password for user %s" % request.user)
|
||||
messages.success(request, 'Set forum password.')
|
||||
messages.success(request, _('Set forum password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom forum password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your forum account.')
|
||||
messages.error(request, _('An error occurred while processing your forum account.'))
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .manager import SmfManager
|
||||
from .models import SmfUser
|
||||
from .tasks import SmfTasks
|
||||
@@ -29,7 +31,7 @@ def activate_smf(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with smf credentials. Updating groups." % request.user)
|
||||
SmfTasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated smf for user %s" % request.user)
|
||||
messages.success(request, 'Activated SMF account.')
|
||||
messages.success(request, _('Activated SMF account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
@@ -38,7 +40,7 @@ def activate_smf(request):
|
||||
context={'credentials': credentials, 'service': 'SMF'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -50,10 +52,10 @@ def deactivate_smf(request):
|
||||
# false we failed
|
||||
if result:
|
||||
logger.info("Successfully deactivated smf for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated SMF account.')
|
||||
messages.success(request, _('Deactivated SMF account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -67,7 +69,7 @@ def reset_smf_password(request):
|
||||
# false we failed
|
||||
if result != "":
|
||||
logger.info("Successfully reset smf password for user %s" % request.user)
|
||||
messages.success(request, 'Reset SMF password.')
|
||||
messages.success(request, _('Reset SMF password.'))
|
||||
credentials = {
|
||||
'username': request.user.smf.username,
|
||||
'password': result,
|
||||
@@ -75,7 +77,7 @@ def reset_smf_password(request):
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'SMF'})
|
||||
logger.error("Unsuccessful attempt to reset smf password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -95,10 +97,10 @@ def set_smf_password(request):
|
||||
password=password)
|
||||
if result != "":
|
||||
logger.info("Successfully set smf password for user %s" % request.user)
|
||||
messages.success(request, 'Set SMF password.')
|
||||
messages.success(request, _('Set SMF password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom smf password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your SMF account.')
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
return redirect("services:services")
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -4,6 +4,8 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .manager import Teamspeak3Manager
|
||||
from .forms import TeamspeakJoinForm
|
||||
from .models import Teamspeak3User
|
||||
@@ -29,10 +31,10 @@ def activate_teamspeak3(request):
|
||||
Teamspeak3User.objects.update_or_create(user=request.user, defaults={'uid': result[0], 'perm_key': result[1]})
|
||||
logger.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
|
||||
logger.info("Successfully activated TS3 for user %s" % request.user)
|
||||
messages.success(request, 'Activated TeamSpeak3 account.')
|
||||
messages.success(request, _('Activated TeamSpeak3 account.'))
|
||||
return redirect("teamspeak3:verify")
|
||||
logger.error("Unsuccessful attempt to activate TS3 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your TeamSpeak3 account.')
|
||||
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -66,10 +68,10 @@ def deactivate_teamspeak3(request):
|
||||
logger.debug("deactivate_teamspeak3 called by user %s" % request.user)
|
||||
if Teamspeak3Tasks.has_account(request.user) and Teamspeak3Tasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated TS3 for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated TeamSpeak3 account.')
|
||||
messages.success(request, _('Deactivated TeamSpeak3 account.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to deactivate TS3 for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your TeamSpeak3 account.')
|
||||
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -92,8 +94,8 @@ def reset_teamspeak3_perm(request):
|
||||
logger.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
|
||||
Teamspeak3Tasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully reset TS3 permission key for user %s" % request.user)
|
||||
messages.success(request, 'Reset TeamSpeak3 permission key.')
|
||||
messages.success(request, _('Reset TeamSpeak3 permission key.'))
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to reset TS3 permission key for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your TeamSpeak3 account.')
|
||||
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
@@ -3,8 +3,10 @@ import logging
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.services.forms import ServicePasswordForm
|
||||
|
||||
from .manager import XenForoManager
|
||||
from .models import XenforoUser
|
||||
from .tasks import XenforoTasks
|
||||
@@ -25,7 +27,7 @@ def activate_xenforo_forum(request):
|
||||
if result['response']['status_code'] == 200:
|
||||
XenforoUser.objects.update_or_create(user=request.user, defaults={'username': result['username']})
|
||||
logger.info("Updated user %s with XenForo credentials. Updating groups." % request.user)
|
||||
messages.success(request, 'Activated XenForo account.')
|
||||
messages.success(request, _('Activated XenForo account.'))
|
||||
credentials = {
|
||||
'username': result['username'],
|
||||
'password': result['password'],
|
||||
@@ -35,7 +37,7 @@ def activate_xenforo_forum(request):
|
||||
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate xenforo for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -45,9 +47,9 @@ def deactivate_xenforo_forum(request):
|
||||
logger.debug("deactivate_xenforo_forum called by user %s" % request.user)
|
||||
if XenforoTasks.delete_user(request.user):
|
||||
logger.info("Successfully deactivated XenForo for user %s" % request.user)
|
||||
messages.success(request, 'Deactivated XenForo account.')
|
||||
messages.success(request, _('Deactivated XenForo account.'))
|
||||
else:
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -60,7 +62,7 @@ def reset_xenforo_password(request):
|
||||
# Based on XenAPI's response codes
|
||||
if result['response']['status_code'] == 200:
|
||||
logger.info("Successfully reset XenForo password for user %s" % request.user)
|
||||
messages.success(request, 'Reset XenForo account password.')
|
||||
messages.success(request, _('Reset XenForo account password.'))
|
||||
credentials = {
|
||||
'username': request.user.xenforo.username,
|
||||
'password': result['password'],
|
||||
@@ -68,7 +70,7 @@ def reset_xenforo_password(request):
|
||||
return render(request, 'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'XenForo'})
|
||||
logger.error("Unsuccessful attempt to reset XenForo password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
@@ -86,10 +88,10 @@ def set_xenforo_password(request):
|
||||
result = XenForoManager.update_user_password(request.user.xenforo.username, password)
|
||||
if result['response']['status_code'] == 200:
|
||||
logger.info("Successfully reset XenForo password for user %s" % request.user)
|
||||
messages.success(request, 'Changed XenForo password.')
|
||||
messages.success(request, _('Changed XenForo password.'))
|
||||
else:
|
||||
logger.error("Failed to install custom XenForo password for user %s" % request.user)
|
||||
messages.error(request, 'An error occurred while processing your XenForo account.')
|
||||
messages.error(request, _('An error occurred while processing your XenForo account.'))
|
||||
return redirect('services:services')
|
||||
else:
|
||||
logger.debug("Request is not type POST - providing empty form.")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.db.models.signals import pre_delete
|
||||
@@ -11,6 +12,7 @@ from .tasks import disable_user
|
||||
|
||||
from allianceauth.authentication.models import State, UserProfile
|
||||
from allianceauth.authentication.signals import state_changed
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -157,14 +159,45 @@ def disable_services_on_inactive(sender, instance, *args, **kwargs):
|
||||
|
||||
|
||||
@receiver(pre_save, sender=UserProfile)
|
||||
def disable_services_on_no_main(sender, instance, *args, **kwargs):
|
||||
if not instance.pk:
|
||||
def process_main_character_change(sender, instance, *args, **kwargs):
|
||||
|
||||
if not instance.pk: # ignore
|
||||
# new model being created
|
||||
return
|
||||
try:
|
||||
old_instance = UserProfile.objects.get(pk=instance.pk)
|
||||
if old_instance.main_character and not instance.main_character:
|
||||
if old_instance.main_character and not instance.main_character: # lost main char disable services
|
||||
logger.info("Disabling services due to loss of main character for user {0}".format(instance.user))
|
||||
disable_user(instance.user)
|
||||
elif old_instance.main_character is not instance.main_character: # swapping/changing main character
|
||||
logger.info("Updating Names due to change of main character for user {0}".format(instance.user))
|
||||
for svc in ServicesHook.get_services():
|
||||
try:
|
||||
svc.validate_user(instance.user)
|
||||
svc.sync_nickname(instance.user)
|
||||
except:
|
||||
logger.exception('Exception running sync_nickname for services module %s on user %s' % (svc, instance))
|
||||
|
||||
except UserProfile.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
@receiver(pre_save, sender=EveCharacter)
|
||||
def process_main_character_update(sender, instance, *args, **kwargs):
|
||||
try:
|
||||
if instance.userprofile:
|
||||
old_instance = EveCharacter.objects.get(pk=instance.pk)
|
||||
if not instance.character_name == old_instance.character_name or \
|
||||
not instance.corporation_name == old_instance.corporation_name or \
|
||||
not instance.alliance_name == old_instance.alliance_name:
|
||||
logger.info("syncing service nickname for user {0}".format(instance.userprofile.user))
|
||||
|
||||
for svc in ServicesHook.get_services():
|
||||
try:
|
||||
svc.validate_user(instance.userprofile.user)
|
||||
svc.sync_nickname(instance.userprofile.user)
|
||||
except:
|
||||
logger.exception('Exception running sync_nickname for services module %s on user %s' % (svc, instance))
|
||||
|
||||
except ObjectDoesNotExist: # not a main char ignore
|
||||
pass
|
||||
|
||||
17
allianceauth/services/tests/test_models.py
Normal file
17
allianceauth/services/tests/test_models.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.test import TestCase
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..models import NameFormatConfig
|
||||
|
||||
|
||||
class TestNameFormatConfig(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
obj = NameFormatConfig.objects.create(
|
||||
service_name='mumble',
|
||||
format='{{character_name}}'
|
||||
)
|
||||
obj.states.add(AuthUtils.get_member_state())
|
||||
obj.states.add(AuthUtils.get_guest_state())
|
||||
expected = 'mumble: Member, Guest'
|
||||
self.assertEqual(str(obj), expected)
|
||||
@@ -1,21 +1,17 @@
|
||||
from allianceauth import NAME
|
||||
from esi.clients import esi_client_factory
|
||||
import requests
|
||||
import logging
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
from allianceauth import NAME
|
||||
from allianceauth.eveonline.providers import provider
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||
"""
|
||||
Swagger Operations:
|
||||
get_killmails_killmail_id_killmail_hash
|
||||
"""
|
||||
|
||||
|
||||
class SRPManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_kill_id(killboard_link):
|
||||
num_set = '0123456789'
|
||||
@@ -34,18 +30,23 @@ class SRPManager:
|
||||
if result:
|
||||
killmail_id = result['killmail_id']
|
||||
killmail_hash = result['zkb']['hash']
|
||||
c = esi_client_factory(spec_file=SWAGGER_SPEC_PATH)
|
||||
km = c.Killmails.get_killmails_killmail_id_killmail_hash(killmail_id=killmail_id,
|
||||
killmail_hash=killmail_hash).result()
|
||||
c = provider.client
|
||||
km = c.Killmails.get_killmails_killmail_id_killmail_hash(
|
||||
killmail_id=killmail_id,
|
||||
killmail_hash=killmail_hash
|
||||
).result()
|
||||
else:
|
||||
raise ValueError("Invalid Kill ID")
|
||||
if km:
|
||||
ship_type = km['victim']['ship_type_id']
|
||||
logger.debug("Ship type for kill ID %s is %s" % (kill_id, ship_type))
|
||||
logger.debug(
|
||||
"Ship type for kill ID %s is %s" % (kill_id, ship_type)
|
||||
)
|
||||
ship_value = result['zkb']['totalValue']
|
||||
logger.debug("Total loss value for kill id %s is %s" % (kill_id, ship_value))
|
||||
logger.debug(
|
||||
"Total loss value for kill id %s is %s" % (kill_id, ship_value)
|
||||
)
|
||||
victim_id = km['victim']['character_id']
|
||||
return ship_type, ship_value, victim_id
|
||||
else:
|
||||
raise ValueError("Invalid Kill ID or Hash.")
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
# Create your tests here.
|
||||
0
allianceauth/srp/tests/__init__.py
Executable file
0
allianceauth/srp/tests/__init__.py
Executable file
72
allianceauth/srp/tests/test_managers.py
Executable file
72
allianceauth/srp/tests/test_managers.py
Executable file
@@ -0,0 +1,72 @@
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ..managers import SRPManager
|
||||
|
||||
MODULE_PATH = 'allianceauth.srp.managers'
|
||||
|
||||
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(
|
||||
inspect.currentframe()
|
||||
)))
|
||||
|
||||
def load_data(filename):
|
||||
"""loads given JSON file from `testdata` sub folder and returns content"""
|
||||
with open(
|
||||
currentdir + '/testdata/%s.json' % filename, 'r', encoding='utf-8'
|
||||
) as f:
|
||||
data = json.load(f)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class TestSrpManager(TestCase):
|
||||
|
||||
def test_can_extract_kill_id(self):
|
||||
link = 'https://zkillboard.com/kill/81973979/'
|
||||
expected = 81973979
|
||||
self.assertEqual(int(SRPManager.get_kill_id(link)), expected)
|
||||
|
||||
@patch(MODULE_PATH + '.provider')
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
def test_can_get_kill_data(self, mock_get, mock_provider):
|
||||
mock_get.return_value.json.return_value = load_data(
|
||||
'zkillboard_killmail_api_81973979'
|
||||
)
|
||||
mock_provider.client.Killmails.\
|
||||
get_killmails_killmail_id_killmail_hash.return_value.\
|
||||
result.return_value = load_data(
|
||||
'get_killmails_killmail_id_killmail_hash_81973979'
|
||||
)
|
||||
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
self.assertEqual(ship_type, 19720)
|
||||
self.assertEqual(ship_value, 3177859026.86)
|
||||
self.assertEqual(victim_id, 93330670)
|
||||
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
def test_invalid_id_for_zkb_raises_exception(self, mock_get):
|
||||
mock_get.return_value.json.return_value = ['']
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
|
||||
@patch(MODULE_PATH + '.provider')
|
||||
@patch(MODULE_PATH + '.requests.get')
|
||||
def test_invalid_id_for_esi_raises_exception(
|
||||
self, mock_get, mock_provider
|
||||
):
|
||||
mock_get.return_value.json.return_value = load_data(
|
||||
'zkillboard_killmail_api_81973979'
|
||||
)
|
||||
mock_provider.client.Killmails.\
|
||||
get_killmails_killmail_id_killmail_hash.return_value.\
|
||||
result.return_value = None
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
ship_type, ship_value, victim_id = SRPManager.get_kill_data(81973979)
|
||||
|
||||
|
||||
953
allianceauth/srp/tests/testdata/get_killmails_killmail_id_killmail_hash_81973979.json
vendored
Normal file
953
allianceauth/srp/tests/testdata/get_killmails_killmail_id_killmail_hash_81973979.json
vendored
Normal file
@@ -0,0 +1,953 @@
|
||||
{
|
||||
"attackers": [
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 92606407,
|
||||
"corporation_id": 98343297,
|
||||
"damage_done": 65236,
|
||||
"final_blow": false,
|
||||
"security_status": -6.4,
|
||||
"ship_type_id": 47271,
|
||||
"weapon_type_id": 47271
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95104060,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 56425,
|
||||
"final_blow": true,
|
||||
"security_status": -1.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"alliance_id": 1220922756,
|
||||
"character_id": 92793488,
|
||||
"corporation_id": 679468421,
|
||||
"damage_done": 55225,
|
||||
"final_blow": false,
|
||||
"security_status": -3.4,
|
||||
"ship_type_id": 47271,
|
||||
"weapon_type_id": 47271
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90376343,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 51941,
|
||||
"final_blow": false,
|
||||
"security_status": 0.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 676848606,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 45906,
|
||||
"final_blow": false,
|
||||
"security_status": -1.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96692394,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 44900,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96624133,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 44146,
|
||||
"final_blow": false,
|
||||
"security_status": -9.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"character_id": 95050100,
|
||||
"corporation_id": 98497860,
|
||||
"damage_done": 41517,
|
||||
"final_blow": false,
|
||||
"security_status": -3,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 458944878,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 39888,
|
||||
"final_blow": false,
|
||||
"security_status": -6.5,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96029663,
|
||||
"corporation_id": 98433294,
|
||||
"damage_done": 39406,
|
||||
"final_blow": false,
|
||||
"security_status": -6.7,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90626300,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 37808,
|
||||
"final_blow": false,
|
||||
"security_status": -0.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90740848,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 36342,
|
||||
"final_blow": false,
|
||||
"security_status": -1.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 1105550086,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 35971,
|
||||
"final_blow": false,
|
||||
"security_status": -2.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99003581,
|
||||
"character_id": 94727582,
|
||||
"corporation_id": 98514029,
|
||||
"damage_done": 33501,
|
||||
"final_blow": false,
|
||||
"security_status": 3.2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 31894
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90368224,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 32116,
|
||||
"final_blow": false,
|
||||
"security_status": -2.4,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2929
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90001595,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 31387,
|
||||
"final_blow": false,
|
||||
"security_status": -0.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 2456
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95278082,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 31250,
|
||||
"final_blow": false,
|
||||
"security_status": 1.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 91971344,
|
||||
"corporation_id": 98217414,
|
||||
"damage_done": 31247,
|
||||
"final_blow": false,
|
||||
"security_status": -1,
|
||||
"ship_type_id": 29986,
|
||||
"weapon_type_id": 29986
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2113448089,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 30174,
|
||||
"final_blow": false,
|
||||
"security_status": -0.4,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115912819,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 29242,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115885290,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 28009,
|
||||
"final_blow": false,
|
||||
"security_status": -2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95746094,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 27565,
|
||||
"final_blow": false,
|
||||
"security_status": -7.8,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99003581,
|
||||
"character_id": 90345487,
|
||||
"corporation_id": 98514029,
|
||||
"damage_done": 26016,
|
||||
"final_blow": false,
|
||||
"security_status": 0.5,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115874625,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 25679,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115880975,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 23320,
|
||||
"final_blow": false,
|
||||
"security_status": -3.5,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96667534,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 21699,
|
||||
"final_blow": false,
|
||||
"security_status": -0.6,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115866658,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 20506,
|
||||
"final_blow": false,
|
||||
"security_status": -1.3,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 97105982,
|
||||
"corporation_id": 98217414,
|
||||
"damage_done": 19400,
|
||||
"final_blow": false,
|
||||
"security_status": 0,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99008704,
|
||||
"character_id": 96110151,
|
||||
"corporation_id": 98614116,
|
||||
"damage_done": 17547,
|
||||
"final_blow": false,
|
||||
"security_status": -5.5,
|
||||
"ship_type_id": 17740,
|
||||
"weapon_type_id": 17740
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 90526637,
|
||||
"corporation_id": 98343297,
|
||||
"damage_done": 16791,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 33157,
|
||||
"weapon_type_id": 21640
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2112972140,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 16749,
|
||||
"final_blow": false,
|
||||
"security_status": -1.2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115879470,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 14402,
|
||||
"final_blow": false,
|
||||
"security_status": -3.9,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95698217,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 11546,
|
||||
"final_blow": false,
|
||||
"security_status": -5.2,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 353190170,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 10896,
|
||||
"final_blow": false,
|
||||
"security_status": -4.7,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 28215
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 91546798,
|
||||
"corporation_id": 98217414,
|
||||
"damage_done": 9872,
|
||||
"final_blow": false,
|
||||
"security_status": -0.7,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 91578428,
|
||||
"corporation_id": 302750157,
|
||||
"damage_done": 7699,
|
||||
"final_blow": false,
|
||||
"security_status": -8.7,
|
||||
"ship_type_id": 17920,
|
||||
"weapon_type_id": 2185
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 94105463,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 5265,
|
||||
"final_blow": false,
|
||||
"security_status": -3.3,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 2488
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95526304,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 3967,
|
||||
"final_blow": false,
|
||||
"security_status": -8.7,
|
||||
"ship_type_id": 22474,
|
||||
"weapon_type_id": 2488
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 90331727,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 2940,
|
||||
"final_blow": false,
|
||||
"security_status": -1.2,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 2185
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2115880459,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 2301,
|
||||
"final_blow": false,
|
||||
"security_status": 4.1,
|
||||
"ship_type_id": 17738,
|
||||
"weapon_type_id": 17738
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009547,
|
||||
"character_id": 1832436128,
|
||||
"corporation_id": 98618666,
|
||||
"damage_done": 937,
|
||||
"final_blow": false,
|
||||
"security_status": -8.1,
|
||||
"ship_type_id": 17740,
|
||||
"weapon_type_id": 3186
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009547,
|
||||
"character_id": 96632877,
|
||||
"corporation_id": 98618666,
|
||||
"damage_done": 430,
|
||||
"final_blow": false,
|
||||
"security_status": -5.4,
|
||||
"ship_type_id": 17740,
|
||||
"weapon_type_id": 3186
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96146444,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 126,
|
||||
"final_blow": false,
|
||||
"security_status": -1.2,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 49713
|
||||
},
|
||||
{
|
||||
"character_id": 2116393370,
|
||||
"corporation_id": 98593091,
|
||||
"damage_done": 111,
|
||||
"final_blow": false,
|
||||
"security_status": 0,
|
||||
"ship_type_id": 602,
|
||||
"weapon_type_id": 27321
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 93745147,
|
||||
"corporation_id": 98418839,
|
||||
"damage_done": 6,
|
||||
"final_blow": false,
|
||||
"security_status": -1.6,
|
||||
"ship_type_id": 12021,
|
||||
"weapon_type_id": 2873
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95610468,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 4,
|
||||
"final_blow": false,
|
||||
"security_status": -2.2,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 484
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009221,
|
||||
"character_id": 92304254,
|
||||
"corporation_id": 98343297,
|
||||
"damage_done": 1,
|
||||
"final_blow": false,
|
||||
"security_status": -9.3,
|
||||
"ship_type_id": 22474,
|
||||
"weapon_type_id": 22474
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 96624034,
|
||||
"corporation_id": 98493618,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 37611
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95388762,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": 0.4,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 3001
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 1290463210,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -1.9,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 23707
|
||||
},
|
||||
{
|
||||
"alliance_id": 99009547,
|
||||
"character_id": 95748579,
|
||||
"corporation_id": 98618666,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -7.9,
|
||||
"ship_type_id": 643,
|
||||
"weapon_type_id": 16497
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2114899882,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -1.7,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 484
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 95624225,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -9.2,
|
||||
"ship_type_id": 12017,
|
||||
"weapon_type_id": 37608
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 93452185,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 22474,
|
||||
"weapon_type_id": 7537
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2114109824,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -2.1,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 484
|
||||
},
|
||||
{
|
||||
"alliance_id": 99006941,
|
||||
"character_id": 2113100583,
|
||||
"corporation_id": 98416134,
|
||||
"damage_done": 0,
|
||||
"final_blow": false,
|
||||
"security_status": -0.2,
|
||||
"ship_type_id": 49713,
|
||||
"weapon_type_id": 484
|
||||
}
|
||||
],
|
||||
"killmail_id": 81973979,
|
||||
"killmail_time": "2020-03-01T13:10:55Z",
|
||||
"solar_system_id": 30002537,
|
||||
"victim": {
|
||||
"alliance_id": 99009333,
|
||||
"character_id": 93330670,
|
||||
"corporation_id": 98267621,
|
||||
"damage_taken": 1127412,
|
||||
"items": [
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41332,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41332,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 27,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 33474,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 18,
|
||||
"item_type_id": 2048,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 28,
|
||||
"item_type_id": 4292,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 29001,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 22,
|
||||
"item_type_id": 41218,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 133,
|
||||
"item_type_id": 16275,
|
||||
"quantity_destroyed": 1125,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41330,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41330,
|
||||
"quantity_dropped": 4,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 94,
|
||||
"item_type_id": 31452,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20028,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20028,
|
||||
"quantity_dropped": 2,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 41489,
|
||||
"quantity_dropped": 48,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 13,
|
||||
"item_type_id": 18708,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 21,
|
||||
"item_type_id": 1978,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20026,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 29,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 21,
|
||||
"item_type_id": 29001,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 16275,
|
||||
"quantity_dropped": 1250,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 16299,
|
||||
"quantity_destroyed": 6,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 155,
|
||||
"item_type_id": 16299,
|
||||
"quantity_dropped": 2,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 41489,
|
||||
"quantity_dropped": 12,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 29,
|
||||
"item_type_id": 37298,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 17,
|
||||
"item_type_id": 40351,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 19,
|
||||
"item_type_id": 41491,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 12,
|
||||
"item_type_id": 2364,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20030,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 14,
|
||||
"item_type_id": 18708,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 28999,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 93,
|
||||
"item_type_id": 30993,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 11,
|
||||
"item_type_id": 2364,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 20,
|
||||
"item_type_id": 29001,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 15,
|
||||
"item_type_id": 40351,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 19,
|
||||
"item_type_id": 41489,
|
||||
"quantity_dropped": 4,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 21254,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 21254,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20032,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20032,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 16,
|
||||
"item_type_id": 40351,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20843,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 31,
|
||||
"item_type_id": 37298,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20845,
|
||||
"quantity_destroyed": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 92,
|
||||
"item_type_id": 30993,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 31,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 133,
|
||||
"item_type_id": 16274,
|
||||
"quantity_dropped": 141666,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 20,
|
||||
"item_type_id": 1978,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20847,
|
||||
"quantity_destroyed": 6,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 20847,
|
||||
"quantity_dropped": 3,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 5,
|
||||
"item_type_id": 21246,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 27,
|
||||
"item_type_id": 37298,
|
||||
"quantity_dropped": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 90,
|
||||
"item_type_id": 585,
|
||||
"items": [
|
||||
{
|
||||
"flag": 94,
|
||||
"item_type_id": 31159,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 20,
|
||||
"item_type_id": 3831,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 93,
|
||||
"item_type_id": 31159,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 22,
|
||||
"item_type_id": 9568,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 12,
|
||||
"item_type_id": 1405,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 92,
|
||||
"item_type_id": 31159,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 21,
|
||||
"item_type_id": 2553,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 11,
|
||||
"item_type_id": 1405,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
},
|
||||
{
|
||||
"flag": 19,
|
||||
"item_type_id": 5971,
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
}
|
||||
],
|
||||
"quantity_destroyed": 1,
|
||||
"singleton": 0
|
||||
}
|
||||
],
|
||||
"position": {
|
||||
"x": 55026869426.47358,
|
||||
"y": 7310382040.828209,
|
||||
"z": -163690355689.8978
|
||||
},
|
||||
"ship_type_id": 19720
|
||||
}
|
||||
}
|
||||
15
allianceauth/srp/tests/testdata/zkillboard_killmail_api_81973979.json
vendored
Normal file
15
allianceauth/srp/tests/testdata/zkillboard_killmail_api_81973979.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"killmail_id": 81973979,
|
||||
"zkb": {
|
||||
"locationID": 60004816,
|
||||
"hash": "e88a5fa7f342fa658ebe74a055b7679e28b05628",
|
||||
"fittedValue": 1532365686.21,
|
||||
"totalValue": 3177859026.86,
|
||||
"points": 1,
|
||||
"npc": false,
|
||||
"solo": false,
|
||||
"awox": false
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -8,7 +8,7 @@ from django.contrib.humanize.templatetags.humanize import intcomma
|
||||
from django.http import JsonResponse, Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models import Sum
|
||||
from allianceauth.authentication.decorators import permissions_required
|
||||
from allianceauth.eveonline.providers import provider
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
ReferrerUrl=https://github.com/ErikKalkoken/filterDropDown/blob/master/js/filterDropDown.min.js
|
||||
HostUrl=https://raw.githubusercontent.com/ErikKalkoken/filterDropDown/master/js/filterDropDown.min.js
|
||||
@@ -1,18 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% trans "Help" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
|
||||
<h1 class="page-header text-center">{% trans "Help" %}</h1>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<iframe class="embed-responsive-item" src="https://allianceauth.readthedocs.io/en/latest/features/"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -26,15 +26,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% menu_items %}
|
||||
|
||||
{% if user.is_superuser %}
|
||||
<li>
|
||||
<a class="{% navactive request 'authentication:help' %}"
|
||||
href="{% url 'authentication:help' %}">
|
||||
<i class="fa fa-question fa-fw"></i> {% trans "Help" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,13 @@
|
||||
{% else %}
|
||||
<li><a href="{% url 'authentication:login' %}">{% trans "Login" %}</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_superuser %}
|
||||
<li>
|
||||
<a class="navbar-brand" style="margin-left: -4px;" href="https://allianceauth.readthedocs.io/" target="_blank">
|
||||
<i class="fa fa-question-circle fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
from allianceauth.authentication.models import UserProfile, State, get_guest_state
|
||||
from allianceauth.authentication.signals import state_member_alliances_changed, state_member_characters_changed, \
|
||||
state_member_corporations_changed, state_saved
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.db.models.signals import m2m_changed, pre_save, post_save
|
||||
from django.test import TestCase
|
||||
from esi.models import Token
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
from allianceauth.services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, \
|
||||
m2m_changed_state_permissions
|
||||
from allianceauth.services.signals import m2m_changed_user_groups, disable_services_on_inactive
|
||||
from esi.models import Token
|
||||
|
||||
from allianceauth.authentication.models import (
|
||||
UserProfile, State, get_guest_state
|
||||
)
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from allianceauth.authentication.signals import (
|
||||
state_member_alliances_changed,
|
||||
state_member_characters_changed,
|
||||
state_member_corporations_changed,
|
||||
state_saved,
|
||||
reassess_on_profile_save,
|
||||
assign_state_on_active_change,
|
||||
check_state_on_character_update
|
||||
)
|
||||
from allianceauth.services.signals import (
|
||||
m2m_changed_group_permissions,
|
||||
m2m_changed_user_permissions,
|
||||
m2m_changed_state_permissions,
|
||||
m2m_changed_user_groups, disable_services_on_inactive
|
||||
)
|
||||
|
||||
|
||||
class AuthUtils:
|
||||
def __init__(self):
|
||||
pass
|
||||
"""Utilities for making it easier to create tests for Alliance Auth"""
|
||||
|
||||
@staticmethod
|
||||
def _create_user(username):
|
||||
def _create_user(username):
|
||||
return User.objects.create(username=username)
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, username, disconnect_signals=False):
|
||||
"""create a new user
|
||||
|
||||
username: Name of the user
|
||||
|
||||
disconnect_signals: whether to run process without signals
|
||||
"""
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
@@ -95,6 +113,11 @@ class AuthUtils:
|
||||
m2m_changed.disconnect(state_member_characters_changed, sender=State.member_characters.through)
|
||||
m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through)
|
||||
post_save.disconnect(state_saved, sender=State)
|
||||
post_save.disconnect(reassess_on_profile_save, sender=UserProfile)
|
||||
pre_save.disconnect(assign_state_on_active_change, sender=User)
|
||||
post_save.disconnect(
|
||||
check_state_on_character_update, sender=EveCharacter
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def connect_signals(cls):
|
||||
@@ -107,6 +130,9 @@ class AuthUtils:
|
||||
m2m_changed.connect(state_member_characters_changed, sender=State.member_characters.through)
|
||||
m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through)
|
||||
post_save.connect(state_saved, sender=State)
|
||||
post_save.connect(reassess_on_profile_save, sender=UserProfile)
|
||||
pre_save.connect(assign_state_on_active_change, sender=User)
|
||||
post_save.connect(check_state_on_character_update, sender=EveCharacter)
|
||||
|
||||
@classmethod
|
||||
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='',
|
||||
@@ -122,6 +148,40 @@ class AuthUtils:
|
||||
)
|
||||
UserProfile.objects.update_or_create(user=user, defaults={'main_character': char})
|
||||
|
||||
@classmethod
|
||||
def add_main_character_2(
|
||||
cls,
|
||||
user,
|
||||
name,
|
||||
character_id,
|
||||
corp_id='',
|
||||
corp_name='',
|
||||
corp_ticker='',
|
||||
alliance_id='',
|
||||
alliance_name='',
|
||||
disconnect_signals=False
|
||||
):
|
||||
"""new version that works in all cases"""
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
|
||||
char = EveCharacter.objects.create(
|
||||
character_id=character_id,
|
||||
character_name=name,
|
||||
corporation_id=corp_id,
|
||||
corporation_name=corp_name,
|
||||
corporation_ticker=corp_ticker,
|
||||
alliance_id=alliance_id,
|
||||
alliance_name=alliance_name,
|
||||
)
|
||||
user.profile.main_character = char
|
||||
user.profile.save()
|
||||
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
|
||||
return char
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
|
||||
if disconnect_signals:
|
||||
@@ -130,14 +190,67 @@ class AuthUtils:
|
||||
for group in groups:
|
||||
for perm in perms:
|
||||
group.permissions.add(perm)
|
||||
group = Group.objects.get(pk=group.pk) # reload permission cache
|
||||
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_state(cls, perms, states, disconnect_signals=True):
|
||||
return cls.add_permissions_to_groups(perms, states, disconnect_signals=disconnect_signals)
|
||||
return cls.add_permissions_to_groups(
|
||||
perms, states, disconnect_signals=disconnect_signals
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_user(cls, perms, user, disconnect_signals=True):
|
||||
"""add list of permissions to user
|
||||
|
||||
perms: list of Permission objects
|
||||
|
||||
user: user object
|
||||
|
||||
disconnect_signals: whether to run process without signals
|
||||
"""
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
|
||||
for perm in perms:
|
||||
user.user_permissions.add(perm)
|
||||
|
||||
user = User.objects.get(pk=user.pk) # reload permission cache
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
|
||||
@classmethod
|
||||
def add_permission_to_user_by_name(
|
||||
cls, perm, user, disconnect_signals=True
|
||||
):
|
||||
"""returns permission specified by qualified name
|
||||
|
||||
perm: Permission name as 'app_label.codename'
|
||||
|
||||
user: user object
|
||||
|
||||
disconnect_signals: whether to run process without signals
|
||||
"""
|
||||
p = cls.get_permission_by_name(perm)
|
||||
cls.add_permissions_to_user([p], user, disconnect_signals)
|
||||
|
||||
@staticmethod
|
||||
def get_permission_by_name(perm: str) -> Permission:
|
||||
"""returns permission specified by qualified name
|
||||
|
||||
perm: Permission name as 'app_label.codename'
|
||||
|
||||
Returns: Permission object or throws exception if not found
|
||||
"""
|
||||
perm_parts = perm.split('.')
|
||||
if len(perm_parts) != 2:
|
||||
raise ValueError('Invalid format for permission name')
|
||||
|
||||
return Permission.objects.get(
|
||||
content_type__app_label=perm_parts[0], codename=perm_parts[1]
|
||||
)
|
||||
|
||||
class BaseViewTestCase(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
60
allianceauth/tests/test_auth_utils.py
Normal file
60
allianceauth/tests/test_auth_utils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
)
|
||||
|
||||
from .auth_utils import AuthUtils
|
||||
|
||||
|
||||
class TestAuthUtils(TestCase):
|
||||
|
||||
def test_can_create_user(self):
|
||||
user = AuthUtils.create_user('Bruce Wayne')
|
||||
self.assertTrue(User.objects.filter(username='Bruce Wayne').exists())
|
||||
|
||||
def test_can_add_main_character_2(self):
|
||||
user = AuthUtils.create_user('Bruce Wayne')
|
||||
character = AuthUtils.add_main_character_2(
|
||||
user,
|
||||
name='Bruce Wayne',
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name='Wayne Technologies',
|
||||
corp_ticker='WYT',
|
||||
alliance_id=3001,
|
||||
alliance_name='Wayne Enterprises'
|
||||
)
|
||||
expected = character
|
||||
self.assertEqual(user.profile.main_character, expected)
|
||||
|
||||
def test_can_add_permission_to_group(self):
|
||||
group = Group.objects.create(name='Dummy Group')
|
||||
p = AuthUtils.get_permission_by_name('auth.group_management')
|
||||
AuthUtils.add_permissions_to_groups([p], [group])
|
||||
self.assertTrue(group.permissions.filter(pk=p.pk).exists())
|
||||
|
||||
def test_can_add_permission_to_user_by_name(self):
|
||||
user = AuthUtils.create_user('Bruce Wayne')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.timer_management', user
|
||||
)
|
||||
self.assertTrue(user.has_perm('auth.timer_management'))
|
||||
|
||||
|
||||
class TestGetPermissionByName(TestCase):
|
||||
|
||||
def test_can_get_permission_by_name(self):
|
||||
expected = Permission.objects.get(
|
||||
content_type__app_label='auth', codename='timer_management'
|
||||
)
|
||||
self.assertEqual(
|
||||
AuthUtils.get_permission_by_name('auth.timer_management'), expected
|
||||
)
|
||||
|
||||
def test_raises_exception_on_invalid_permission_format(self):
|
||||
with self.assertRaises(ValueError):
|
||||
AuthUtils.get_permission_by_name('timer_management')
|
||||
@@ -1,13 +1,15 @@
|
||||
from django.views.generic.base import View
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
class NightModeRedirectView(View):
|
||||
SESSION_VAR = 'NIGHT_MODE'
|
||||
SESSION_VAR = "NIGHT_MODE"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
request.session[self.SESSION_VAR] = not self.night_mode_state(request)
|
||||
return HttpResponseRedirect(request.GET.get('next', '/'))
|
||||
return HttpResponseRedirect(request.GET.get("next", "/"))
|
||||
|
||||
@classmethod
|
||||
def night_mode_state(cls, request):
|
||||
@@ -17,3 +19,39 @@ class NightModeRedirectView(View):
|
||||
# Session is middleware
|
||||
# Sometimes request wont have a session attribute
|
||||
return False
|
||||
|
||||
|
||||
def Generic500Redirect(request):
|
||||
messages.error(
|
||||
request,
|
||||
"Auth encountered an error processing your request, please try again. "
|
||||
"If the error persists, please contact the administrators. (500 Internal Server Error)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
def Generic404Redirect(request, exception):
|
||||
messages.error(
|
||||
request,
|
||||
"Page does not exist. If you believe this is in error please contact the administrators. "
|
||||
"(404 Page Not Found)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
def Generic403Redirect(request, exception):
|
||||
messages.error(
|
||||
request,
|
||||
"You do not have permission to access the requested page. "
|
||||
"If you believe this is in error please contact the administrators. (403 Permission Denied)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
def Generic400Redirect(request, exception):
|
||||
messages.error(
|
||||
request,
|
||||
"Auth encountered an error processing your request, please try again. "
|
||||
"If the error persists, please contact the administrators. (400 Bad Request)",
|
||||
)
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
BIN
docs/_static/images/development/aa_core.png
vendored
Normal file
BIN
docs/_static/images/development/aa_core.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
BIN
docs/_static/images/features/apps/corpstats.png
vendored
Normal file
BIN
docs/_static/images/features/apps/corpstats.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -1,4 +0,0 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
ReferrerUrl=https://i.imgur.com/6dXZ5BH.png
|
||||
HostUrl=https://i.imgur.com/6dXZ5BH.png
|
||||
@@ -1,4 +0,0 @@
|
||||
[ZoneTransfer]
|
||||
ZoneId=3
|
||||
ReferrerUrl=https://i.imgur.com/Er1g02v.png
|
||||
HostUrl=https://i.imgur.com/Er1g02v.png
|
||||
17
docs/conf.py
17
docs/conf.py
@@ -18,7 +18,10 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
import django
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings_all'
|
||||
django.setup()
|
||||
|
||||
# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
@@ -38,7 +41,9 @@ from recommonmark.transform import AutoStructify
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -96,7 +101,10 @@ html_theme = 'sphinx_rtd_theme'
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
html_theme_options = {
|
||||
'navigation_depth': 4,
|
||||
}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
@@ -148,6 +156,9 @@ man_pages = [
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# -- Options for autodoc -------------------------------------------------
|
||||
|
||||
add_module_names = False
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ It is possible to customize your **Alliance Auth** instance.
|
||||
|
||||
```eval_rst
|
||||
.. warning::
|
||||
Keep in mind that you may need to update some of your customizations manually after new release (e.g. when replacing AA templates).
|
||||
Keep in mind that you may need to update some of your customizations manually after new Auth releases (e.g. when replacing templates).
|
||||
```
|
||||
|
||||
## Site name
|
||||
@@ -9,4 +9,5 @@ This section describes how to extend **Alliance Auth** with custom apps and serv
|
||||
integrating-services
|
||||
menu-hooks
|
||||
url-hooks
|
||||
logging
|
||||
```
|
||||
|
||||
15
docs/development/custom/logging.md
Normal file
15
docs/development/custom/logging.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Logging from Custom Apps
|
||||
Alliance Auth provides a logger for use with custom apps to make everyone's life a little easier.
|
||||
|
||||
## Using the Extensions Logger
|
||||
AllianceAuth provides a helper function to get the logger for the current module to reduce the amount of
|
||||
code you need to write.
|
||||
|
||||
```python
|
||||
from allianceauth.services.hooks import get_extension_logger
|
||||
|
||||
logger = get_extension_logger(__name__)
|
||||
```
|
||||
|
||||
This works by creating a child logger of the extension logger which propagates all log entries
|
||||
to the parent (extensions) logger.
|
||||
463
docs/development/dev_setup/aa-dev-setup-wsl-vsc-v2.md
Normal file
463
docs/development/dev_setup/aa-dev-setup-wsl-vsc-v2.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# Development on Windows 10 with WSL and Visual Studio Code
|
||||
|
||||
This document describes step-by-step how to setup a complete development environment for Alliance Auth apps on Windows 10 with Windows Subsystem for Linux (WSL) and Visual Studio Code.
|
||||
|
||||
The main benefit of this setup is that it runs all services and code in the native Linux environment (WSL) and at the same time can be full controlled from within a comfortable Windows IDE (Visual Studio Code) including code debugging.
|
||||
|
||||
In addition all tools described in this guide are open source or free software.
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
This guide is meant for development purposes only and not for installing AA in a production environment. For production installation please see chapter **Installation**.
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
The development environment consists of the following components:
|
||||
|
||||
- Visual Studio Code with Remote WSL and Python extension
|
||||
- WSL with Ubunutu 18.04. LTS
|
||||
- Python 3.6 environment on WSL
|
||||
- MySQL server on WSL
|
||||
- Redis on WSL
|
||||
- Alliance Auth on WSL
|
||||
- Celery on WSL
|
||||
|
||||
We will use the build-in Django development webserver, so we don't need to setup a WSGI server or a web server.
|
||||
|
||||
## Requirement
|
||||
|
||||
The only requirement is a PC with Windows 10 and Internet connection in order to download the additional software components.
|
||||
|
||||
## Windows installation
|
||||
|
||||
### Windows Subsystem for Linux
|
||||
|
||||
- Install from here: [Microsoft docs](https://docs.microsoft.com/en-us/windows/wsl/install-win10)
|
||||
|
||||
- Choose Ubuntu 18.04. LTS
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
- Install from here: [VSC Download](https://code.visualstudio.com/Download)
|
||||
|
||||
- Open the app and install the following VSC extensions:
|
||||
|
||||
- Remote WSL
|
||||
|
||||
- Connect to WSL. This will automatically install the VSC server on the VSC server for WSL
|
||||
|
||||
- Once connected to WSL install the Python extension on the WSL side
|
||||
|
||||
## WSL Installation
|
||||
|
||||
Open a WSL bash and update all software packets:
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
```
|
||||
|
||||
### Install Tools
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential
|
||||
sudo apt-get install gettext
|
||||
```
|
||||
|
||||
### Install Python
|
||||
|
||||
For AA we want to develop with Python 3.6, because that provides the maximum compatibility with today's AA installations. This also happens to be the default Python 3 version for Ubuntu 18.04. at the point of this writing.
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
To check your system's Python 3 version you can enter: ``python3 --version``
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
Should your Ubuntu come with a newer version of Python we recommend to still setup your dev environment with the oldest Python 3 version supported by AA, e.g Python 3.6
|
||||
You an check out this `page <https://askubuntu.com/questions/682869/how-do-i-install-a-different-python-version-using-apt-get/1195153>`_ on how to install additional Python versions on Ubuntu.
|
||||
```
|
||||
|
||||
Use the following command to install Python 3 with all required libraries with the default version:
|
||||
|
||||
```bash
|
||||
sudo apt-get install python3 python3-dev python3-venv python3-setuptools python3-pip python-pip
|
||||
```
|
||||
|
||||
### Installing the DBMS
|
||||
|
||||
Install MySQL and required libraries with the following command:
|
||||
|
||||
```bash
|
||||
sudo apt-get install mysql-server mysql-client libmysqlclient-dev
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
We chose to use MySQL instead of MariaDB, because the standard version of MariaDB that comes with this Ubuntu distribution will not work with AA.
|
||||
```
|
||||
|
||||
We need to apply a permission fix to mysql or you will get a warning with every startup:
|
||||
|
||||
```bash
|
||||
sudo usermod -d /var/lib/mysql/ mysql
|
||||
```
|
||||
|
||||
Start the mysql server
|
||||
|
||||
```bash
|
||||
sudo service mysql start
|
||||
```
|
||||
|
||||
Create database and user for AA
|
||||
|
||||
```bash
|
||||
sudo mysql -u root
|
||||
```
|
||||
|
||||
```sql
|
||||
CREATE USER 'aa_dev'@'localhost' IDENTIFIED BY 'PASSWORD';
|
||||
CREATE DATABASE aa_dev CHARACTER SET utf8mb4;
|
||||
GRANT ALL PRIVILEGES ON aa_dev . * TO 'aa_dev'@'localhost';
|
||||
CREATE DATABASE test_aa_dev CHARACTER SET utf8mb4;
|
||||
GRANT ALL PRIVILEGES ON test_aa_dev . * TO 'aa_dev'@'localhost';
|
||||
exit;
|
||||
```
|
||||
|
||||
Add timezone info to mysql
|
||||
|
||||
```bash
|
||||
sudo mysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql -u root mysql
|
||||
```
|
||||
|
||||
### Install redis and other tools
|
||||
|
||||
```bash
|
||||
sudo apt-get install unzip git redis-server curl libssl-dev libbz2-dev libffi-dev
|
||||
```
|
||||
|
||||
Start redis
|
||||
|
||||
```bash
|
||||
sudo redis-server --daemonize yes
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
WSL does not have an init.d service, so it will not automatically start your services such as MySQL and Redis when you boot your Windows machine. For convenience we recommend putting the commands for starting these services in a bash script. Here is an example: ::
|
||||
|
||||
#/bin/bash
|
||||
# start services for AA dev
|
||||
sudo service mysql start
|
||||
sudo redis-server --daemonize yes
|
||||
|
||||
In addition it is possible to configure Windows to automatically start WSL services, but that procedure goes beyond the scopes of this guide.
|
||||
```
|
||||
|
||||
### Setup dev folder on WSL
|
||||
|
||||
Setup your folders on WSL bash for your dev project. Our approach will setup one AA project with one venv and multiple apps running under the same AA project, but each in their own folder and git.
|
||||
|
||||
A good location for setting up this folder structure is your home folder or a subfolder of your home:
|
||||
|
||||
```text
|
||||
~/aa-dev
|
||||
|- venv
|
||||
|- myauth
|
||||
|- my_app_1
|
||||
|- my_app_2
|
||||
|- ...
|
||||
```
|
||||
|
||||
Following this approach you can also setup additional AA projects, e.g. aa-dev-2, aa-dev-3 if needed.
|
||||
|
||||
Create the root folder aa-dev.
|
||||
|
||||
### setup virtual Python environment for aa-dev
|
||||
|
||||
Create the virtual environment. Run this in your aa-dev folder:
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
```
|
||||
|
||||
And activate your venv:
|
||||
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
### install Python packages
|
||||
|
||||
```bash
|
||||
pip install --upgrade pip
|
||||
pip install wheel
|
||||
```
|
||||
|
||||
## Alliance Auth installation
|
||||
|
||||
## Install and create AA instance
|
||||
|
||||
```bash
|
||||
pip install allianceauth
|
||||
```
|
||||
|
||||
Now we are ready to setup our AA instance. Make sure to run this command in your aa-dev folder:
|
||||
|
||||
```bash
|
||||
allianceauth start myauth
|
||||
```
|
||||
|
||||
Next we will setup our VSC project for aa-dev by starting it directly from the WSL bash:
|
||||
|
||||
```bash
|
||||
code .
|
||||
```
|
||||
|
||||
First you want to make sure exclude the venv folder from VSC as follows:
|
||||
Open settings and go to Files:Exclude
|
||||
Add pattern: `**/venv`
|
||||
|
||||
### Update settings
|
||||
|
||||
Open the settings file with VSC. Its under `myauth/myauth/settings/local.py`
|
||||
|
||||
Make sure to have the settings of your Eve Online app ready.
|
||||
|
||||
Turn on DEBUG mode to ensure your static files get served by Django:
|
||||
|
||||
```python
|
||||
DEBUG = True
|
||||
```
|
||||
|
||||
Update name, user and password of your DATABASE configuration.
|
||||
|
||||
```python
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'aa_dev',
|
||||
'USER': 'aa_dev',
|
||||
'PASSWORD': 'PASSWORD',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
'OPTIONS': {'charset': 'utf8mb4'},
|
||||
}
|
||||
```
|
||||
|
||||
For the Eve Online related setup you need to create a SSO app on the developer site:
|
||||
|
||||
- Create your Eve Online SSO App on the [Eve Online developer site](https://developers.eveonline.com/)
|
||||
- Add all ESI scopes
|
||||
- Set callback URL to: `http://localhost:8000/sso/callback`
|
||||
|
||||
Then update local.py with your settings:
|
||||
|
||||
```python
|
||||
ESI_SSO_CLIENT_ID = 'YOUR-ID'
|
||||
ESI_SSO_CLIENT_SECRET = 'YOUR_SECRET'
|
||||
ESI_SSO_CALLBACK_URL = 'http://localhost:8000/sso/callback'
|
||||
```
|
||||
|
||||
Disable email registration:
|
||||
|
||||
```python
|
||||
REGISTRATION_VERIFY_EMAIL = False
|
||||
```
|
||||
|
||||
### Migrations and superuser
|
||||
|
||||
Before we can start AA we need to run migrations:
|
||||
|
||||
```bash
|
||||
cd myauth
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
We also need to create a superuser for our AA installation:
|
||||
|
||||
```bash
|
||||
python /home/allianceserver/myauth/manage.py createsuperuser
|
||||
```
|
||||
|
||||
## Running Alliance Auth
|
||||
|
||||
## AA instance
|
||||
|
||||
We are now ready to run out AA instance with the following command:
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
Once running you can access your auth site on the browser under `http://localhost:8000`. Or the admin site under `http://localhost:8000/admin`
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
You can start your AA server directly from a terminal window in VSC or with a VSC debug config (see chapter about debugging for details).
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
**Debug vs. Non-Debug mode**
|
||||
Usually it is best to run your dev AA instance in debug mode, so you get all the detailed error messages that helps a lot for finding errors. But there might be cases where you want to test features that do not exist in debug mode (e.g. error pages) or just want to see how your app behaves in non-debug / production mode.
|
||||
|
||||
When you turn off debug mode you will see a problem though: Your pages will not render correctly. The reason is that Django will stop serving your static files in production mode and expect you to serve them from a real web server. Luckily, there is an option that forces Django to continue serving your static files directly even when not in debug mode. Just start your server with the following option: ``python manage.py runserver --insecure``
|
||||
```
|
||||
|
||||
### Celery
|
||||
|
||||
In addition you can start a celery worker instance for myauth. For development purposed it makes sense to only start one instance and add some additional logging.
|
||||
|
||||
This can be done from the command line with the following command in the myauth folder (where manage.py is located):
|
||||
|
||||
```bash
|
||||
celery -E -A myauth worker -l info -P solo
|
||||
```
|
||||
|
||||
Same as AA itself you can start Celery from any terminal session, from a terminal window within VSC or as a debug config in VSC (see chapter about debugging for details). For convenience we recommend starting Celery as debug config.
|
||||
|
||||
## Debugging setup
|
||||
|
||||
To be able to debug your code you need to add debugging configuration to VSC. At least one for AA and one for celery.
|
||||
|
||||
### Breakpoints
|
||||
|
||||
By default VSC will break on any uncaught exception. Since every error raised by your tests will cause an uncaught exception we recommend to deactivate this feature.
|
||||
|
||||
To deactivate open click on the debug icon to switch to the debug view. Then un-check "Uncaught Exceptions" on the breakpoints window.
|
||||
|
||||
### AA debug config
|
||||
|
||||
In VSC click on Debug / Add Configuration and choose "Django". Should Django not appear as option make sure to first open a Django file (e.g. the local.py settings) to help VSC detect that you are using Django.
|
||||
|
||||
The result should look something like this:
|
||||
|
||||
```python
|
||||
{
|
||||
"name": "Python: Django",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/myauth/manage.py",
|
||||
"args": [
|
||||
"runserver",
|
||||
"--noreload"
|
||||
],
|
||||
"django": true
|
||||
}
|
||||
```
|
||||
|
||||
### Debug celery
|
||||
|
||||
For celery we need another debug config, so that we can run it in parallel to our AA instance.
|
||||
|
||||
Here is an example debug config for Celery:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"name": "Python: Celery",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "celery",
|
||||
"cwd": "${workspaceFolder}/myauth",
|
||||
"console": "integratedTerminal",
|
||||
"args": [
|
||||
"-E",
|
||||
"-A",
|
||||
"myauth",
|
||||
"worker",
|
||||
"-l",
|
||||
"info",
|
||||
"-P",
|
||||
"solo",
|
||||
],
|
||||
"django": true
|
||||
},
|
||||
```
|
||||
|
||||
### Debug config for unit tests
|
||||
|
||||
Finally it makes sense to have a dedicated debug config for running unit tests. Here is an example config for running all tests of the app `example`.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"name": "Python: myauth unit tests",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/myauth/manage.py",
|
||||
"args": [
|
||||
"test",
|
||||
"-v 2",
|
||||
"--keepdb",
|
||||
"--debug-mode",
|
||||
"--failfast",
|
||||
"example",
|
||||
],
|
||||
|
||||
"django": true
|
||||
},
|
||||
```
|
||||
|
||||
You can also specify to run just a part of your test suite down to a test method. Just give the full path to the test you want to run, e.g. `example.test.test_models.TestDemoModel.test_this_method`
|
||||
|
||||
### Debugging normal python scripts
|
||||
|
||||
Finally you may also want to have a debug config to debug a non-Django Python script:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
```
|
||||
|
||||
## Additional tools
|
||||
|
||||
The following additional tools are very helpful when developing for AA.
|
||||
|
||||
### Code Spell Checker
|
||||
|
||||
Typos in your user facing comments can be quite embarrassing. This spell checker helps you avoid them.
|
||||
|
||||
Install from here: [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
|
||||
|
||||
### DBeaver
|
||||
|
||||
DBeaver is a free universal database tool and works with many different kinds of databases include MySQL. It can be installed on Windows 10 and will be able to help manage your MySQL databases running on WSL.
|
||||
|
||||
Install from here. [DBeaver](https://dbeaver.io/)
|
||||
|
||||
### django-extensions
|
||||
|
||||
[django-extensions](https://django-extensions.readthedocs.io/en/latest/) is a swiss army knife for django developers with adds a lot of very useful features to your Django site. Here are a few highlights:
|
||||
|
||||
- shell_plus - An enhanced version of the Django shell. It will auto-load all your models at startup so you don't have to import anything and can use them right away.
|
||||
- graph_models - Creates a dependency graph of Django models. Visualizing a model dependency structure can be very useful for trying to understand how an existing Django app works, or e.g. how all the AA models work together.
|
||||
- runserver_plus - The standard runserver stuff but with the Werkzeug debugger baked in. This is a must have for any serious debugging.
|
||||
|
||||
## Adding apps for development
|
||||
|
||||
The idea behind the particular folder structure of aa-dev is to have each and every app in its own folder and git repo. To integrate them with the AA instance they need to be installed once using the -e option that enabled editing of the package. And then added to the INSTALLED_APPS settings.
|
||||
|
||||
To demonstrate let's add the example plugin to our environment.
|
||||
|
||||
Open a WSL bash and navigate to the aa-dev folder. Make sure you have activate your virtual environment. (`source venv/bin/activate`)
|
||||
|
||||
Run these commands:
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.com/ErikKalkoken/allianceauth-example-plugin.git
|
||||
pip install -e allianceauth-example-plugin
|
||||
```
|
||||
|
||||
Add `'example'` to INSTALLED_APPS in your `local.py` settings.
|
||||
|
||||
Run migrations and restart your AA server, e.g.:
|
||||
|
||||
```bash
|
||||
cd myauth
|
||||
python manage.py migrate
|
||||
```
|
||||
10
docs/development/dev_setup/index.md
Normal file
10
docs/development/dev_setup/index.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Setup dev environment for AA
|
||||
|
||||
Here you find guides on how to setup your development environment for AA.
|
||||
|
||||
```eval_rst
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
aa-dev-setup-wsl-vsc-v2
|
||||
```
|
||||
@@ -8,4 +8,6 @@
|
||||
|
||||
custom/index
|
||||
aa_core/index
|
||||
dev_setup/index
|
||||
tech_docu/index
|
||||
```
|
||||
|
||||
39
docs/development/tech_docu/api/esi.rst
Normal file
39
docs/development/tech_docu/api/esi.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
======================
|
||||
django-esi
|
||||
======================
|
||||
|
||||
The django-esi package provides an interface for easy access to the ESI.
|
||||
|
||||
Location: ``esi``
|
||||
|
||||
This is an external package. Please also see `here <https://gitlab.com/allianceauth/django-esi>`_ for it's official documentation.
|
||||
|
||||
clients
|
||||
===========
|
||||
|
||||
.. automodule:: esi.clients
|
||||
:members: esi_client_factory
|
||||
:undoc-members:
|
||||
|
||||
decorators
|
||||
===========
|
||||
|
||||
.. automodule:: esi.decorators
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
errors
|
||||
===========
|
||||
|
||||
.. automodule:: esi.errors
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
models
|
||||
===========
|
||||
|
||||
.. automodule:: esi.models
|
||||
:members: Scope, Token
|
||||
:exclude-members: objects, provider
|
||||
:undoc-members:
|
||||
30
docs/development/tech_docu/api/evelinks.rst
Normal file
30
docs/development/tech_docu/api/evelinks.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
===============================
|
||||
evelinks
|
||||
===============================
|
||||
|
||||
This package generates profile URLs for eve entities on 3rd party websites like evewho and zKillboard.
|
||||
|
||||
Location: ``allianceauth.eveonline.evelinks``
|
||||
|
||||
dotlan
|
||||
===============
|
||||
|
||||
.. automodule:: allianceauth.eveonline.evelinks.dotlan
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
eveho
|
||||
==============
|
||||
|
||||
.. automodule:: allianceauth.eveonline.evelinks.evewho
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
zkillboard
|
||||
===================
|
||||
|
||||
.. automodule:: allianceauth.eveonline.evelinks.zkillboard
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
15
docs/development/tech_docu/api/eveonline.rst
Normal file
15
docs/development/tech_docu/api/eveonline.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
======================
|
||||
eveonline
|
||||
======================
|
||||
|
||||
The eveonline package provides models for commonly used Eve Online entities like characters, corporations and alliances. All models have the ability to be loaded from ESI.
|
||||
|
||||
Location: ``allianceauth.eveonline``
|
||||
|
||||
models
|
||||
======
|
||||
|
||||
.. automodule:: allianceauth.eveonline.models
|
||||
:members:
|
||||
:exclude-members: objects, provider
|
||||
:undoc-members:
|
||||
13
docs/development/tech_docu/api/index.md
Normal file
13
docs/development/tech_docu/api/index.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# API
|
||||
|
||||
To reduce redundancy and help speed up development we encourage developers to utilize the following packages when developing apps for Alliance Auth.
|
||||
|
||||
```eval_rst
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
esi
|
||||
evelinks
|
||||
eveonline
|
||||
testutils
|
||||
```
|
||||
14
docs/development/tech_docu/api/testutils.rst
Normal file
14
docs/development/tech_docu/api/testutils.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
=============================
|
||||
tests
|
||||
=============================
|
||||
|
||||
Here you find utility functions and classes, which can help speed up writing test cases for AA.
|
||||
|
||||
Location: ``allianceauth.tests.auth_utils``
|
||||
|
||||
auth_utils
|
||||
===========
|
||||
|
||||
.. automodule:: allianceauth.tests.auth_utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
204
docs/development/tech_docu/celery.md
Normal file
204
docs/development/tech_docu/celery.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# Celery FAQ
|
||||
|
||||
**Alliance Auth** uses Celery for asynchronous task management. This page aims to give developers some guidance on how to use Celery when developing apps for Alliance Auth.
|
||||
|
||||
For a complete documentation of Celery please refer to the [official Celery documentation](http://docs.celeryproject.org/en/latest/index.html).
|
||||
|
||||
## When should I use Celery in my app?
|
||||
|
||||
There are two main cases for using celery. Long duration of a process and recurrence of a process.
|
||||
|
||||
### Duration
|
||||
|
||||
Alliance Auth is an online web application and as such the user expects fast and immediate responses to any of his clicks or actions. Same as with any other good web site. Good response times are measures in ms and a user will perceive everything that takes longer than 1 sec as an interruption of his flow of thought (see also [Response Times: The 3 Important Limits](https://www.nngroup.com/articles/response-times-3-important-limits/)).
|
||||
|
||||
As a rule of thumb we therefore recommend to use celery tasks for every process that can take longer than 1 sec to complete (also think about how long your process might take with large amounts of data).
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
Another solution for dealing with long response time in particular when loading pages is to load parts of a page asynchronously, for example with AJAX.
|
||||
```
|
||||
|
||||
### Recurrence
|
||||
|
||||
Another case for using celery tasks is when you need recurring execution of tasks. For example you may want to update the list of characters in a corporation from ESI every hour.
|
||||
|
||||
These are called periodic tasks and Alliance Auth uses [celery beat](https://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html) to implement them.
|
||||
|
||||
## What is a celery task?
|
||||
|
||||
For the most part a celery task is a Python functions that is configured to be executed asynchronously and controlled by Celery. Celery tasks can be automatically retried, executed periodically, executed in work flows and much more. See the [celery docs](https://docs.celeryproject.org/en/latest/userguide/tasks.html) for a more detailed description.
|
||||
|
||||
## How should I use Celery in my app?
|
||||
|
||||
Please use the following approach to ensure your tasks are working properly with Alliance Auth:
|
||||
|
||||
- All tasks should be defined in a module of your app's package called `tasks.py`
|
||||
- Every task is a Python function with has the `@shared_task` decorator.
|
||||
- Task functions and the tasks module should be kept slim, just like views by mostly utilizing business logic defined in your models/managers.
|
||||
- Tasks should always have logging, so their function and potential errors can be monitored properly
|
||||
|
||||
Here is an example implementation of a task:
|
||||
|
||||
```Python
|
||||
import logging
|
||||
from celery import shared_task
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def example():
|
||||
logger.info('example task started')
|
||||
```
|
||||
|
||||
This task can then be started from any another Python module like so:
|
||||
|
||||
```Python
|
||||
from .tasks import example
|
||||
|
||||
example.delay()
|
||||
```
|
||||
|
||||
## How should I use celery tasks in the UI?
|
||||
|
||||
There is a well established pattern for integrating asynchronous processes in the UI, for example when the user asks your app to perform a longer running action:
|
||||
|
||||
1. Notify the user immediately (with a Django message) that the process for completing the action has been started and that he will receive a report once completed.
|
||||
|
||||
2. Start the celery task
|
||||
|
||||
3. Once the celery task is completed it should send a notification containing the result of the action to the user. It's important to send that notification also in case of errors.
|
||||
|
||||
## Can I use long running tasks?
|
||||
|
||||
Long running tasks are possible, but in general Celery works best with short running tasks. Therefore we strongly recommend to try and break down long running tasks into smaller tasks if possible.
|
||||
|
||||
If contextually possible try to break down your long running task in shorter tasks that can run in parallel.
|
||||
|
||||
However, many long running tasks consist of several smaller processes that need to run one after the other. For example you may have a loop where you perform the same action on hundreds of objects. In those cases you can define each of the smaller processes as it's own task and then link them together, so that they are run one after the other. That is called chaining in Celery and is the preferred approach for implementing long running processes.
|
||||
|
||||
Example implementation for a celery chain:
|
||||
|
||||
```Python
|
||||
import logging
|
||||
from celery import shared_task, chain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def example():
|
||||
logger.info('example task')
|
||||
|
||||
@shared_task
|
||||
def long_runner():
|
||||
logger.info('started long runner')
|
||||
my_tasks = list()
|
||||
for _ in range(10):
|
||||
task_signature = example.si()
|
||||
my_task.append(task_signature)
|
||||
|
||||
chain(my_tasks).delay()
|
||||
```
|
||||
|
||||
In this example we fist add 10 example tasks that need to run one after the other to a list. This can be done by creating a so called signature for a task. Those signature are a kind of wrapper for tasks and can be used in various ways to compose work flow for tasks.
|
||||
|
||||
The list of task signatures is then converted to a chain and started asynchronously.
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
In our example we use ``si()``, which is a shortcut for "immutable signatures" and prevents us from having to deal with result sharing between tasks.
|
||||
|
||||
For more information on signature and work flows see the official documentation on `Canvas <https://docs.celeryproject.org/en/latest/userguide/canvas.html>`_.
|
||||
|
||||
In this context please note that Alliance Auth currently only supports chaining, because all other variants require a so called results back, which Alliance Auth does not have.
|
||||
```
|
||||
|
||||
## How can I define periodic tasks for my app?
|
||||
|
||||
Periodic tasks are normal celery tasks which are added the scheduler for periodic execution. The convention for defining periodic tasks for an app is to define them in the local settings. So user will need to add those settings manually to his local settings during the installation process.
|
||||
|
||||
Example setting:
|
||||
|
||||
```Python
|
||||
CELERYBEAT_SCHEDULE['structures_update_all_structures'] = {
|
||||
'task': 'structures.tasks.update_all_structures',
|
||||
'schedule': crontab(minute='*/30'),
|
||||
}
|
||||
```
|
||||
|
||||
- `structures_update_all_structures` is the name of the scheduling entry. You can chose any name, but the convention is name of your app plus name of the task.
|
||||
|
||||
- `'task'`: Name of your task (full path)
|
||||
- `'schedule'`: Schedule definition (see Celery documentation on [Periodic Tasks](https://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html) for details)
|
||||
|
||||
## How can I use priorities for tasks?
|
||||
|
||||
In Alliance Auth we have defined task priorities from 0 - 9 as follows:
|
||||
|
||||
```eval_rst
|
||||
====== ========= ===========
|
||||
Number Priority Description
|
||||
====== ========= ===========
|
||||
0 Reserved Reserved for Auth and may not be used by apps
|
||||
1, 2 Highest Needs to run right now
|
||||
3, 4 High needs to run as soon as practical
|
||||
5 Normal default priority for most tasks
|
||||
6, 7 Low needs to run soonish, but is less urgent than most tasks
|
||||
8, 9 Lowest not urgent, can be run whenever there is time
|
||||
====== ========= ===========
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. warning::
|
||||
Please make sure to use task priorities with care and especially do not use higher priorities without a good reason. All apps including Alliance Auth share the same task queues, so using higher task priorities excessively can potentially prevent more important tasks (of other apps) from completing on time.
|
||||
|
||||
You also want to make sure to run use lower priorities if you have a large amount of tasks or long running tasks, which are not super urgent. (e.g. the regular update of all Eve characters from ESI runs with priority 7)
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
If no priority is specified all tasks will be started with the default priority, which is 5.
|
||||
```
|
||||
|
||||
To run a task with a different priority you need to specify it when starting it.
|
||||
|
||||
Example for starting a task with priority 3:
|
||||
|
||||
```Python
|
||||
example.apply_async(priority=3)
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
.. hint::
|
||||
For defining a priority to tasks you can not use the convenient shortcut ``delay()``, but instead need to start a task with ``apply_async()``, which also requires you to pass parameters to your task function differently. Please check out the `official docs <https://docs.celeryproject.org/en/stable/reference/celery.app.task.html#celery.app.task.Task.apply_async>`_ for details.
|
||||
```
|
||||
|
||||
## What special features should I be aware of?
|
||||
|
||||
Every Alliance Auth installation will come with a couple of special celery related features "out-of-the-box" that you can make use of in your apps.
|
||||
|
||||
### celery-once
|
||||
|
||||
Celery-once is a celery extension "that allows you to prevent multiple execution and queuing of celery tasks". What that means is that you can ensure that only one instance of a celery task runs at any given time. This can be useful for example if you do not want multiple instances of you task to talk to the same external service at the same time.
|
||||
|
||||
We use a custom backend for celery_once in Alliance Auth defined [here](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/services/tasks.py#L14)
|
||||
You can import it for use like so:
|
||||
|
||||
```Python
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
```
|
||||
|
||||
An example of AllianceAuth's use within the `@sharedtask` decorator, can be seen [here](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/services/modules/discord/tasks.py#L62) in the discord module
|
||||
You can use it like so:
|
||||
|
||||
```Python
|
||||
@shared_task(bind=True, name='your_modules.update_task', base=QueueOnce)
|
||||
```
|
||||
|
||||
Please see the [official documentation](hhttps://pypi.org/project/celery_once/) of celery-once for details.
|
||||
|
||||
### task priorities
|
||||
|
||||
Alliance Auth is using task priorities to enable priority based scheduling of task execution. Please see [How can I use priorities for tasks?](#how-can-i-use-priorities-for-tasks) for details.
|
||||
5
docs/development/tech_docu/core_models.md
Normal file
5
docs/development/tech_docu/core_models.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Core models
|
||||
|
||||
The following diagram shows the core models of AA and Django and their relationships:
|
||||
|
||||

|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user