mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aecc94bdb3 | ||
|
|
fcb7f2905a | ||
|
|
34c7169ca3 | ||
|
|
6e450061f4 | ||
|
|
fc3d7e9f43 | ||
|
|
514db4f9a2 | ||
|
|
23a8b65ce2 | ||
|
|
f8e6662bc8 | ||
|
|
89be2456fb | ||
|
|
bfd3451717 | ||
|
|
0b759d6a32 | ||
|
|
65e05084e6 | ||
|
|
f25a4ed386 | ||
|
|
b2a1d41829 | ||
|
|
2741a92d31 | ||
|
|
3570ce86d7 | ||
|
|
d809902d1e | ||
|
|
ec4232c00a | ||
|
|
dec793bfac | ||
|
|
fe3fe0527a | ||
|
|
a8855e86ed | ||
|
|
179d1c38e6 | ||
|
|
287da73a4f | ||
|
|
e9ed917888 | ||
|
|
70d842c971 | ||
|
|
bcda228e05 | ||
|
|
000dafc5e6 | ||
|
|
4ea824fe71 | ||
|
|
f72f539516 | ||
|
|
1b192a184f | ||
|
|
0edf896b4c | ||
|
|
7dec4deb70 | ||
|
|
d511221899 | ||
|
|
d2b7de5221 | ||
|
|
79c5be02e2 | ||
|
|
09df37438d | ||
|
|
8561e4c6fd | ||
|
|
976cb4d988 | ||
|
|
20f7d5103c | ||
|
|
d049ec2832 | ||
|
|
00fe2a527e | ||
|
|
f53ec3b43e | ||
|
|
4d0417f114 | ||
|
|
00903b64db | ||
|
|
a3038cad00 | ||
|
|
ef99f1afac | ||
|
|
cc00d4bd04 | ||
|
|
250f26ff6f | ||
|
|
62b786ca4a | ||
|
|
9cfb47e658 | ||
|
|
ccef27b637 | ||
|
|
8dee61fd39 | ||
|
|
ae64bd0e19 | ||
|
|
a75e93dbfc | ||
|
|
0aa66c5729 | ||
|
|
4c2434219d | ||
|
|
8c65fda33b | ||
|
|
14f2751bbb | ||
|
|
d37a543c39 | ||
|
|
4947e0c483 | ||
|
|
f87c630b86 | ||
|
|
73789ea734 | ||
|
|
5a16c9c495 | ||
|
|
9dd8357f67 | ||
|
|
623e77a268 | ||
|
|
73403b98df | ||
|
|
7aa1a2f336 | ||
|
|
08e42d2f56 | ||
|
|
69248fd7bb | ||
|
|
0af188c6ab | ||
|
|
8b6d32d0d1 | ||
|
|
3c11c25d69 | ||
|
|
12e6cc63e8 | ||
|
|
d429c8b59a | ||
|
|
ddd7a3551b | ||
|
|
49ede92e06 | ||
|
|
b813213328 | ||
|
|
14065b3ca9 | ||
|
|
41f9dc490a | ||
|
|
48d25ca73f | ||
|
|
e49e04034c | ||
|
|
c7860f8e5c | ||
|
|
adb982114a | ||
|
|
5b8983deac | ||
|
|
1730bc3b98 | ||
|
|
4374064d98 | ||
|
|
c1d7994045 | ||
|
|
7bda367cc8 | ||
|
|
3de7a2ccd2 | ||
|
|
9cc278df31 | ||
|
|
a0bab07e2f | ||
|
|
149bbd92ca | ||
|
|
1de3c989d7 | ||
|
|
2e547945e2 | ||
|
|
4d4a9a27af | ||
|
|
5b41d0995f | ||
|
|
ab98d72022 | ||
|
|
8a7cd3f74d | ||
|
|
35cb56d6e9 | ||
|
|
a7a2ffd16b | ||
|
|
dbeda324e0 | ||
|
|
bf1fe99d98 | ||
|
|
41429ec7c7 | ||
|
|
ee9ed13a66 | ||
|
|
490ce286ff | ||
|
|
099c2c0a21 | ||
|
|
46e15f7fa1 | ||
|
|
6677e63e08 | ||
|
|
6d6a3a3d6b | ||
|
|
5006246cf1 | ||
|
|
6187fb9b86 | ||
|
|
86f57ccd56 | ||
|
|
854096bac7 | ||
|
|
9d2b3bb157 | ||
|
|
22bda62e59 | ||
|
|
c8ad1dcc7a | ||
|
|
7212a7a328 | ||
|
|
f6b1b7b6bb | ||
|
|
53a9d72c4a | ||
|
|
ca10fbcde5 | ||
|
|
b4d33e5dfc | ||
|
|
37bed989f1 | ||
|
|
507eda8a7d | ||
|
|
cbe67e9ebc | ||
|
|
cd38200506 | ||
|
|
5d5cf92a19 | ||
|
|
98230d0ee3 | ||
|
|
e47c04a0b0 | ||
|
|
b65ccac58f | ||
|
|
bee69cc250 | ||
|
|
a350e175c7 | ||
|
|
2cd8188ffb | ||
|
|
b8a2d65a1d | ||
|
|
95f72c854d | ||
|
|
cd8bcfbbb5 | ||
|
|
08f89d2844 | ||
|
|
f3f156bf57 | ||
|
|
73e6f576f4 | ||
|
|
20236cab8a | ||
|
|
6c7b65edad | ||
|
|
21782293ea | ||
|
|
e52478c9aa | ||
|
|
319cba8653 | ||
|
|
df3acccc50 | ||
|
|
19282cac60 | ||
|
|
933c12b48d | ||
|
|
8a73890646 | ||
|
|
d6df5184a6 | ||
|
|
91e1a374b4 | ||
|
|
c725de7b5b | ||
|
|
ad1fd633b1 | ||
|
|
ef9284030b | ||
|
|
89e5740027 | ||
|
|
106f6bbcea | ||
|
|
b53c7a624b | ||
|
|
6fa788a8f9 | ||
|
|
19f0788f47 | ||
|
|
7767226000 | ||
|
|
4eb6b73903 | ||
|
|
cb46ecb002 | ||
|
|
e694921fe6 | ||
|
|
8266661855 | ||
|
|
cf7ddbe0e1 | ||
|
|
bdb3ab366f | ||
|
|
1fc71a0738 | ||
|
|
0b7520e3b1 | ||
|
|
48c8ccfe97 | ||
|
|
ad79b4f77c | ||
|
|
fd876b8636 | ||
|
|
21e896642a | ||
|
|
b4c395f116 | ||
|
|
a38116014d | ||
|
|
54223db1e9 | ||
|
|
8a897abc7b | ||
|
|
fe7b078ec8 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -8,6 +8,7 @@ __pycache__/
|
|||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
env/
|
env/
|
||||||
|
venv/
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
@@ -41,7 +42,6 @@ nosetests.xml
|
|||||||
coverage.xml
|
coverage.xml
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
@@ -62,3 +62,7 @@ celerybeat-schedule
|
|||||||
|
|
||||||
#pycharm
|
#pycharm
|
||||||
.idea/*
|
.idea/*
|
||||||
|
/nbproject/
|
||||||
|
|
||||||
|
#gitlab configs
|
||||||
|
.gitlab/
|
||||||
|
|||||||
33
.gitlab-ci.yml
Normal file
33
.gitlab-ci.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Official language image. Look for the different tagged releases at:
|
||||||
|
# https://hub.docker.com/r/library/python/tags/
|
||||||
|
|
||||||
|
.job_template: &job_definition
|
||||||
|
# Change pip's cache directory to be inside the project directory since we can
|
||||||
|
# only cache local items.
|
||||||
|
variables:
|
||||||
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache"
|
||||||
|
|
||||||
|
# Pip's cache doesn't store the python packages
|
||||||
|
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
|
||||||
|
#
|
||||||
|
# If you want to also cache the installed packages, you have to install
|
||||||
|
# them in a virtualenv and cache it as well.
|
||||||
|
cache:
|
||||||
|
paths:
|
||||||
|
- .cache/pip
|
||||||
|
- venv/
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- python -V # Print out python version for debugging
|
||||||
|
- pip install virtualenv tox
|
||||||
|
- virtualenv venv
|
||||||
|
- source venv/bin/activate
|
||||||
|
|
||||||
|
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
|
||||||
|
|
||||||
|
py36-dj20:
|
||||||
|
<<: *job_definition
|
||||||
|
image: python:3.6-stretch
|
||||||
|
script:
|
||||||
|
- export TOXENV=py36-dj20
|
||||||
|
- tox
|
||||||
14
.gitlab/issue_templates/Bug.md
Normal file
14
.gitlab/issue_templates/Bug.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Bug
|
||||||
|
|
||||||
|
- I have searched [issues](https://gitlab.com/allianceauth/allianceauth/issues?scope=all&utf8=%E2%9C%93&state=all) (Y/N):
|
||||||
|
- What Version of Alliance Auth:
|
||||||
|
- What Operating System:
|
||||||
|
- Version of other components relevant to issue eg. Service, Database:
|
||||||
|
|
||||||
|
Please include a brief description of your issue here.
|
||||||
|
|
||||||
|
Please include steps to reproduce the issue
|
||||||
|
|
||||||
|
Please include any tracebacks or logs
|
||||||
|
|
||||||
|
Please include the results of the command `pip list`
|
||||||
7
.gitlab/issue_templates/Feature Request.md
Normal file
7
.gitlab/issue_templates/Feature Request.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Feature Request
|
||||||
|
|
||||||
|
- Describe the feature are you requesting.
|
||||||
|
|
||||||
|
- Is this a Service (external integration), a Module (Alliance Auth extension) or an enhancement to an existing service/module.
|
||||||
|
|
||||||
|
- Describe why its useful to you or others.
|
||||||
22
README.md
22
README.md
@@ -1,28 +1,32 @@
|
|||||||
Alliance Auth
|
Alliance Auth
|
||||||
============
|
============
|
||||||
|
|
||||||
[](https://gitter.im/R4stl1n/allianceauth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://discord.gg/fjnHAmk)
|
||||||
[](http://allianceauth.readthedocs.io/?badge=latest)
|
[](http://allianceauth.readthedocs.io/?badge=latest)
|
||||||
[](https://travis-ci.org/allianceauth/allianceauth)
|
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||||
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||||
|
|
||||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||||
|
|
||||||
[Get help on gitter](https://gitter.im/R4stl1n/allianceauth) or submit an Issue.
|
[Get help on Discord](https://discord.gg/fjnHAmk) or submit an Issue.
|
||||||
|
|
||||||
|
|
||||||
Active Developers:
|
Active Developers:
|
||||||
|
|
||||||
- [Adarnof](https://github.com/adarnof/)
|
- [Adarnof](https://gitlab.com/adarnof/)
|
||||||
- [Basraah](https://github.com/basraah/)
|
- [Basraah](https://gitlab.com/basraah/)
|
||||||
|
- [Ariel Rin](https://gitlab.com/soratidus999/)
|
||||||
|
- [Col Crunch](https://gitlab.com/colcrunch/)
|
||||||
|
|
||||||
Beta Testers / Bug Fixers:
|
Beta Testers / Bug Fixers:
|
||||||
|
|
||||||
- [ghoti](https://github.com/ghoti/)
|
- [ghoti](https://gitlab.com/ChainsawMcGinny/)
|
||||||
- [mmolitor87](https://github.com/mmolitor87/)
|
- [mmolitor87](https://gitlab.com/mmolitor87/)
|
||||||
|
- [TargetZ3R0](https://github.com/TargetZ3R0)
|
||||||
- [kaezon](https://github.com/kaezon/)
|
- [kaezon](https://github.com/kaezon/)
|
||||||
- [orbitroom](https://github.com/orbitroom/)
|
- [orbitroom](https://github.com/orbitroom/)
|
||||||
- [tehfiend](https://github.com/tehfiend/)
|
- [tehfiend](https://github.com/tehfiend/)
|
||||||
@@ -30,4 +34,4 @@ Beta Testers / Bug Fixers:
|
|||||||
Special thanks to [Nikdoof](https://github.com/nikdoof/), as his [auth](https://github.com/nikdoof/test-auth) was the foundation for the original work on this project.
|
Special thanks to [Nikdoof](https://github.com/nikdoof/), as his [auth](https://github.com/nikdoof/test-auth) was the foundation for the original work on this project.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
Make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
|
Make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# This will make sure the app is always imported when
|
# This will make sure the app is always imported when
|
||||||
# Django starts so that shared_task will use this app.
|
# Django starts so that shared_task will use this app.
|
||||||
|
|
||||||
__version__ = '2.0b3'
|
__version__ = '2.2.1'
|
||||||
NAME = 'Alliance Auth v%s' % __version__
|
NAME = 'Alliance Auth v%s' % __version__
|
||||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.db.models import Q
|
|||||||
from allianceauth.services.hooks import ServicesHook
|
from allianceauth.services.hooks import ServicesHook
|
||||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile
|
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile, OwnershipRecord
|
||||||
from allianceauth.hooks import get_hooks
|
from allianceauth.hooks import get_hooks
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
@@ -160,12 +160,23 @@ class StateAdmin(admin.ModelAdmin):
|
|||||||
return obj.userprofile_set.all().count()
|
return obj.userprofile_set.all().count()
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CharacterOwnership)
|
class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||||
class CharacterOwnershipAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('user', 'character')
|
list_display = ('user', 'character')
|
||||||
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
||||||
readonly_fields = ('owner_hash', 'character')
|
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if obj and obj.pk:
|
||||||
|
return 'owner_hash', 'character'
|
||||||
|
return tuple()
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(OwnershipRecord)
|
||||||
|
class OwnershipRecordAdmin(BaseOwnershipAdmin):
|
||||||
|
list_display = BaseOwnershipAdmin.list_display + ('created',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CharacterOwnership)
|
||||||
|
class CharacterOwnershipAdmin(BaseOwnershipAdmin):
|
||||||
def has_add_permission(self, request):
|
def has_add_permission(self, request):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
import logging
|
||||||
|
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||||
|
|
||||||
from .models import UserProfile, CharacterOwnership
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class StateBackend(ModelBackend):
|
class StateBackend(ModelBackend):
|
||||||
@@ -24,38 +27,54 @@ class StateBackend(ModelBackend):
|
|||||||
user_obj._perm_cache.update(self.get_state_permissions(user_obj))
|
user_obj._perm_cache.update(self.get_state_permissions(user_obj))
|
||||||
return user_obj._perm_cache
|
return user_obj._perm_cache
|
||||||
|
|
||||||
def authenticate(self, token=None):
|
def authenticate(self, request=None, token=None, **credentials):
|
||||||
if not token:
|
if not token:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
||||||
if ownership.owner_hash == token.character_owner_hash:
|
if ownership.owner_hash == token.character_owner_hash:
|
||||||
|
logger.debug('Authenticating {0} by ownership of character {1}'.format(ownership.user, token.character_name))
|
||||||
return ownership.user
|
return ownership.user
|
||||||
else:
|
else:
|
||||||
|
logger.debug('{0} has changed ownership. Creating new user account.'.format(token.character_name))
|
||||||
ownership.delete()
|
ownership.delete()
|
||||||
return self.create_user(token)
|
return self.create_user(token)
|
||||||
except CharacterOwnership.DoesNotExist:
|
except CharacterOwnership.DoesNotExist:
|
||||||
try:
|
try:
|
||||||
# insecure legacy main check for pre-sso registration auth installs
|
# insecure legacy main check for pre-sso registration auth installs
|
||||||
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
|
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
|
||||||
|
logger.debug('Authenticating {0} by their main character {1} without active ownership.'.format(profile.user, profile.main_character))
|
||||||
# attach an ownership
|
# attach an ownership
|
||||||
token.user = profile.user
|
token.user = profile.user
|
||||||
CharacterOwnership.objects.create_by_token(token)
|
CharacterOwnership.objects.create_by_token(token)
|
||||||
return profile.user
|
return profile.user
|
||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
pass
|
# now we check historical records to see if this is a returning user
|
||||||
|
records = OwnershipRecord.objects.filter(owner_hash=token.character_owner_hash).filter(character__character_id=token.character_id)
|
||||||
|
if records.exists():
|
||||||
|
# we've seen this character owner before. Re-attach to their old user account
|
||||||
|
user = records[0].user
|
||||||
|
token.user = user
|
||||||
|
co = CharacterOwnership.objects.create_by_token(token)
|
||||||
|
logger.debug('Authenticating {0} by matching owner hash record of character {1}'.format(user, co.character))
|
||||||
|
if not user.profile.main_character:
|
||||||
|
# set this as their main by default if they have none
|
||||||
|
user.profile.main_character = co.character
|
||||||
|
user.profile.save()
|
||||||
|
return user
|
||||||
|
logger.debug('Unable to authenticate character {0}. Creating new user.'.format(token.character_name))
|
||||||
return self.create_user(token)
|
return self.create_user(token)
|
||||||
|
|
||||||
def create_user(self, token):
|
def create_user(self, token):
|
||||||
username = self.iterate_username(token.character_name) # build unique username off character name
|
username = self.iterate_username(token.character_name) # build unique username off character name
|
||||||
user = User.objects.create_user(username)
|
user = User.objects.create_user(username, is_active=False) # prevent login until email set
|
||||||
user.set_unusable_password() # prevent login via password
|
user.set_unusable_password() # prevent login via password
|
||||||
user.is_active = False # prevent login until email set
|
|
||||||
user.save()
|
user.save()
|
||||||
token.user = user
|
token.user = user
|
||||||
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
|
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
|
||||||
user.profile.main_character = co.character # assign main character as token character
|
user.profile.main_character = co.character # assign main character as token character
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
logger.debug('Created new user {0}'.format(user))
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@@ -35,3 +37,32 @@ def main_character_required(view_func):
|
|||||||
messages.error(request, _('A main character is required to perform that action. Add one below.'))
|
messages.error(request, _('A main character is required to perform that action. Add one below.'))
|
||||||
return redirect('authentication:dashboard')
|
return redirect('authentication:dashboard')
|
||||||
return login_required(_wrapped_view)
|
return login_required(_wrapped_view)
|
||||||
|
|
||||||
|
|
||||||
|
def permissions_required(perm, login_url=None, raise_exception=False):
|
||||||
|
"""
|
||||||
|
Decorator for views that checks whether a user has a particular permission
|
||||||
|
enabled, redirecting to the log-in page if necessary.
|
||||||
|
If the raise_exception parameter is given the PermissionDenied exception
|
||||||
|
is raised.
|
||||||
|
|
||||||
|
This decorator is identical to the django permission_required except it
|
||||||
|
allows for passing a tuple/list of perms that will return true if any one
|
||||||
|
of them is present.
|
||||||
|
"""
|
||||||
|
def check_perms(user):
|
||||||
|
if isinstance(perm, str):
|
||||||
|
perms = (perm,)
|
||||||
|
else:
|
||||||
|
perms = perm
|
||||||
|
# First check if the user has the permission (even anon users)
|
||||||
|
for perm_ in perms:
|
||||||
|
perm_ = (perm_,)
|
||||||
|
if user.has_perms(perm_):
|
||||||
|
return True
|
||||||
|
# In case the 403 handler should be called raise the exception
|
||||||
|
if raise_exception:
|
||||||
|
raise PermissionDenied
|
||||||
|
# As the last resort, show the login form
|
||||||
|
return False
|
||||||
|
return user_passes_test(check_perms, login_url=login_url)
|
||||||
|
|||||||
0
allianceauth/authentication/management/__init__.py
Normal file
0
allianceauth/authentication/management/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from allianceauth.authentication.models import UserProfile
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Ensures all main characters have an active ownership'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
profiles = UserProfile.objects.filter(main_character__isnull=False).filter(
|
||||||
|
main_character__character_ownership__isnull=True)
|
||||||
|
if profiles.exists():
|
||||||
|
for profile in profiles:
|
||||||
|
self.stdout.write(self.style.ERROR(
|
||||||
|
'{0} does not have an ownership. Resetting user {1} main character.'.format(profile.main_character,
|
||||||
|
profile.user)))
|
||||||
|
profile.main_character = None
|
||||||
|
profile.save()
|
||||||
|
self.stdout.write(self.style.WARNING('Reset {0} main characters.'.format(profiles.count())))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.SUCCESS('All main characters have active ownership.'))
|
||||||
@@ -43,7 +43,7 @@ def create_member_group(apps, schema_editor):
|
|||||||
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
g = Group.objects.get(name=member_state_name)
|
g, _ = Group.objects.get_or_create(name=member_state_name)
|
||||||
# move permissions back
|
# move permissions back
|
||||||
state = State.objects.get(name=member_state_name)
|
state = State.objects.get(name=member_state_name)
|
||||||
[g.permissions.add(p.pk) for p in state.permissions.all()]
|
[g.permissions.add(p.pk) for p in state.permissions.all()]
|
||||||
@@ -51,7 +51,7 @@ def create_member_group(apps, schema_editor):
|
|||||||
# move users back
|
# move users back
|
||||||
for profile in state.userprofile_set.all().select_related('user'):
|
for profile in state.userprofile_set.all().select_related('user'):
|
||||||
profile.user.groups.add(g.pk)
|
profile.user.groups.add(g.pk)
|
||||||
except (Group.DoesNotExist, State.DoesNotExist):
|
except State.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ def create_blue_state(apps, schema_editor):
|
|||||||
# move group permissions to state
|
# move group permissions to state
|
||||||
g = Group.objects.get(name=blue_state_name)
|
g = Group.objects.get(name=blue_state_name)
|
||||||
[s.permissions.add(p.pk) for p in g.permissions.all()]
|
[s.permissions.add(p.pk) for p in g.permissions.all()]
|
||||||
g.permissions.clear()
|
g.delete()
|
||||||
except Group.DoesNotExist:
|
except Group.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ def create_blue_group(apps, schema_editor):
|
|||||||
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
g = Group.objects.get(name=blue_state_name)
|
g, _ = Group.objects.get_or_create(name=blue_state_name)
|
||||||
# move permissions back
|
# move permissions back
|
||||||
state = State.objects.get(name=blue_state_name)
|
state = State.objects.get(name=blue_state_name)
|
||||||
[g.permissions.add(p.pk) for p in state.permissions.all()]
|
[g.permissions.add(p.pk) for p in state.permissions.all()]
|
||||||
@@ -92,10 +92,15 @@ def create_blue_group(apps, schema_editor):
|
|||||||
# move users back
|
# move users back
|
||||||
for profile in state.userprofile_set.all().select_related('user'):
|
for profile in state.userprofile_set.all().select_related('user'):
|
||||||
profile.user.groups.add(g.pk)
|
profile.user.groups.add(g.pk)
|
||||||
except (Group.DoesNotExist, State.DoesNotExist):
|
except State.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def purge_tokens(apps, schema_editor):
|
||||||
|
Token = apps.get_model('esi', 'Token')
|
||||||
|
Token.objects.filter(refresh_token__isnull=True).delete()
|
||||||
|
|
||||||
|
|
||||||
def populate_ownerships(apps, schema_editor):
|
def populate_ownerships(apps, schema_editor):
|
||||||
Token = apps.get_model('esi', 'Token')
|
Token = apps.get_model('esi', 'Token')
|
||||||
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||||
@@ -128,15 +133,24 @@ def create_profiles(apps, schema_editor):
|
|||||||
auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id']).exists()]
|
auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id']).exists()]
|
||||||
|
|
||||||
auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user')
|
auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user')
|
||||||
|
|
||||||
|
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||||
|
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||||
|
|
||||||
|
states = {
|
||||||
|
'Member': State.objects.get(name=member_state_name),
|
||||||
|
'Blue': State.objects.get(name=blue_state_name),
|
||||||
|
}
|
||||||
|
guest_state = State.objects.get(name='Guest')
|
||||||
|
|
||||||
for auth in auths:
|
for auth in auths:
|
||||||
# carry states and mains forward
|
# carry states and mains forward
|
||||||
state = State.objects.get(name=auth.state if auth.state else 'Guest')
|
state = states.get(auth.state, guest_state)
|
||||||
char = EveCharacter.objects.get(character_id=auth.main_char_id)
|
char = EveCharacter.objects.get(character_id=auth.main_char_id)
|
||||||
UserProfile.objects.create(user=auth.user, state=state, main_character=char)
|
UserProfile.objects.create(user=auth.user, state=state, main_character=char)
|
||||||
for auth in AuthServicesInfo.objects.exclude(main_char_id__in=unique_mains).select_related('user'):
|
for auth in AuthServicesInfo.objects.exclude(main_char_id__in=unique_mains).select_related('user'):
|
||||||
# prepare empty profiles
|
# prepare empty profiles
|
||||||
state = State.objects.get(name='Guest')
|
UserProfile.objects.create(user=auth.user, state=guest_state)
|
||||||
UserProfile.objects.create(user=auth.user, state=state)
|
|
||||||
|
|
||||||
|
|
||||||
def recreate_authservicesinfo(apps, schema_editor):
|
def recreate_authservicesinfo(apps, schema_editor):
|
||||||
@@ -144,6 +158,14 @@ def recreate_authservicesinfo(apps, schema_editor):
|
|||||||
UserProfile = apps.get_model('authentication', 'UserProfile')
|
UserProfile = apps.get_model('authentication', 'UserProfile')
|
||||||
User = apps.get_model('auth', 'User')
|
User = apps.get_model('auth', 'User')
|
||||||
|
|
||||||
|
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||||
|
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||||
|
|
||||||
|
states = {
|
||||||
|
member_state_name: 'Member',
|
||||||
|
blue_state_name: 'Blue',
|
||||||
|
}
|
||||||
|
|
||||||
# recreate all missing AuthServicesInfo models
|
# recreate all missing AuthServicesInfo models
|
||||||
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()])
|
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()])
|
||||||
|
|
||||||
@@ -154,8 +176,8 @@ def recreate_authservicesinfo(apps, schema_editor):
|
|||||||
|
|
||||||
# repopulate states we understand
|
# repopulate states we understand
|
||||||
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
|
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
|
||||||
state__name__in=['Member', 'Blue']).select_related('user', 'state'):
|
state__name__in=[member_state_name, blue_state_name]).select_related('user', 'state'):
|
||||||
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name})
|
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': states[profile.state.name]})
|
||||||
|
|
||||||
|
|
||||||
def disable_passwords(apps, schema_editor):
|
def disable_passwords(apps, schema_editor):
|
||||||
@@ -221,6 +243,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
|
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
|
||||||
migrations.RunPython(create_member_state, create_member_group),
|
migrations.RunPython(create_member_state, create_member_group),
|
||||||
migrations.RunPython(create_blue_state, create_blue_group),
|
migrations.RunPython(create_blue_state, create_blue_group),
|
||||||
|
migrations.RunPython(purge_tokens, migrations.RunPython.noop),
|
||||||
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
|
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
|
||||||
migrations.RunPython(create_profiles, recreate_authservicesinfo),
|
migrations.RunPython(create_profiles, recreate_authservicesinfo),
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Generated by Django 2.0.4 on 2018-04-14 18:28
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def create_initial_records(apps, schema_editor):
|
||||||
|
OwnershipRecord = apps.get_model('authentication', 'OwnershipRecord')
|
||||||
|
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||||
|
|
||||||
|
OwnershipRecord.objects.bulk_create([
|
||||||
|
OwnershipRecord(user=o.user, character=o.character, owner_hash=o.owner_hash) for o in CharacterOwnership.objects.all()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('eveonline', '0009_on_delete'),
|
||||||
|
('authentication', '0015_user_profiles'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OwnershipRecord',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('owner_hash', models.CharField(db_index=True, max_length=28)),
|
||||||
|
('created', models.DateTimeField(auto_now=True)),
|
||||||
|
('character', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to='eveonline.EveCharacter')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['-created'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RunPython(create_initial_records, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
@@ -96,3 +96,16 @@ class CharacterOwnership(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: %s" % (self.user, self.character)
|
return "%s: %s" % (self.user, self.character)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnershipRecord(models.Model):
|
||||||
|
character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE, related_name='ownership_records')
|
||||||
|
owner_hash = models.CharField(max_length=28, db_index=True)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ownership_records')
|
||||||
|
created = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-created']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s: %s on %s" % (self.user, self.character, self.created)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .models import CharacterOwnership, UserProfile, get_guest_state, State
|
from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||||
from django.dispatch import receiver, Signal
|
from django.dispatch import receiver, Signal
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
|
||||||
@@ -103,29 +103,23 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
|
|||||||
|
|
||||||
@receiver(pre_delete, sender=CharacterOwnership)
|
@receiver(pre_delete, sender=CharacterOwnership)
|
||||||
def validate_main_character(sender, instance, *args, **kwargs):
|
def validate_main_character(sender, instance, *args, **kwargs):
|
||||||
if instance.user.profile.main_character == instance.character:
|
try:
|
||||||
logger.debug("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format(
|
if instance.user.profile.main_character == instance.character:
|
||||||
instance.character, instance.user))
|
logger.info("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format(
|
||||||
# clear main character as user no longer owns them
|
instance.character, instance.user))
|
||||||
instance.user.profile.main_character = None
|
# clear main character as user no longer owns them
|
||||||
instance.user.profile.save()
|
instance.user.profile.main_character = None
|
||||||
|
instance.user.profile.save()
|
||||||
|
except UserProfile.DoesNotExist:
|
||||||
|
# a user is being deleted
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Token)
|
@receiver(post_delete, sender=Token)
|
||||||
def validate_main_character_token(sender, instance, *args, **kwargs):
|
def validate_ownership(sender, instance, *args, **kwargs):
|
||||||
if UserProfile.objects.filter(main_character__character_id=instance.character_id).exists():
|
if not Token.objects.filter(character_owner_hash=instance.character_owner_hash).filter(refresh_token__isnull=False).exists():
|
||||||
logger.debug(
|
logger.info("No remaining tokens to validate ownership of character {0}. Revoking ownership.".format(instance.character_name))
|
||||||
"Token for a main character {0} is being deleted. Ensuring there are valid tokens to refresh.".format(
|
CharacterOwnership.objects.filter(owner_hash=instance.character_owner_hash).delete()
|
||||||
instance.character_name))
|
|
||||||
profile = UserProfile.objects.get(main_character__character_id=instance.character_id)
|
|
||||||
if not Token.objects.filter(character_id=instance.character_id).filter(user=profile.user).exclude(
|
|
||||||
pk=instance.pk).require_valid().exists():
|
|
||||||
logger.debug(
|
|
||||||
"No remaining tokens to validate {0} ownership of main character {1}. Resetting main character.".format(
|
|
||||||
profile.user, profile.main_character))
|
|
||||||
# clear main character as we can no longer verify ownership
|
|
||||||
profile.main_character = None
|
|
||||||
profile.save()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=User)
|
@receiver(pre_save, sender=User)
|
||||||
@@ -153,3 +147,15 @@ def check_state_on_character_update(sender, instance, *args, **kwargs):
|
|||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
|
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=CharacterOwnership)
|
||||||
|
def ownership_record_creation(sender, instance, created, *args, **kwargs):
|
||||||
|
if created:
|
||||||
|
records = OwnershipRecord.objects.filter(owner_hash=instance.owner_hash).filter(character=instance.character)
|
||||||
|
if records.exists():
|
||||||
|
if records[0].user == instance.user: # most recent record is sorted first
|
||||||
|
logger.debug("Already have ownership record of {0} by user {1}".format(instance.character, instance.user))
|
||||||
|
return
|
||||||
|
logger.info("Character {0} has a new owner {1}. Creating ownership record.".format(instance.character, instance.user))
|
||||||
|
OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from esi.errors import TokenExpiredError, TokenInvalidError
|
from esi.errors import TokenExpiredError, TokenInvalidError, IncompleteResponseError
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
@@ -20,13 +20,19 @@ def check_character_ownership(owner_hash):
|
|||||||
except (TokenExpiredError, TokenInvalidError):
|
except (TokenExpiredError, TokenInvalidError):
|
||||||
t.delete()
|
t.delete()
|
||||||
continue
|
continue
|
||||||
|
except (KeyError, IncompleteResponseError):
|
||||||
if t.character_owner_hash == old_hash:
|
# We can't validate the hash hasn't changed but also can't assume it has. Abort for now.
|
||||||
|
logger.warning("Failed to validate owner hash of {0} due to problems contacting SSO servers.".format(
|
||||||
|
tokens[0].character_name))
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
logger.info('Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count()))
|
if not t.character_owner_hash == old_hash:
|
||||||
|
logger.info(
|
||||||
|
'Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count()))
|
||||||
tokens.delete()
|
tokens.delete()
|
||||||
else:
|
break
|
||||||
|
|
||||||
|
if not Token.objects.filter(character_owner_hash=owner_hash).exists():
|
||||||
logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash)
|
logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash)
|
||||||
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
|
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
from io import StringIO
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
from .models import CharacterOwnership, UserProfile, State, get_guest_state
|
from .models import CharacterOwnership, UserProfile, State, get_guest_state, OwnershipRecord
|
||||||
from .backends import StateBackend
|
from .backends import StateBackend
|
||||||
from .tasks import check_character_ownership
|
from .tasks import check_character_ownership
|
||||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
from esi.errors import IncompleteResponseError
|
||||||
from allianceauth.authentication.decorators import main_character_required
|
from allianceauth.authentication.decorators import main_character_required
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
|
from django.core.management import call_command
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
MODULE_PATH = 'allianceauth.authentication'
|
MODULE_PATH = 'allianceauth.authentication'
|
||||||
@@ -90,6 +92,7 @@ class BackendTestCase(TestCase):
|
|||||||
corporation_ticker='CORP',
|
corporation_ticker='CORP',
|
||||||
)
|
)
|
||||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
|
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
|
||||||
AuthUtils.disconnect_signals()
|
AuthUtils.disconnect_signals()
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
||||||
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
||||||
@@ -113,6 +116,14 @@ class BackendTestCase(TestCase):
|
|||||||
self.assertEqual(user.username, 'Unclaimed_Character')
|
self.assertEqual(user.username, 'Unclaimed_Character')
|
||||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||||
|
|
||||||
|
def test_authenticate_character_record(self):
|
||||||
|
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||||
|
record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||||
|
user = StateBackend().authenticate(token=t)
|
||||||
|
self.assertEqual(user, self.old_user)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||||
|
self.assertTrue(user.profile.main_character)
|
||||||
|
|
||||||
def test_iterate_username(self):
|
def test_iterate_username(self):
|
||||||
t = Token(character_id=self.unclaimed_character.character_id,
|
t = Token(character_id=self.unclaimed_character.character_id,
|
||||||
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||||
@@ -185,28 +196,6 @@ class CharacterOwnershipTestCase(TestCase):
|
|||||||
self.user = User.objects.get(pk=self.user.pk)
|
self.user = User.objects.get(pk=self.user.pk)
|
||||||
self.assertIsNone(self.user.profile.main_character)
|
self.assertIsNone(self.user.profile.main_character)
|
||||||
|
|
||||||
@mock.patch('esi.models.Token.update_token_data')
|
|
||||||
def test_character_ownership_check(self, update_token_data):
|
|
||||||
t = Token.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
character_id=self.character.character_id,
|
|
||||||
character_name=self.character.character_name,
|
|
||||||
character_owner_hash='1',
|
|
||||||
)
|
|
||||||
co = CharacterOwnership.objects.get(owner_hash='1')
|
|
||||||
check_character_ownership(co.owner_hash)
|
|
||||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='1').exists())
|
|
||||||
|
|
||||||
t.character_owner_hash = '2'
|
|
||||||
t.save()
|
|
||||||
check_character_ownership(co.owner_hash)
|
|
||||||
self.assertFalse(CharacterOwnership.objects.filter(owner_hash='1').exists())
|
|
||||||
|
|
||||||
t.delete()
|
|
||||||
co = CharacterOwnership.objects.create(user=self.user, character=self.character, owner_hash='3')
|
|
||||||
check_character_ownership(co.owner_hash)
|
|
||||||
self.assertFalse(CharacterOwnership.objects.filter(owner_hash='3').exists())
|
|
||||||
|
|
||||||
|
|
||||||
class StateTestCase(TestCase):
|
class StateTestCase(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -341,3 +330,73 @@ class StateTestCase(TestCase):
|
|||||||
self.user.save()
|
self.user.save()
|
||||||
self._refresh_user()
|
self._refresh_user()
|
||||||
self.assertEquals(self.user.profile.state, self.member_state)
|
self.assertEquals(self.user.profile.state, self.member_state)
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterOwnershipCheckTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
|
||||||
|
corp_name='Test Corp', alliance_name='Test Alliance')
|
||||||
|
cls.character = EveCharacter.objects.get(character_id='1')
|
||||||
|
cls.token = Token.objects.create(
|
||||||
|
user=cls.user,
|
||||||
|
character_id='1',
|
||||||
|
character_name='Test',
|
||||||
|
character_owner_hash='1',
|
||||||
|
)
|
||||||
|
cls.ownership = CharacterOwnership.objects.get(character=cls.character)
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||||
|
def test_no_change_owner_hash(self, update_token_data):
|
||||||
|
# makes sure the ownership isn't delete if owner hash hasn't changed
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||||
|
def test_unable_to_update_token_data(self, update_token_data):
|
||||||
|
# makes sure ownerships and tokens aren't hellpurged when there's problems with the SSO servers
|
||||||
|
update_token_data.side_effect = IncompleteResponseError()
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||||
|
|
||||||
|
update_token_data.side_effect = KeyError()
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.delete')
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.Token.objects.exists')
|
||||||
|
@mock.patch(MODULE_PATH + '.tasks.CharacterOwnership.objects.filter')
|
||||||
|
def test_owner_hash_changed(self, filter, exists, delete, update_token_data):
|
||||||
|
# makes sure the ownership is revoked when owner hash changes
|
||||||
|
filter.return_value.exists.return_value = False
|
||||||
|
check_character_ownership(self.ownership)
|
||||||
|
self.assertTrue(filter.return_value.delete.called)
|
||||||
|
|
||||||
|
|
||||||
|
class ManagementCommandTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
|
||||||
|
character = UserProfile.objects.get(user=cls.user).main_character
|
||||||
|
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.stdout = StringIO()
|
||||||
|
|
||||||
|
def test_ownership(self):
|
||||||
|
call_command('checkmains', stdout=self.stdout)
|
||||||
|
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||||
|
self.assertNotIn(self.user.username, self.stdout.getvalue())
|
||||||
|
self.assertIn('All main characters', self.stdout.getvalue())
|
||||||
|
|
||||||
|
def test_no_ownership(self):
|
||||||
|
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
|
||||||
|
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
|
||||||
|
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||||
|
|
||||||
|
call_command('checkmains', stdout=self.stdout)
|
||||||
|
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
|
||||||
|
self.assertIn(user.username, self.stdout.getvalue())
|
||||||
|
|||||||
@@ -95,20 +95,33 @@ class RegistrationView(BaseRegistrationView):
|
|||||||
form_class = RegistrationForm
|
form_class = RegistrationForm
|
||||||
success_url = 'authentication:dashboard'
|
success_url = 'authentication:dashboard'
|
||||||
|
|
||||||
def dispatch(self, *args, **kwargs):
|
def get_success_url(self, user):
|
||||||
|
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
|
return 'authentication:dashboard', (), {}
|
||||||
|
return super().get_success_url(user)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
|
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
|
||||||
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
|
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
|
||||||
pk=self.request.session.get('registration_uid')).exists():
|
pk=self.request.session.get('registration_uid')).exists():
|
||||||
messages.error(self.request, _('Registration token has expired.'))
|
messages.error(self.request, _('Registration token has expired.'))
|
||||||
return redirect(settings.LOGIN_URL)
|
return redirect(settings.LOGIN_URL)
|
||||||
return super(RegistrationView, self).dispatch(*args, **kwargs)
|
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
|
# Keep the request so the user can be automagically logged in.
|
||||||
|
setattr(self, 'request', request)
|
||||||
|
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def register(self, form):
|
def register(self, form):
|
||||||
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
||||||
user.email = form.cleaned_data['email']
|
user.email = form.cleaned_data['email']
|
||||||
user_registered.send(self.__class__, user=user, request=self.request)
|
user_registered.send(self.__class__, user=user, request=self.request)
|
||||||
# Go to Step 3
|
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
self.send_activation_email(user)
|
# Go to Step 3
|
||||||
|
self.send_activation_email(user)
|
||||||
|
else:
|
||||||
|
user.is_active = True
|
||||||
|
user.save()
|
||||||
|
login(self.request, user, 'allianceauth.authentication.backends.StateBackend')
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def get_activation_key(self, user):
|
def get_activation_key(self, user):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.corputils import urls
|
from allianceauth.corputils import urls
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ from allianceauth.corputils import urls
|
|||||||
class CorpStats(MenuItemHook):
|
class CorpStats(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(self,
|
||||||
'Corporation Stats',
|
_('Corporation Stats'),
|
||||||
'fa fa-share-alt fa-fw',
|
'fa fa-share-alt fa-fw',
|
||||||
'corputils:view',
|
'corputils:view',
|
||||||
navactive=['corputils:'])
|
navactive=['corputils:'])
|
||||||
|
|||||||
@@ -16,10 +16,16 @@ class CorpStatsQuerySet(models.QuerySet):
|
|||||||
assert char
|
assert char
|
||||||
# build all accepted queries
|
# build all accepted queries
|
||||||
queries = [models.Q(token__user=user)]
|
queries = [models.Q(token__user=user)]
|
||||||
if user.has_perm('corputils.view_corp_corpstats'):
|
|
||||||
queries.append(models.Q(corp__corporation_id=char.corporation_id))
|
|
||||||
if user.has_perm('corputils.view_alliance_corpstats'):
|
if user.has_perm('corputils.view_alliance_corpstats'):
|
||||||
queries.append(models.Q(corp__alliance__alliance_id=char.alliance_id))
|
if char.alliance_id is not None:
|
||||||
|
queries.append(models.Q(corp__alliance__alliance_id=char.alliance_id))
|
||||||
|
else:
|
||||||
|
queries.append(models.Q(corp__corporation_id=char.corporation_id))
|
||||||
|
if user.has_perm('corputils.view_corp_corpstats'):
|
||||||
|
if user.has_perm('corputils.view_alliance_corpstats'):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
queries.append(models.Q(corp__corporation_id=char.corporation_id))
|
||||||
if user.has_perm('corputils.view_state_corpstats'):
|
if user.has_perm('corputils.view_state_corpstats'):
|
||||||
queries.append(models.Q(corp__in=user.profile.state.member_corporations.all()))
|
queries.append(models.Q(corp__in=user.profile.state.member_corporations.all()))
|
||||||
queries.append(models.Q(corp__alliance__in=user.profile.state.member_alliances.all()))
|
queries.append(models.Q(corp__alliance__in=user.profile.state.member_alliances.all()))
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ from allianceauth.notifications import notify
|
|||||||
from allianceauth.corputils.managers import CorpStatsManager
|
from allianceauth.corputils.managers import CorpStatsManager
|
||||||
|
|
||||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
"""
|
||||||
|
Swagger spec operations:
|
||||||
|
|
||||||
|
Character
|
||||||
|
get_characters_character_id
|
||||||
|
get_corporations_corporation_id_members
|
||||||
|
Universe
|
||||||
|
post_universe_names
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -40,19 +50,18 @@ class CorpStats(models.Model):
|
|||||||
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||||
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
||||||
'corporation_id'] == int(self.corp.corporation_id)
|
'corporation_id'] == int(self.corp.corporation_id)
|
||||||
members = c.Corporation.get_corporations_corporation_id_members(
|
member_ids = c.Corporation.get_corporations_corporation_id_members(
|
||||||
corporation_id=self.corp.corporation_id).result()
|
corporation_id=self.corp.corporation_id).result()
|
||||||
member_ids = [m['character_id'] for m in members]
|
|
||||||
|
|
||||||
# requesting too many ids per call results in a HTTP400
|
# requesting too many ids per call results in a HTTP400
|
||||||
# the swagger spec doesn't have a maxItems count
|
# the swagger spec doesn't have a maxItems count
|
||||||
# manual testing says we can do over 350, but let's not risk it
|
# manual testing says we can do over 350, but let's not risk it
|
||||||
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
|
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
|
||||||
member_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in
|
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in
|
||||||
member_id_chunks]
|
member_id_chunks]
|
||||||
member_list = {}
|
member_list = {}
|
||||||
for name_chunk in member_name_chunks:
|
for name_chunk in member_name_chunks:
|
||||||
member_list.update({m['character_id']: m['character_name'] for m in name_chunk})
|
member_list.update({m['id']: m['name'] for m in name_chunk})
|
||||||
|
|
||||||
# bulk create new member models
|
# bulk create new member models
|
||||||
missing_members = [m_id for m_id in member_ids if
|
missing_members = [m_id for m_id in member_ids if
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from allianceauth.corputils import CorpStats
|
from allianceauth.corputils.models import CorpStats
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img
|
{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img
|
||||||
class="ra-avatar" src="{{ corpstats.corp.logo_url_128 }}"></td>
|
class="ra-avatar" src="{{ corpstats.corp.logo_url_128 }}"></td>
|
||||||
{% if corpstats.corp.alliance %}
|
{% if corpstats.corp.alliance %}
|
||||||
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.alliance.logo_url_128 }}">
|
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_128 }}">
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -202,4 +202,4 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ class CorpStatsUpdateTestCase(TestCase):
|
|||||||
@mock.patch('esi.clients.SwaggerClient')
|
@mock.patch('esi.clients.SwaggerClient')
|
||||||
def test_update_add_member(self, SwaggerClient):
|
def test_update_add_member(self, SwaggerClient):
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
||||||
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [{'character_id': 1}]
|
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}]
|
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
|
||||||
self.corpstats.update()
|
self.corpstats.update()
|
||||||
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists())
|
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists())
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ class CorpStatsUpdateTestCase(TestCase):
|
|||||||
def test_update_remove_member(self, SwaggerClient):
|
def test_update_remove_member(self, SwaggerClient):
|
||||||
CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats)
|
CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats)
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
|
||||||
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [{'character_id': 1}]
|
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
|
||||||
SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}]
|
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
|
||||||
self.corpstats.update()
|
self.corpstats.update()
|
||||||
self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists())
|
self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists())
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,17 @@ from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo
|
|||||||
from .models import CorpStats
|
from .models import CorpStats
|
||||||
|
|
||||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
"""
|
||||||
|
Swagger spec operations:
|
||||||
|
|
||||||
|
get_characters_character_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def access_corpstats_test(user):
|
def access_corpstats_test(user):
|
||||||
return user.has_perm('corputils.view_corp_corpstats') or user.has_perm(
|
return user.has_perm('corputils.view_corp_corpstats') or user.has_perm(
|
||||||
'corputils.view_alliance_corpstats') or user.has_perm('corputils.view_state_corpstats')
|
'corputils.view_alliance_corpstats') or user.has_perm('corputils.view_state_corpstats') or user.has_perm(
|
||||||
|
'corputils.add_corpstats')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -62,7 +68,7 @@ def corpstats_view(request, corp_id=None):
|
|||||||
corpstats = get_object_or_404(CorpStats, corp=corp)
|
corpstats = get_object_or_404(CorpStats, corp=corp)
|
||||||
|
|
||||||
# get available models
|
# get available models
|
||||||
available = CorpStats.objects.visible_to(request.user)
|
available = CorpStats.objects.visible_to(request.user).order_by('corp__corporation_name')
|
||||||
|
|
||||||
# ensure we can see the requested model
|
# ensure we can see the requested model
|
||||||
if corpstats and corpstats not in available:
|
if corpstats and corpstats not in available:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from .models import AutogroupsConfig
|
from .models import AutogroupsConfig, ManagedCorpGroup, ManagedAllianceGroup
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -37,3 +37,6 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
|
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
|
||||||
|
admin.site.register(ManagedCorpGroup)
|
||||||
|
admin.site.register(ManagedAllianceGroup)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def get_users_for_state(state: State):
|
def get_users_for_state(state: State):
|
||||||
return User.objects.select_related('profile').prefetch_related('profile__main_character')\
|
return User.objects.select_related('profile').prefetch_related('profile__main_character')\
|
||||||
.filter(profile__state__pk=state.pk)
|
.filter(profile__state_id=state.pk)
|
||||||
|
|
||||||
|
|
||||||
class AutogroupsConfigManager(models.Manager):
|
class AutogroupsConfigManager(models.Manager):
|
||||||
@@ -39,7 +39,12 @@ class AutogroupsConfigManager(models.Manager):
|
|||||||
if state is None:
|
if state is None:
|
||||||
state = user.profile.state
|
state = user.profile.state
|
||||||
for config in self.filter(states=state):
|
for config in self.filter(states=state):
|
||||||
config.update_group_membership_for_user(user)
|
# grant user new groups for their state
|
||||||
|
config.update_group_membership_for_user(user)
|
||||||
|
for config in self.exclude(states=state):
|
||||||
|
# ensure user does not have groups from previous state
|
||||||
|
config.remove_user_from_alliance_groups(user)
|
||||||
|
config.remove_user_from_corp_groups(user)
|
||||||
|
|
||||||
|
|
||||||
class AutogroupsConfig(models.Model):
|
class AutogroupsConfig(models.Model):
|
||||||
@@ -119,8 +124,9 @@ class AutogroupsConfig(models.Model):
|
|||||||
return
|
return
|
||||||
group = self.get_alliance_group(alliance)
|
group = self.get_alliance_group(alliance)
|
||||||
except EveAllianceInfo.DoesNotExist:
|
except EveAllianceInfo.DoesNotExist:
|
||||||
logger.warning('User {} main characters alliance does not exist in the database.'
|
logger.debug('User {} main characters alliance does not exist in the database. Creating.'.format(user))
|
||||||
' Group membership not updated'.format(user))
|
alliance = EveAllianceInfo.objects.create_alliance(user.profile.main_character.alliance_id)
|
||||||
|
group = self.get_alliance_group(alliance)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
||||||
finally:
|
finally:
|
||||||
@@ -139,8 +145,9 @@ class AutogroupsConfig(models.Model):
|
|||||||
corp = user.profile.main_character.corporation
|
corp = user.profile.main_character.corporation
|
||||||
group = self.get_corp_group(corp)
|
group = self.get_corp_group(corp)
|
||||||
except EveCorporationInfo.DoesNotExist:
|
except EveCorporationInfo.DoesNotExist:
|
||||||
logger.warning('User {} main characters corporation does not exist in the database.'
|
logger.debug('User {} main characters corporation does not exist in the database. Creating.'.format(user))
|
||||||
' Group membership not updated'.format(user))
|
corp = EveCorporationInfo.objects.create_corporation(user.profile.main_character.corporation_id)
|
||||||
|
group = self.get_corp_group(corp)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
||||||
finally:
|
finally:
|
||||||
@@ -172,15 +179,13 @@ class AutogroupsConfig(models.Model):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_alliance_group(self, alliance: EveAllianceInfo) -> Group:
|
def create_alliance_group(self, alliance: EveAllianceInfo) -> Group:
|
||||||
group, created = Group.objects.get_or_create(name=self.get_alliance_group_name(alliance))
|
group, created = Group.objects.get_or_create(name=self.get_alliance_group_name(alliance))
|
||||||
if created:
|
ManagedAllianceGroup.objects.get_or_create(group=group, config=self, alliance=alliance)
|
||||||
ManagedAllianceGroup.objects.create(group=group, config=self, alliance=alliance)
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create_corp_group(self, corp: EveCorporationInfo) -> Group:
|
def create_corp_group(self, corp: EveCorporationInfo) -> Group:
|
||||||
group, created = Group.objects.get_or_create(name=self.get_corp_group_name(corp))
|
group, created = Group.objects.get_or_create(name=self.get_corp_group_name(corp))
|
||||||
if created:
|
ManagedCorpGroup.objects.get_or_create(group=group, config=self, corp=corp)
|
||||||
ManagedCorpGroup.objects.create(group=group, config=self, corp=corp)
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def delete_alliance_managed_groups(self):
|
def delete_alliance_managed_groups(self):
|
||||||
@@ -233,6 +238,8 @@ class ManagedGroup(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Managed Group: %s" % self.group.name
|
||||||
|
|
||||||
class ManagedCorpGroup(ManagedGroup):
|
class ManagedCorpGroup(ManagedGroup):
|
||||||
corp = models.ForeignKey(EveCorporationInfo, on_delete=models.CASCADE)
|
corp = models.ForeignKey(EveCorporationInfo, on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import logging
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||||
from allianceauth.authentication.models import UserProfile, State
|
from allianceauth.authentication.models import UserProfile, State
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
|
||||||
from .models import AutogroupsConfig
|
from .models import AutogroupsConfig
|
||||||
|
|
||||||
@@ -45,9 +46,7 @@ def check_groups_on_profile_update(sender, instance, created, *args, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Trigger check when main character or state changes.
|
Trigger check when main character or state changes.
|
||||||
"""
|
"""
|
||||||
update_fields = kwargs.pop('update_fields', []) or []
|
AutogroupsConfig.objects.update_groups_for_user(instance.user)
|
||||||
if 'main_character' in update_fields or 'state' in update_fields:
|
|
||||||
AutogroupsConfig.objects.update_groups_for_user(instance.user)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=AutogroupsConfig.states.through)
|
@receiver(m2m_changed, sender=AutogroupsConfig.states.through)
|
||||||
@@ -64,3 +63,13 @@ def autogroups_states_changed(sender, instance, action, reverse, model, pk_set,
|
|||||||
except State.DoesNotExist:
|
except State.DoesNotExist:
|
||||||
# Deleted States handled by the profile state change
|
# Deleted States handled by the profile state change
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=EveCharacter)
|
||||||
|
def check_groups_on_character_update(sender, instance, created, *args, **kwargs):
|
||||||
|
if not created:
|
||||||
|
try:
|
||||||
|
profile = UserProfile.objects.prefetch_related('user').get(main_character_id=instance.pk)
|
||||||
|
AutogroupsConfig.objects.update_groups_for_user(profile.user)
|
||||||
|
except UserProfile.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||||
from allianceauth.authentication.models import UserProfile
|
from allianceauth.authentication.models import UserProfile
|
||||||
from allianceauth.authentication.signals import state_changed
|
from allianceauth.authentication.signals import reassess_on_profile_save
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
|
||||||
from .. import signals
|
from .. import signals
|
||||||
from ..models import AutogroupsConfig
|
from ..models import AutogroupsConfig
|
||||||
|
|
||||||
@@ -14,6 +13,7 @@ def patch(target, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def connect_signals():
|
def connect_signals():
|
||||||
|
post_save.connect(receiver=reassess_on_profile_save, sender=UserProfile)
|
||||||
pre_save.connect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
pre_save.connect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
||||||
pre_delete.connect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
pre_delete.connect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
||||||
post_save.connect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
post_save.connect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
||||||
@@ -21,6 +21,7 @@ def connect_signals():
|
|||||||
|
|
||||||
|
|
||||||
def disconnect_signals():
|
def disconnect_signals():
|
||||||
|
post_save.disconnect(receiver=reassess_on_profile_save, sender=UserProfile)
|
||||||
pre_save.disconnect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
pre_save.disconnect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
|
||||||
pre_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
pre_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
||||||
post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from . import patch
|
|||||||
|
|
||||||
class AutogroupsConfigManagerTestCase(TestCase):
|
class AutogroupsConfigManagerTestCase(TestCase):
|
||||||
|
|
||||||
def test_update_groups_for_state(self, ):
|
def test_update_groups_for_state(self):
|
||||||
member = AuthUtils.create_member('test member')
|
member = AuthUtils.create_member('test member')
|
||||||
obj = AutogroupsConfig.objects.create()
|
obj = AutogroupsConfig.objects.create()
|
||||||
obj.states.add(member.profile.state)
|
obj.states.add(member.profile.state)
|
||||||
@@ -32,3 +32,23 @@ class AutogroupsConfigManagerTestCase(TestCase):
|
|||||||
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
||||||
args, kwargs = update_group_membership_for_user.call_args
|
args, kwargs = update_group_membership_for_user.call_args
|
||||||
self.assertEqual(args[0], member)
|
self.assertEqual(args[0], member)
|
||||||
|
|
||||||
|
@patch('.models.AutogroupsConfig.update_group_membership_for_user')
|
||||||
|
@patch('.models.AutogroupsConfig.remove_user_from_alliance_groups')
|
||||||
|
@patch('.models.AutogroupsConfig.remove_user_from_corp_groups')
|
||||||
|
def test_update_groups_no_config(self, remove_corp, remove_alliance, update_groups):
|
||||||
|
member = AuthUtils.create_member('test member')
|
||||||
|
obj = AutogroupsConfig.objects.create()
|
||||||
|
|
||||||
|
# Corp and alliance groups should be removed from users if their state has no config
|
||||||
|
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||||
|
|
||||||
|
self.assertFalse(update_groups.called)
|
||||||
|
self.assertTrue(remove_alliance.called)
|
||||||
|
self.assertTrue(remove_corp.called)
|
||||||
|
|
||||||
|
# The normal group assignment should occur if there state has a config
|
||||||
|
obj.states.add(member.profile.state)
|
||||||
|
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||||
|
|
||||||
|
self.assertTrue(update_groups.called)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ class AutogroupsConfigTestCase(TestCase):
|
|||||||
# Disconnect signals
|
# Disconnect signals
|
||||||
disconnect_signals()
|
disconnect_signals()
|
||||||
|
|
||||||
self.member = AuthUtils.create_member('test user')
|
|
||||||
|
|
||||||
state = AuthUtils.get_member_state()
|
state = AuthUtils.get_member_state()
|
||||||
|
|
||||||
self.alliance = EveAllianceInfo.objects.create(
|
self.alliance = EveAllianceInfo.objects.create(
|
||||||
@@ -38,6 +36,8 @@ class AutogroupsConfigTestCase(TestCase):
|
|||||||
state.member_alliances.add(self.alliance)
|
state.member_alliances.add(self.alliance)
|
||||||
state.member_corporations.add(self.corp)
|
state.member_corporations.add(self.corp)
|
||||||
|
|
||||||
|
self.member = AuthUtils.create_member('test user')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Reconnect signals
|
# Reconnect signals
|
||||||
connect_signals()
|
connect_signals()
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||||
|
|
||||||
from ..models import AutogroupsConfig, ManagedAllianceGroup
|
from ..models import AutogroupsConfig
|
||||||
|
|
||||||
from . import patch, disconnect_signals, connect_signals
|
from . import patch, disconnect_signals, connect_signals
|
||||||
|
|
||||||
@@ -13,8 +13,6 @@ from . import patch, disconnect_signals, connect_signals
|
|||||||
class SignalsTestCase(TestCase):
|
class SignalsTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
disconnect_signals()
|
disconnect_signals()
|
||||||
self.member = AuthUtils.create_member('test user')
|
|
||||||
|
|
||||||
state = AuthUtils.get_member_state()
|
state = AuthUtils.get_member_state()
|
||||||
|
|
||||||
self.char = EveCharacter.objects.create(
|
self.char = EveCharacter.objects.create(
|
||||||
@@ -27,9 +25,6 @@ class SignalsTestCase(TestCase):
|
|||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.member.profile.main_character = self.char
|
|
||||||
self.member.profile.save()
|
|
||||||
|
|
||||||
self.alliance = EveAllianceInfo.objects.create(
|
self.alliance = EveAllianceInfo.objects.create(
|
||||||
alliance_id='3456',
|
alliance_id='3456',
|
||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
@@ -47,13 +42,17 @@ class SignalsTestCase(TestCase):
|
|||||||
|
|
||||||
state.member_alliances.add(self.alliance)
|
state.member_alliances.add(self.alliance)
|
||||||
state.member_corporations.add(self.corp)
|
state.member_corporations.add(self.corp)
|
||||||
|
|
||||||
|
self.member = AuthUtils.create_member('test user')
|
||||||
|
self.member.profile.main_character = self.char
|
||||||
|
self.member.profile.save()
|
||||||
|
|
||||||
connect_signals()
|
connect_signals()
|
||||||
|
|
||||||
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
|
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
|
||||||
def test_check_groups_on_profile_update_state(self, update_groups_for_user):
|
def test_check_groups_on_profile_update_state(self, update_groups_for_user):
|
||||||
# Trigger signal
|
# Trigger signal
|
||||||
self.member.profile.state = AuthUtils.get_guest_state()
|
self.member.profile.assign_state(state=AuthUtils.get_guest_state())
|
||||||
self.member.profile.save()
|
|
||||||
|
|
||||||
self.assertTrue(update_groups_for_user.called)
|
self.assertTrue(update_groups_for_user.called)
|
||||||
self.assertEqual(update_groups_for_user.call_count, 1)
|
self.assertEqual(update_groups_for_user.call_count, 1)
|
||||||
@@ -71,10 +70,10 @@ class SignalsTestCase(TestCase):
|
|||||||
alliance_id='3456',
|
alliance_id='3456',
|
||||||
alliance_name='alliance name',
|
alliance_name='alliance name',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Trigger signal
|
# Trigger signal
|
||||||
self.member.profile.main_character = char
|
self.member.profile.main_character = char
|
||||||
self.member.profile.save()
|
self.member.profile.save()
|
||||||
|
|
||||||
self.assertTrue(update_groups_for_user.called)
|
self.assertTrue(update_groups_for_user.called)
|
||||||
self.assertEqual(update_groups_for_user.call_count, 1)
|
self.assertEqual(update_groups_for_user.call_count, 1)
|
||||||
args, kwargs = update_groups_for_user.call_args
|
args, kwargs = update_groups_for_user.call_args
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class EveCharacterManager(models.Manager):
|
|||||||
corporation_ticker=character.corp.ticker,
|
corporation_ticker=character.corp.ticker,
|
||||||
alliance_id=character.alliance.id,
|
alliance_id=character.alliance.id,
|
||||||
alliance_name=character.alliance.name,
|
alliance_name=character.alliance.name,
|
||||||
|
alliance_ticker=getattr(character.alliance, 'ticker', None),
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_character(self, character_id):
|
def update_character(self, character_id):
|
||||||
|
|||||||
23
allianceauth/eveonline/migrations/0010_alliance_ticker.py
Normal file
23
allianceauth/eveonline/migrations/0010_alliance_ticker.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 2.0.4 on 2018-04-17 20:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('eveonline', '0009_on_delete'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='alliance_ticker',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=5, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='evecharacter',
|
||||||
|
name='corporation_ticker',
|
||||||
|
field=models.CharField(max_length=5),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -84,9 +84,10 @@ class EveCharacter(models.Model):
|
|||||||
character_name = models.CharField(max_length=254, unique=True)
|
character_name = models.CharField(max_length=254, unique=True)
|
||||||
corporation_id = models.CharField(max_length=254)
|
corporation_id = models.CharField(max_length=254)
|
||||||
corporation_name = models.CharField(max_length=254)
|
corporation_name = models.CharField(max_length=254)
|
||||||
corporation_ticker = models.CharField(max_length=254)
|
corporation_ticker = models.CharField(max_length=5)
|
||||||
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='')
|
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='')
|
||||||
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
|
||||||
|
alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
|
||||||
|
|
||||||
objects = EveCharacterManager()
|
objects = EveCharacterManager()
|
||||||
provider = EveCharacterProviderManager()
|
provider = EveCharacterProviderManager()
|
||||||
@@ -120,6 +121,7 @@ class EveCharacter(models.Model):
|
|||||||
self.corporation_ticker = character.corp.ticker
|
self.corporation_ticker = character.corp.ticker
|
||||||
self.alliance_id = character.alliance.id
|
self.alliance_id = character.alliance.id
|
||||||
self.alliance_name = character.alliance.name
|
self.alliance_name = character.alliance.name
|
||||||
|
self.alliance_ticker = getattr(character.alliance, 'ticker', None)
|
||||||
self.save()
|
self.save()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,16 @@ import logging
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
"""
|
||||||
|
Swagger spec operations:
|
||||||
|
|
||||||
|
get_alliances_alliance_id
|
||||||
|
get_alliances_alliance_id_corporations
|
||||||
|
get_corporations_corporation_id
|
||||||
|
get_characters_character_id
|
||||||
|
get_universe_types_type_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -81,7 +91,9 @@ class Alliance(Entity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def executor_corp(self):
|
def executor_corp(self):
|
||||||
return self.corp(self.executor_corp_id)
|
if self.executor_corp_id:
|
||||||
|
return self.corp(self.executor_corp_id)
|
||||||
|
return Entity(None, None)
|
||||||
|
|
||||||
|
|
||||||
class Character(Entity):
|
class Character(Entity):
|
||||||
@@ -150,10 +162,10 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
|
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
|
||||||
model = Alliance(
|
model = Alliance(
|
||||||
id=alliance_id,
|
id=alliance_id,
|
||||||
name=data['alliance_name'],
|
name=data['name'],
|
||||||
ticker=data['ticker'],
|
ticker=data['ticker'],
|
||||||
corp_ids=corps,
|
corp_ids=corps,
|
||||||
executor_corp_id=data['executor_corp'],
|
executor_corp_id=data['executor_corporation_id'] if 'executor_corporation_id' in data else None,
|
||||||
)
|
)
|
||||||
return model
|
return model
|
||||||
except HTTPNotFound:
|
except HTTPNotFound:
|
||||||
@@ -164,7 +176,7 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
|
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
|
||||||
model = Corporation(
|
model = Corporation(
|
||||||
id=corp_id,
|
id=corp_id,
|
||||||
name=data['corporation_name'],
|
name=data['name'],
|
||||||
ticker=data['ticker'],
|
ticker=data['ticker'],
|
||||||
ceo_id=data['ceo_id'],
|
ceo_id=data['ceo_id'],
|
||||||
members=data['member_count'],
|
members=data['member_count'],
|
||||||
@@ -177,12 +189,11 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
def get_character(self, character_id):
|
def get_character(self, character_id):
|
||||||
try:
|
try:
|
||||||
data = self.client.Character.get_characters_character_id(character_id=character_id).result()
|
data = self.client.Character.get_characters_character_id(character_id=character_id).result()
|
||||||
alliance_id = self.adapter.get_corp(data['corporation_id']).alliance_id
|
|
||||||
model = Character(
|
model = Character(
|
||||||
id=character_id,
|
id=character_id,
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
corp_id=data['corporation_id'],
|
corp_id=data['corporation_id'],
|
||||||
alliance_id=alliance_id,
|
alliance_id=data['alliance_id'] if 'alliance_id' in data else None,
|
||||||
)
|
)
|
||||||
return model
|
return model
|
||||||
except (HTTPNotFound, HTTPUnprocessableEntity):
|
except (HTTPNotFound, HTTPUnprocessableEntity):
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,12 @@
|
|||||||
from . import urls
|
from . import urls
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||||
|
|
||||||
|
|
||||||
@hooks.register('menu_item_hook')
|
@hooks.register('menu_item_hook')
|
||||||
def register_menu():
|
def register_menu():
|
||||||
return MenuItemHook('Fleet Activity Tracking', 'fa fa-users fa-lightbulb-o fa-fw', 'fatlink:view',
|
return MenuItemHook(_('Fleet Activity Tracking'), 'fa fa-users fa-lightbulb-o fa-fw', 'fatlink:view',
|
||||||
navactive=['fatlink:'])
|
navactive=['fatlink:'])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.0.6 on 2018-08-03 04:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('fleetactivitytracking', '0005_remove_fat_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='fat',
|
||||||
|
name='shiptype',
|
||||||
|
field=models.CharField(max_length=100),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -24,7 +24,7 @@ class Fat(models.Model):
|
|||||||
character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE)
|
character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE)
|
||||||
fatlink = models.ForeignKey(Fatlink, on_delete=models.CASCADE)
|
fatlink = models.ForeignKey(Fatlink, on_delete=models.CASCADE)
|
||||||
system = models.CharField(max_length=30)
|
system = models.CharField(max_length=30)
|
||||||
shiptype = models.CharField(max_length=30)
|
shiptype = models.CharField(max_length=100)
|
||||||
station = models.CharField(max_length=125)
|
station = models.CharField(max_length=125)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -22,6 +22,16 @@ from allianceauth.eveonline.models import EveCharacter
|
|||||||
from allianceauth.eveonline.models import EveCorporationInfo
|
from allianceauth.eveonline.models import EveCorporationInfo
|
||||||
|
|
||||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
"""
|
||||||
|
Swagger spec operations:
|
||||||
|
|
||||||
|
get_characters_character_id_location
|
||||||
|
get_characters_character_id_ship
|
||||||
|
get_universe_systems_system_id
|
||||||
|
get_universe_stations_station_id
|
||||||
|
get_universe_structures_structure_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -112,7 +122,7 @@ def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
|
|||||||
start_of_next_month = first_day_of_next_month(year, month)
|
start_of_next_month = first_day_of_next_month(year, month)
|
||||||
start_of_previous_month = first_day_of_previous_month(year, month)
|
start_of_previous_month = first_day_of_previous_month(year, month)
|
||||||
fat_stats = {}
|
fat_stats = {}
|
||||||
corp_members = CharacterOwnership.objects.filter(character__corporation_id=corpid).values('user_id').distinct()
|
corp_members = CharacterOwnership.objects.filter(character__corporation_id=corpid).order_by('user_id').values('user_id').distinct()
|
||||||
|
|
||||||
for member in corp_members:
|
for member in corp_members:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-condensed table-hover table-striped">
|
<table class="table table-condensed table-hover table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-1"></th>
|
<th class="col-md-1"></th>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
{% for char_name, user_id in member_list %}
|
{% for char_name, user_id in member_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img src="http://image.eveonline.com/Character/{{ user_id.char_id }}_32.jpg" class="img-circle">
|
<img src="https://imageserver.eveonline.com/Character/{{ user_id.char_id }}_32.jpg" class="img-circle">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<p>{{ user_id.char_name }}</p>
|
<p>{{ user_id.char_name }}</p>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
{% include "fleetup/menu.html" %}
|
{% include "fleetup/menu.html" %}
|
||||||
<div class="panel">
|
<div>
|
||||||
{% for a, j in doctrine.items %}
|
{% for a, j in doctrine.items %}
|
||||||
{% regroup j.Data|dictsort:"Role" by Role as role_list %}
|
{% regroup j.Data|dictsort:"Role" by Role as role_list %}
|
||||||
|
|
||||||
{% for Role in role_list %}
|
{% for Role in role_list %}
|
||||||
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title"><b>{{ Role.grouper }}</b></h3>
|
<h3 class="panel-title"><b>{{ Role.grouper }}</b></h3>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
{% load humanize %}{{ item.EstPrice|intword }}
|
{% load humanize %}{{ item.EstPrice|intword }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% for categories in item.Categories %}
|
{% for categories in item.Categories %}
|
||||||
{{ categories }},
|
{{ categories }},
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
{% include "fleetup/menu.html" %}
|
{% include "fleetup/menu.html" %}
|
||||||
<div class="panel">
|
<div>
|
||||||
{% if doctrines_list %}
|
{% if doctrines_list %}
|
||||||
{% for a, j in doctrines_list.items %}
|
{% for a, j in doctrines_list.items %}
|
||||||
{% regroup j|dictsort:"FolderName" by FolderName as folder_list %}
|
{% regroup j|dictsort:"FolderName" by FolderName as folder_list %}
|
||||||
{% for FolderName in folder_list %}
|
{% for FolderName in folder_list %}
|
||||||
<div class="col-lg-8">
|
<div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title"><b>{{ FolderName.grouper }}</b></h3>
|
<h3 class="panel-title"><b>{{ FolderName.grouper }}</b></h3>
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
<th class="col-lg-2">Note</th>-->
|
<th class="col-lg-2">Note</th>-->
|
||||||
</tr>
|
</tr>
|
||||||
{% for item in FolderName.list %}
|
{% for item in FolderName.list %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'fleetup:doctrine' item.DoctrineId %}"><img src="https://image.eveonline.com/InventoryType/{{ item.IconId }}_32.png"></a>
|
<a href="{% url 'fleetup:doctrine' item.DoctrineId %}"><img src="https://image.eveonline.com/InventoryType/{{ item.IconId }}_32.png"></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.Name }}
|
{{ item.Name }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
{% include "fleetup/menu.html" %}
|
{% include "fleetup/menu.html" %}
|
||||||
<div class="tab-content">
|
<div class="tab-content row">
|
||||||
<div id="fit" class="tab-pane fade in active">
|
<div id="fit" class="tab-pane fade in active">
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-4">
|
||||||
{% for x, y in fitting_data.items %}
|
{% for x, y in fitting_data.items %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
@@ -18,22 +18,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% for doctrin in y.Doctrines %}
|
{% for doctrin in y.Doctrines %}
|
||||||
<h4>{{ doctrin.Name }}</h4>
|
<div class="clearfix">
|
||||||
<div class="col-lg-12">
|
<h4>{{ doctrin.Name }}</h4>
|
||||||
<p>{% trans "Role in doctrine:" %} {{ doctrin.Role }}</p>
|
<div class="col-lg-12">
|
||||||
</div>
|
<p>{% trans "Role in doctrine:" %} {{ doctrin.Role }}</p>
|
||||||
<div class="col-lg-4">
|
</div>
|
||||||
<p>{% trans "Priority:" %}</p>
|
<div class="col-lg-4">
|
||||||
</div>
|
<p>{% trans "Priority:" %}</p>
|
||||||
<div class="col-lg-8">
|
</div>
|
||||||
<div class="progress">
|
<div class="col-lg-8">
|
||||||
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{{ doctrin.Priority }}" aria-valuemin="0" aria-valuemax="5" style="width: {% widthratio doctrin.Priority 5 100 %}%;">
|
<div class="progress">
|
||||||
{{ doctrin.Priority }}/5
|
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{{ doctrin.Priority }}" aria-valuemin="0" aria-valuemax="5" style="width: {% widthratio doctrin.Priority 5 100 %}%;">
|
||||||
|
{{ doctrin.Priority }}/5
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="pull-right">
|
||||||
<div class="pull-right">
|
<a class="btn btn-primary" href="{% url 'fleetup:doctrine' doctrin.DoctrineId %}">{% trans "See doctrine" %}</a>
|
||||||
<a class="btn btn-primary" href="{% url 'fleetup:doctrine' doctrin.DoctrineId %}">{% trans "See doctrine" %}</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -56,10 +58,10 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for arbit, orbit in doctrines_list.items %}
|
{% for arbit, orbit in doctrines_list.items %}
|
||||||
|
|
||||||
{% for fitting in orbit.Data %}
|
{% for fitting in orbit.Data %}
|
||||||
<a href="{% url 'fleetup:fitting' fitting.FittingId %}" class="list-group-item">
|
<a href="{% url 'fleetup:fitting' fitting.FittingId %}" class="list-group-item">
|
||||||
|
|
||||||
<h4 class="list-group-item-heading">{{ fitting.Name }}<span class="pull-right"><img src="https://image.eveonline.com/InventoryType/{{ fitting.EveTypeId }}_32.png" class="img-circle"></span></h4>
|
<h4 class="list-group-item-heading">{{ fitting.Name }}<span class="pull-right"><img src="https://image.eveonline.com/InventoryType/{{ fitting.EveTypeId }}_32.png" class="img-circle"></span></h4>
|
||||||
<p class="list-group-item-heading">{{ fitting.Role }} - {{ fitting.ShipType }}</p>
|
<p class="list-group-item-heading">{{ fitting.Role }} - {{ fitting.ShipType }}</p>
|
||||||
</a>
|
</a>
|
||||||
@@ -107,8 +109,8 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-4">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h3 class="panel-title">{% trans "EFT/Export" %}</h3>
|
<h3 class="panel-title">{% trans "EFT/Export" %}</h3>
|
||||||
|
|||||||
@@ -20,11 +20,11 @@
|
|||||||
<th class="col-md-2">{% trans "Categories" %}</th>
|
<th class="col-md-2">{% trans "Categories" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for id, fittings in fitting_list %}
|
{% for id, fittings in fitting_list %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'fleetup:fitting' fittings.fitting_id %}"><img src="https://image.eveonline.com/InventoryType/{{ fittings.icon_id }}_32.png"></a>
|
<a href="{% url 'fleetup:fitting' fittings.fitting_id %}"><img src="https://image.eveonline.com/InventoryType/{{ fittings.icon_id }}_32.png"></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ fittings.name }}
|
{{ fittings.name }}
|
||||||
</td>
|
</td>
|
||||||
@@ -38,12 +38,12 @@
|
|||||||
{% load humanize %}{{ fittings.estimated|intword }}
|
{% load humanize %}{{ fittings.estimated|intword }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% for categories in fittings.categories %}
|
{% for categories in fittings.categories %}
|
||||||
{{ categories }},
|
{{ categories }},
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
{% include "fleetup/menu.html" %}
|
{% include "fleetup/menu.html" %}
|
||||||
<div class="panel">
|
<div>
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="active"><a data-toggle="tab" href="#operations">{% trans "Operations" %}</a></li>
|
<li class="active"><a data-toggle="tab" href="#operations">{% trans "Operations" %}</a></li>
|
||||||
<li><a data-toggle="tab" href="#timers">{% trans "Timers" %}</a></li>
|
<li><a data-toggle="tab" href="#timers">{% trans "Timers" %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content row">
|
||||||
<div id="operations" class="tab-pane fade in active">
|
<div id="operations" class="tab-pane fade in active">
|
||||||
<div class="col-lg-7">
|
<div class="col-lg-8">
|
||||||
{% if operations_list %}
|
{% if operations_list %}
|
||||||
{% for subject, start in operations_list %}
|
{% for subject, start in operations_list %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="col-md-6">{{ start.start|date:"l d M H:i" }} <span class="label label-success">{% trans "Eve Time" %}</span></td>
|
<td class="col-md-6">{{ start.start|date:"l d M H:i" }} <span class="label label-success">{% trans "Eve Time" %}</span></td>
|
||||||
|
|
||||||
<td class="col-md-6">{{ start.end|date:"l d M H:i" }} <span class="label label-success">{% trans "Eve Time" %}</span></td>
|
<td class="col-md-6">{{ start.end|date:"l d M H:i" }} <span class="label label-success">{% trans "Eve Time" %}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -39,13 +39,12 @@
|
|||||||
<span id="localtime{{ start.operation_id }}"></span> <span class='label label-success'>Local time</span><br>
|
<span id="localtime{{ start.operation_id }}"></span> <span class='label label-success'>Local time</span><br>
|
||||||
<div id="countdown{{ start.operation_id }}"></div>
|
<div id="countdown{{ start.operation_id }}"></div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="col-md-6"></td>
|
<td class="col-md-6"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<p>{{ start.details }}</p>
|
{{ start.details|linebreaks }}
|
||||||
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<table class="table table-condensed table-striped">
|
<table class="table table-condensed table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-4">{% trans "Location" %}</th>
|
<th class="col-md-4">{% trans "Location" %}</th>
|
||||||
@@ -60,11 +59,11 @@
|
|||||||
<td>
|
<td>
|
||||||
{% if start.doctrine %}
|
{% if start.doctrine %}
|
||||||
{% for doctrine in start.doctrine %}
|
{% for doctrine in start.doctrine %}
|
||||||
|
|
||||||
<a href="{% url 'fleetup:doctrine' doctrine.Id %}" class="label label-success">{{ doctrine.Name }}</a>
|
<a href="{% url 'fleetup:doctrine' doctrine.Id %}" class="label label-success">{{ doctrine.Name }}</a>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">{% trans "TBA" %}</span>
|
<span class="label label-danger">{% trans "TBA" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -81,7 +80,6 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -89,7 +87,7 @@
|
|||||||
<h3>{% trans "There seems to be no Operations in the near future." %}</h3>
|
<h3>{% trans "There seems to be no Operations in the near future." %}</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-4">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h2 class="panel-title">{% trans "Current Eve Time:" %}</h2>
|
<h2 class="panel-title">{% trans "Current Eve Time:" %}</h2>
|
||||||
@@ -158,7 +156,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ type.owner }}
|
{{ type.owner }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ type.notes }}
|
{{ type.notes }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ from .models import GroupRequest
|
|||||||
|
|
||||||
class AuthGroupInlineAdmin(admin.StackedInline):
|
class AuthGroupInlineAdmin(admin.StackedInline):
|
||||||
model = AuthGroup
|
model = AuthGroup
|
||||||
filter_horizontal = ('group_leaders',)
|
filter_horizontal = ('group_leaders', 'states',)
|
||||||
fields = ('description', 'group_leaders', 'internal', 'hidden', 'open', 'public')
|
fields = ('description', 'group_leaders', 'states', 'internal', 'hidden', 'open', 'public')
|
||||||
verbose_name_plural = 'Auth Settings'
|
verbose_name_plural = 'Auth Settings'
|
||||||
verbose_name = ''
|
verbose_name = ''
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
class GroupManager:
|
class GroupManager:
|
||||||
@@ -6,7 +7,12 @@ class GroupManager:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_joinable_groups():
|
def get_joinable_groups(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)
|
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -14,13 +20,35 @@ class GroupManager:
|
|||||||
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user])
|
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def joinable_group(group):
|
def joinable_group(group, state):
|
||||||
"""
|
"""
|
||||||
Check if a group is a user joinable group, i.e.
|
Check if a group is a user/state joinable group, i.e.
|
||||||
not an internal group for Corp, Alliance, Members etc
|
not an internal group for Corp, Alliance, Members etc,
|
||||||
|
or restricted from the user's current state.
|
||||||
:param group: django.contrib.auth.models.Group object
|
:param group: django.contrib.auth.models.Group object
|
||||||
|
:param state: allianceauth.authentication.State object
|
||||||
:return: bool True if its joinable, False otherwise
|
:return: bool True if its joinable, False otherwise
|
||||||
"""
|
"""
|
||||||
|
if len(group.authgroup.states.all()) != 0 and state not in group.authgroup.states.all():
|
||||||
|
return False
|
||||||
|
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 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
|
return not group.authgroup.internal
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
28
allianceauth/groupmanagement/migrations/0009_requestlog.py
Normal file
28
allianceauth/groupmanagement/migrations/0009_requestlog.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 2.0.6 on 2018-06-04 02:45
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0008_alter_user_username_max_length'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('groupmanagement', '0008_remove_authgroup_permissions'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RequestLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('request_type', models.NullBooleanField(default=0)),
|
||||||
|
('request_info', models.CharField(max_length=254)),
|
||||||
|
('action', models.BooleanField(default=0)),
|
||||||
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
|
||||||
|
('request_actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.0.6 on 2018-07-11 00:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0016_ownershiprecord'),
|
||||||
|
('groupmanagement', '0009_requestlog'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='authgroup',
|
||||||
|
name='states',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='States listed here will have the ability to join this group provided they have the proper permissions.', related_name='valid_states', to='authentication.State'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.0.8 on 2018-12-07 08:56
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('groupmanagement', '0010_authgroup_states'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='requestlog',
|
||||||
|
name='date',
|
||||||
|
field=models.DateTimeField(default=datetime.datetime(2018, 12, 7, 8, 56, 33, 846342)),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -3,6 +3,8 @@ from django.contrib.auth.models import User
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from allianceauth.authentication.models import State
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class GroupRequest(models.Model):
|
class GroupRequest(models.Model):
|
||||||
@@ -23,6 +25,38 @@ class GroupRequest(models.Model):
|
|||||||
return self.user.username + ":" + self.group.name
|
return self.user.username + ":" + self.group.name
|
||||||
|
|
||||||
|
|
||||||
|
class RequestLog(models.Model):
|
||||||
|
request_type = models.NullBooleanField(default=0)
|
||||||
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
|
request_info = models.CharField(max_length=254)
|
||||||
|
action = models.BooleanField(default=0)
|
||||||
|
request_actor = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
date = models.DateTimeField(auto_now_add=datetime.utcnow())
|
||||||
|
|
||||||
|
def requestor(self):
|
||||||
|
return self.request_info.split(":")[0]
|
||||||
|
|
||||||
|
def type_to_str(self):
|
||||||
|
if self.request_type is None:
|
||||||
|
return "Removed"
|
||||||
|
elif self.request_type is True:
|
||||||
|
return "Leave"
|
||||||
|
elif self.request_type is False:
|
||||||
|
return "Join"
|
||||||
|
|
||||||
|
def action_to_str(self):
|
||||||
|
if self.action is True:
|
||||||
|
return "Accept"
|
||||||
|
elif self.action is False:
|
||||||
|
return "Reject"
|
||||||
|
|
||||||
|
def req_char(self):
|
||||||
|
usr = self.requestor()
|
||||||
|
user = User.objects.get(username=usr)
|
||||||
|
return user.profile.main_character
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AuthGroup(models.Model):
|
class AuthGroup(models.Model):
|
||||||
"""
|
"""
|
||||||
Extends Django Group model with a one-to-one field
|
Extends Django Group model with a one-to-one field
|
||||||
@@ -65,6 +99,10 @@ class AuthGroup(models.Model):
|
|||||||
"specifically. Use the auth.group_management permission to allow "
|
"specifically. Use the auth.group_management permission to allow "
|
||||||
"a user to manage all groups.")
|
"a user to manage all groups.")
|
||||||
|
|
||||||
|
states = models.ManyToManyField(State, related_name='valid_states', blank=True,
|
||||||
|
help_text="States listed here will have the ability to join this group provided "
|
||||||
|
"they have the proper permissions.")
|
||||||
|
|
||||||
description = models.CharField(max_length=512, blank=True, help_text="Description of the group shown to users.")
|
description = models.CharField(max_length=512, blank=True, help_text="Description of the group shown to users.")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{% extends "allianceauth/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block page_title %}{{ group }} {% trans "Audit Log" %}{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<br>
|
||||||
|
{% include 'groupmanagement/menu.html' %}
|
||||||
|
<div>
|
||||||
|
<h3>{{ group }} Audit Log</h3>
|
||||||
|
<p> All times displayed are EVE/UTC.</p>
|
||||||
|
{% if entries %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped" id="log-entries">
|
||||||
|
<thead>
|
||||||
|
<th class="text-center" scope="col">{% trans "Date/Time" %}</th>
|
||||||
|
<th class="text-center" scope="col">{% trans "Requestor" %}</th>
|
||||||
|
<th class="text-center" scope="col">{% trans "Main Character" %}</th>
|
||||||
|
<th class="text-center" scope="col">{% trans "Group" %}</th>
|
||||||
|
<th class="text-center" scope="col">{% trans "Type" %}</th>
|
||||||
|
<th class="text-center" scope="col">{% trans "Action" %}</th>
|
||||||
|
<th class="text-center" scope="col">{% trans "Actor" %}</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for entry in entries %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ entry.date }}</td>
|
||||||
|
<td class="text-center">{{ entry.requestor }}</td>
|
||||||
|
<td class="text-center">{{ entry.req_char }}</td>
|
||||||
|
<td class="text-center">{{ entry.group }}</td>
|
||||||
|
<td class="text-center">{{ entry.type_to_str }}</td>
|
||||||
|
{% if entry.request_type is None %}
|
||||||
|
<td class="text-center"> Removed</td>
|
||||||
|
{% else %}
|
||||||
|
<td class="text-center">{{ entry.action_to_str }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td class="text-center">{{ entry.request_actor }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning text-center">{% trans "No entries found for this group." %}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block extra_javascript %}
|
||||||
|
{% include 'bundles/datatables-js.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include 'bundles/datatables-css.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block extra_script %}
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#log-entries').DataTable();
|
||||||
|
});
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -41,6 +41,9 @@
|
|||||||
title="{% trans "View Members" %}">
|
title="{% trans "View Members" %}">
|
||||||
<i class="glyphicon glyphicon-eye-open"></i>
|
<i class="glyphicon glyphicon-eye-open"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
|
||||||
|
<i class="glyphicon glyphicon-list-alt"></i>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ url
|
|||||||
{% for g in groups %}
|
{% for g in groups %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ g.group.name }}</td>
|
<td class="text-center">{{ g.group.name }}</td>
|
||||||
<td class="text-center">{{ g.group.authgroup.description }}</td>
|
<td class="text-center">{{ g.group.authgroup.description|urlize }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if g.group in user.groups.all %}
|
{% if g.group in user.groups.all %}
|
||||||
{% if not g.request %}
|
{% if not g.request %}
|
||||||
@@ -32,9 +32,15 @@ url
|
|||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif not g.request %}
|
{% elif not g.request %}
|
||||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
|
{% if g.group.authgroup.open %}
|
||||||
{% trans "Request" %}
|
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
|
||||||
</a>
|
{% trans "Join" %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
|
||||||
|
{% trans "Request" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="button" class="btn btn-primary" disabled>
|
<button type="button" class="btn btn-primary" disabled>
|
||||||
{{ g.request.status }}
|
{{ g.request.status }}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ urlpatterns = [
|
|||||||
name='membership'),
|
name='membership'),
|
||||||
url(r'^membership/(\w+)/$', views.group_membership_list,
|
url(r'^membership/(\w+)/$', views.group_membership_list,
|
||||||
name='membership_list'),
|
name='membership_list'),
|
||||||
|
url(r'^membership/(\w+)/audit/$', views.group_membership_audit, name="audit_log"),
|
||||||
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
|
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
|
||||||
name='membership_remove'),
|
name='membership_remove'),
|
||||||
url(r'^request_add/(\w+)', views.group_request_add,
|
url(r'^request_add/(\w+)', views.group_request_add,
|
||||||
|
|||||||
@@ -5,15 +5,18 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.contrib.auth.decorators import user_passes_test
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||||
|
from django.core.paginator import Paginator, EmptyPage
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from .managers import GroupManager
|
from .managers import GroupManager
|
||||||
from .models import GroupRequest
|
from .models import GroupRequest, RequestLog
|
||||||
|
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ def group_membership(request):
|
|||||||
# Get all open and closed groups
|
# Get all open and closed groups
|
||||||
if GroupManager.has_management_permission(request.user):
|
if GroupManager.has_management_permission(request.user):
|
||||||
# Full access
|
# Full access
|
||||||
groups = GroupManager.get_joinable_groups()
|
groups = GroupManager.get_all_non_internal_groups()
|
||||||
else:
|
else:
|
||||||
# Group leader specific
|
# Group leader specific
|
||||||
groups = GroupManager.get_group_leaders_groups(request.user)
|
groups = GroupManager.get_group_leaders_groups(request.user)
|
||||||
@@ -65,6 +68,31 @@ def group_membership(request):
|
|||||||
return render(request, 'groupmanagement/groupmembership.html', context=render_items)
|
return render(request, 'groupmanagement/groupmembership.html', context=render_items)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@user_passes_test(GroupManager.can_manage_groups)
|
||||||
|
def group_membership_audit(request, group_id):
|
||||||
|
logger.debug("group_management_audit called by user %s" % request.user)
|
||||||
|
group = get_object_or_404(Group, id=group_id)
|
||||||
|
try:
|
||||||
|
|
||||||
|
# Check its a joinable group i.e. not corp or internal
|
||||||
|
# And the user has permission to manage it
|
||||||
|
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||||
|
logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
|
||||||
|
(request.user, group_id))
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
raise Http404("Group does not exist")
|
||||||
|
render_items = {'group': group.name}
|
||||||
|
entries = RequestLog.objects.filter(group=group).order_by('-date')
|
||||||
|
render_items['entries'] = entries
|
||||||
|
|
||||||
|
return render(request, 'groupmanagement/audit.html', context=render_items)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(GroupManager.can_manage_groups)
|
@user_passes_test(GroupManager.can_manage_groups)
|
||||||
def group_membership_list(request, group_id):
|
def group_membership_list(request, group_id):
|
||||||
@@ -74,7 +102,7 @@ def group_membership_list(request, group_id):
|
|||||||
|
|
||||||
# Check its a joinable group i.e. not corp or internal
|
# Check its a joinable group i.e. not corp or internal
|
||||||
# And the user has permission to manage it
|
# And the user has permission to manage it
|
||||||
if not GroupManager.joinable_group(group) or not GroupManager.can_manage_group(request.user, group):
|
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||||
logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
|
logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
|
||||||
(request.user, group_id))
|
(request.user, group_id))
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
@@ -105,13 +133,16 @@ def group_membership_remove(request, group_id, user_id):
|
|||||||
try:
|
try:
|
||||||
# Check its a joinable group i.e. not corp or internal
|
# Check its a joinable group i.e. not corp or internal
|
||||||
# And the user has permission to manage it
|
# And the user has permission to manage it
|
||||||
if not GroupManager.joinable_group(group) or not GroupManager.can_manage_group(request.user, group):
|
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||||
logger.warning("User %s attempted to remove a user from group %s but permission was denied" % (request.user,
|
logger.warning("User %s attempted to remove a user from group %s but permission was denied" % (request.user,
|
||||||
group_id))
|
group_id))
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = group.user_set.get(id=user_id)
|
user = group.user_set.get(id=user_id)
|
||||||
|
request_info = user.username + ":" + group.name
|
||||||
|
log = RequestLog(request_type=None,group=group,request_info=request_info,action=1,request_actor=request.user)
|
||||||
|
log.save()
|
||||||
# Remove group from user
|
# Remove group from user
|
||||||
user.groups.remove(group)
|
user.groups.remove(group)
|
||||||
logger.info("User %s removed user %s from group %s" % (request.user, user, group))
|
logger.info("User %s removed user %s from group %s" % (request.user, user, group))
|
||||||
@@ -133,12 +164,14 @@ def group_accept_request(request, group_request_id):
|
|||||||
try:
|
try:
|
||||||
group, created = Group.objects.get_or_create(name=group_request.group.name)
|
group, created = Group.objects.get_or_create(name=group_request.group.name)
|
||||||
|
|
||||||
if not GroupManager.joinable_group(group_request.group) or \
|
if not GroupManager.joinable_group(group_request.group, group_request.user.profile.state) or \
|
||||||
not GroupManager.can_manage_group(request.user, group_request.group):
|
not GroupManager.can_manage_group(request.user, group_request.group):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
group_request.user.groups.add(group)
|
group_request.user.groups.add(group)
|
||||||
group_request.user.save()
|
group_request.user.save()
|
||||||
|
log = RequestLog(request_type=group_request.leave_request,group=group,request_info=group_request.__str__(),action=1,request_actor=request.user)
|
||||||
|
log.save()
|
||||||
group_request.delete()
|
group_request.delete()
|
||||||
logger.info("User %s accepted group request from user %s to group %s" % (
|
logger.info("User %s accepted group request from user %s to group %s" % (
|
||||||
request.user, group_request.user, group_request.group.name))
|
request.user, group_request.user, group_request.group.name))
|
||||||
@@ -172,6 +205,8 @@ def group_reject_request(request, group_request_id):
|
|||||||
if group_request:
|
if group_request:
|
||||||
logger.info("User %s rejected group request from user %s to group %s" % (
|
logger.info("User %s rejected group request from user %s to group %s" % (
|
||||||
request.user, group_request.user, group_request.group.name))
|
request.user, group_request.user, group_request.group.name))
|
||||||
|
log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=0,request_actor=request.user)
|
||||||
|
log.save()
|
||||||
group_request.delete()
|
group_request.delete()
|
||||||
notify(group_request.user, "Group Application Rejected", level="danger",
|
notify(group_request.user, "Group Application Rejected", level="danger",
|
||||||
message="Your application to %s has been rejected." % group_request.group)
|
message="Your application to %s has been rejected." % group_request.group)
|
||||||
@@ -204,6 +239,8 @@ def group_leave_accept_request(request, group_request_id):
|
|||||||
group, created = Group.objects.get_or_create(name=group_request.group.name)
|
group, created = Group.objects.get_or_create(name=group_request.group.name)
|
||||||
group_request.user.groups.remove(group)
|
group_request.user.groups.remove(group)
|
||||||
group_request.user.save()
|
group_request.user.save()
|
||||||
|
log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=1,request_actor=request.user)
|
||||||
|
log.save()
|
||||||
group_request.delete()
|
group_request.delete()
|
||||||
logger.info("User %s accepted group leave request from user %s to group %s" % (
|
logger.info("User %s accepted group leave request from user %s to group %s" % (
|
||||||
request.user, group_request.user, group_request.group.name))
|
request.user, group_request.user, group_request.group.name))
|
||||||
@@ -236,6 +273,8 @@ def group_leave_reject_request(request, group_request_id):
|
|||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
if group_request:
|
if group_request:
|
||||||
|
log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=0,request_actor=request.user)
|
||||||
|
log.save()
|
||||||
group_request.delete()
|
group_request.delete()
|
||||||
logger.info("User %s rejected group leave request from user %s for group %s" % (
|
logger.info("User %s rejected group leave request from user %s for group %s" % (
|
||||||
request.user, group_request.user, group_request.group.name))
|
request.user, group_request.user, group_request.group.name))
|
||||||
@@ -262,7 +301,7 @@ def groups_view(request):
|
|||||||
logger.debug("groups_view called by user %s" % request.user)
|
logger.debug("groups_view called by user %s" % request.user)
|
||||||
groups = []
|
groups = []
|
||||||
|
|
||||||
group_query = GroupManager.get_joinable_groups()
|
group_query = GroupManager.get_joinable_groups(request.user.profile.state)
|
||||||
|
|
||||||
if not request.user.has_perm('groupmanagement.request_groups'):
|
if not request.user.has_perm('groupmanagement.request_groups'):
|
||||||
# Filter down to public groups only for non-members
|
# Filter down to public groups only for non-members
|
||||||
@@ -284,11 +323,18 @@ def groups_view(request):
|
|||||||
def group_request_add(request, group_id):
|
def group_request_add(request, group_id):
|
||||||
logger.debug("group_request_add called by user %s for group id %s" % (request.user, group_id))
|
logger.debug("group_request_add called by user %s for group id %s" % (request.user, group_id))
|
||||||
group = Group.objects.get(id=group_id)
|
group = Group.objects.get(id=group_id)
|
||||||
if not GroupManager.joinable_group(group):
|
state = request.user.profile.state
|
||||||
|
if not GroupManager.joinable_group(group, state):
|
||||||
logger.warning("User %s attempted to join group id %s but it is not a joinable group" %
|
logger.warning("User %s attempted to join group id %s but it is not a joinable group" %
|
||||||
(request.user, group_id))
|
(request.user, group_id))
|
||||||
messages.warning(request, _("You cannot join that group"))
|
messages.warning(request, _("You cannot join that group"))
|
||||||
return redirect('groupmanagement:groups')
|
return redirect('groupmanagement:groups')
|
||||||
|
if group in request.user.groups.all():
|
||||||
|
# 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.")
|
||||||
|
return redirect('groupmanagement:groups')
|
||||||
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
||||||
# Does not have the required permission, trying to join a non-public group
|
# Does not have the required permission, trying to join a non-public group
|
||||||
logger.warning("User %s attempted to join group id %s but it is not a public group" %
|
logger.warning("User %s attempted to join group id %s but it is not a public group" %
|
||||||
@@ -299,6 +345,11 @@ def group_request_add(request, group_id):
|
|||||||
logger.info("%s joining %s as is an open group" % (request.user, group))
|
logger.info("%s joining %s as is an open group" % (request.user, group))
|
||||||
request.user.groups.add(group)
|
request.user.groups.add(group)
|
||||||
return redirect("groupmanagement:groups")
|
return redirect("groupmanagement:groups")
|
||||||
|
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.")
|
||||||
|
return redirect("groupmanagement:groups")
|
||||||
grouprequest = GroupRequest()
|
grouprequest = GroupRequest()
|
||||||
grouprequest.status = _('Pending')
|
grouprequest.status = _('Pending')
|
||||||
grouprequest.group = group
|
grouprequest.group = group
|
||||||
@@ -314,7 +365,7 @@ def group_request_add(request, group_id):
|
|||||||
def group_request_leave(request, group_id):
|
def group_request_leave(request, group_id):
|
||||||
logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id))
|
logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id))
|
||||||
group = Group.objects.get(id=group_id)
|
group = Group.objects.get(id=group_id)
|
||||||
if not GroupManager.joinable_group(group):
|
if not GroupManager.check_internal_group(group):
|
||||||
logger.warning("User %s attempted to leave group id %s but it is not a joinable group" %
|
logger.warning("User %s attempted to leave group id %s but it is not a joinable group" %
|
||||||
(request.user, group_id))
|
(request.user, group_id))
|
||||||
messages.warning(request, _("You cannot leave that group"))
|
messages.warning(request, _("You cannot leave that group"))
|
||||||
@@ -328,6 +379,15 @@ def group_request_leave(request, group_id):
|
|||||||
logger.info("%s leaving %s as is an open group" % (request.user, group))
|
logger.info("%s leaving %s as is an open group" % (request.user, group))
|
||||||
request.user.groups.remove(group)
|
request.user.groups.remove(group)
|
||||||
return redirect("groupmanagement:groups")
|
return redirect("groupmanagement:groups")
|
||||||
|
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.")
|
||||||
|
return redirect("groupmanagement:groups")
|
||||||
|
if getattr(settings, 'AUTO_LEAVE', False):
|
||||||
|
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))
|
||||||
|
request.user.groups.remove(group)
|
||||||
|
return redirect('groupmanagement:groups')
|
||||||
grouprequest = GroupRequest()
|
grouprequest = GroupRequest()
|
||||||
grouprequest.status = _('Pending')
|
grouprequest.status = _('Pending')
|
||||||
grouprequest.group = group
|
grouprequest.group = group
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.hrapplications import urls
|
from allianceauth.hrapplications import urls
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ from allianceauth.hrapplications import urls
|
|||||||
class ApplicationsMenu(MenuItemHook):
|
class ApplicationsMenu(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(self,
|
||||||
'Applications',
|
_('Applications'),
|
||||||
'fa fa-file-o fa-fw',
|
'fa fa-file-o fa-fw',
|
||||||
'hrapplications:index',
|
'hrapplications:index',
|
||||||
navactive=['hrapplications:'])
|
navactive=['hrapplications:'])
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<span class="glyphicon glyphicon-eye-open"></span>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.hrapplications.delete_application %}
|
{% if perms.hrapplications.delete_application %}
|
||||||
<a href="(% url 'hrapplications:remove' app.id %}"
|
<a href="{% url 'hrapplications:remove' app.id %}"
|
||||||
class="btn btn-danger">
|
class="btn btn-danger">
|
||||||
<span class="glyphicon glyphicon-remove"></span>
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ urlpatterns = [
|
|||||||
name="create_view"),
|
name="create_view"),
|
||||||
url(r'^remove/(\w+)', views.hr_application_remove,
|
url(r'^remove/(\w+)', views.hr_application_remove,
|
||||||
name="remove"),
|
name="remove"),
|
||||||
url(r'view/(\w+)', views.hr_application_view,
|
url(r'^view/(\w+)', views.hr_application_view,
|
||||||
name="view"),
|
name="view"),
|
||||||
url(r'personal/view/(\w+)', views.hr_application_personal_view,
|
url(r'^personal/view/(\w+)', views.hr_application_personal_view,
|
||||||
name="personal_view"),
|
name="personal_view"),
|
||||||
url(r'personal/removal/(\w+)',
|
url(r'^personal/removal/(\w+)',
|
||||||
views.hr_application_personal_removal,
|
views.hr_application_personal_removal,
|
||||||
name="personal_removal"),
|
name="personal_removal"),
|
||||||
url(r'approve/(\w+)', views.hr_application_approve,
|
url(r'^approve/(\w+)', views.hr_application_approve,
|
||||||
name="approve"),
|
name="approve"),
|
||||||
url(r'reject/(\w+)', views.hr_application_reject,
|
url(r'^reject/(\w+)', views.hr_application_reject,
|
||||||
name="reject"),
|
name="reject"),
|
||||||
url(r'search/', views.hr_application_search,
|
url(r'^search/', views.hr_application_search,
|
||||||
name="search"),
|
name="search"),
|
||||||
url(r'mark_in_progress/(\w+)', views.hr_application_mark_in_progress,
|
url(r'^mark_in_progress/(\w+)', views.hr_application_mark_in_progress,
|
||||||
name="mark_in_progress"),
|
name="mark_in_progress"),
|
||||||
]
|
]
|
||||||
|
|||||||
BIN
allianceauth/locale/de/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/de/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/es/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/es/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2043
allianceauth/locale/es/LC_MESSAGES/django.po
Normal file
2043
allianceauth/locale/es/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
|||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from . import urls
|
from . import urls
|
||||||
|
|
||||||
|
|
||||||
class OpTimerboardMenu(MenuItemHook):
|
class OpTimerboardMenu(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self, 'Fleet Operations',
|
MenuItemHook.__init__(self, _('Fleet Operations'),
|
||||||
'fa fa-exclamation fa-fw',
|
'fa fa-exclamation fa-fw',
|
||||||
'optimer:view',
|
'optimer:view',
|
||||||
navactive=['optimer:'])
|
navactive=['optimer:'])
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
<div class="col-lg-12 text-center row">
|
<div class="col-lg-12 text-center row">
|
||||||
<div class="label label-info text-left">
|
<div class="label label-info text-left">
|
||||||
<b>{% trans "Current Eve Time:" %} </b>
|
<b>{% trans "Current Eve Time:" %} </b>
|
||||||
</div><div class="label label-info text-left" id="current-time"></div>
|
</div>
|
||||||
|
<strong class="label label-info text-left" id="current-time"></strong>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateClock() {
|
function updateClock() {
|
||||||
document.getElementById("current-time").innerHTML = "<b>" + moment.utc().format('LLLL') + "</b>";
|
document.getElementById("current-time").innerHTML = getCurrentEveTimeString();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ app = Celery('{{ project_name }}')
|
|||||||
# Using a string here means the worker don't have to serialize
|
# Using a string here means the worker don't have to serialize
|
||||||
# the configuration object to child processes.
|
# the configuration object to child processes.
|
||||||
app.config_from_object('django.conf:settings')
|
app.config_from_object('django.conf:settings')
|
||||||
|
app.conf.ONCE = {
|
||||||
|
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||||
|
'settings': {}
|
||||||
|
}
|
||||||
|
|
||||||
# Load task modules from all registered Django app configs.
|
# Load task modules from all registered Django app configs.
|
||||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
|||||||
CELERYBEAT_SCHEDULE = {
|
CELERYBEAT_SCHEDULE = {
|
||||||
'esi_cleanup_callbackredirect': {
|
'esi_cleanup_callbackredirect': {
|
||||||
'task': 'esi.tasks.cleanup_callbackredirect',
|
'task': 'esi.tasks.cleanup_callbackredirect',
|
||||||
'schedule': crontab(hour='*/4'),
|
'schedule': crontab(minute=0, hour='*/4'),
|
||||||
},
|
},
|
||||||
'esi_cleanup_token': {
|
'esi_cleanup_token': {
|
||||||
'task': 'esi.tasks.cleanup_token',
|
'task': 'esi.tasks.cleanup_token',
|
||||||
'schedule': crontab(day_of_month='*/1'),
|
'schedule': crontab(minute=0, hour=0),
|
||||||
},
|
},
|
||||||
'run_model_update': {
|
'run_model_update': {
|
||||||
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
||||||
@@ -53,7 +53,7 @@ CELERYBEAT_SCHEDULE = {
|
|||||||
},
|
},
|
||||||
'check_all_character_ownership': {
|
'check_all_character_ownership': {
|
||||||
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
||||||
'schedule': crontab(hour='*/4'),
|
'schedule': crontab(minute=0, hour='*/4'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +82,7 @@ ugettext = lambda s: s
|
|||||||
LANGUAGES = (
|
LANGUAGES = (
|
||||||
('en', ugettext('English')),
|
('en', ugettext('English')),
|
||||||
('de', ugettext('German')),
|
('de', ugettext('German')),
|
||||||
|
('es', ugettext('Spanish')),
|
||||||
)
|
)
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
@@ -166,7 +167,6 @@ CACHES = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECRET_KEY = 'this is a very bad secret key you should change'
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
@@ -194,6 +194,8 @@ LOGIN_TOKEN_SCOPES = ['publicData']
|
|||||||
# number of days email verification links are valid for
|
# number of days email verification links are valid for
|
||||||
ACCOUNT_ACTIVATION_DAYS = 1
|
ACCOUNT_ACTIVATION_DAYS = 1
|
||||||
|
|
||||||
|
ESI_API_URL = 'https://esi.evetech.net/'
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
@@ -235,5 +237,9 @@ LOGGING = {
|
|||||||
'handlers': ['log_file', 'console'],
|
'handlers': ['log_file', 'console'],
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
},
|
},
|
||||||
|
'esi': {
|
||||||
|
'handlers': ['log_file', 'console'],
|
||||||
|
'level': 'DEBUG',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ DATABASES['default'] = {
|
|||||||
'PASSWORD': '',
|
'PASSWORD': '',
|
||||||
'HOST': '127.0.0.1',
|
'HOST': '127.0.0.1',
|
||||||
'PORT': '3306',
|
'PORT': '3306',
|
||||||
|
'OPTIONS': {'charset': 'utf8mb4'},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Register an application at https://developers.eveonline.com for Authentication
|
# Register an application at https://developers.eveonline.com for Authentication
|
||||||
@@ -41,10 +42,13 @@ ESI_SSO_CLIENT_ID = ''
|
|||||||
ESI_SSO_CLIENT_SECRET = ''
|
ESI_SSO_CLIENT_SECRET = ''
|
||||||
ESI_SSO_CALLBACK_URL = ''
|
ESI_SSO_CALLBACK_URL = ''
|
||||||
|
|
||||||
# Emails are validated before new users can log in.
|
# By default emails are validated before new users can log in.
|
||||||
# It's recommended to use a free service like SparkPost or Mailgun to send email.
|
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
|
||||||
# https://www.sparkpost.com/docs/integrations/django/
|
# https://www.sparkpost.com/docs/integrations/django/
|
||||||
|
# https://elasticemail.com/resources/settings/smtp-api/
|
||||||
# Set the default from email to something like 'noreply@example.com'
|
# Set the default from email to something like 'noreply@example.com'
|
||||||
|
# Email validation can be turned off by uncommenting the line below. This can break some services.
|
||||||
|
# REGISTRATION_VERIFY_EMAIL = False
|
||||||
EMAIL_HOST = ''
|
EMAIL_HOST = ''
|
||||||
EMAIL_PORT = 587
|
EMAIL_PORT = 587
|
||||||
EMAIL_HOST_USER = ''
|
EMAIL_HOST_USER = ''
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
|
|
||||||
@@ -160,15 +159,12 @@ class NameFormatter:
|
|||||||
'corp_id': getattr(main_char, 'corporation_id', None),
|
'corp_id': getattr(main_char, 'corporation_id', None),
|
||||||
'alliance_name': getattr(main_char, 'alliance_name', None),
|
'alliance_name': getattr(main_char, 'alliance_name', None),
|
||||||
'alliance_id': getattr(main_char, 'alliance_id', None),
|
'alliance_id': getattr(main_char, 'alliance_id', None),
|
||||||
|
'alliance_ticker': getattr(main_char, 'alliance_ticker', None),
|
||||||
'username': self.user.username,
|
'username': self.user.username,
|
||||||
}
|
}
|
||||||
|
|
||||||
if main_char is not None and 'alliance_ticker' in self.string_formatter:
|
format_data['alliance_or_corp_name'] = format_data['alliance_name'] or format_data['corp_name']
|
||||||
# Reduces db lookups
|
format_data['alliance_or_corp_ticker'] = format_data['alliance_ticker'] or format_data['corp_ticker']
|
||||||
try:
|
|
||||||
format_data['alliance_ticker'] = getattr(getattr(main_char, 'alliance', None), 'alliance_ticker', None)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
format_data['alliance_ticker'] = None
|
|
||||||
return format_data
|
return format_data
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ SCOPES = [
|
|||||||
'guilds.join',
|
'guilds.join',
|
||||||
]
|
]
|
||||||
|
|
||||||
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # 2 hours default
|
GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # 2 hours default
|
||||||
|
|
||||||
|
|
||||||
class DiscordApiException(Exception):
|
class DiscordApiException(Exception):
|
||||||
@@ -204,7 +204,7 @@ class DiscordOAuthManager:
|
|||||||
'access_token': token,
|
'access_token': token,
|
||||||
}
|
}
|
||||||
if nickname:
|
if nickname:
|
||||||
data['nick'] = nickname
|
data['nick'] = DiscordOAuthManager._sanitize_name(nickname)
|
||||||
custom_headers['authorization'] = 'Bot ' + settings.DISCORD_BOT_TOKEN
|
custom_headers['authorization'] = 'Bot ' + settings.DISCORD_BOT_TOKEN
|
||||||
r = requests.put(path, headers=custom_headers, json=data)
|
r = requests.put(path, headers=custom_headers, json=data)
|
||||||
logger.debug("Got status code %s after joining Discord server" % r.status_code)
|
logger.debug("Got status code %s after joining Discord server" % r.status_code)
|
||||||
@@ -219,22 +219,18 @@ class DiscordOAuthManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
@api_backoff
|
@api_backoff
|
||||||
def update_nickname(user_id, nickname):
|
def update_nickname(user_id, nickname):
|
||||||
try:
|
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||||
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
data = {'nick': nickname}
|
||||||
data = {'nick': nickname}
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
r = requests.patch(path, headers=custom_headers, json=data)
|
||||||
r = requests.patch(path, headers=custom_headers, json=data)
|
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
|
||||||
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
|
r.status_code, user_id, nickname))
|
||||||
r.status_code, user_id, nickname))
|
if r.status_code == 404:
|
||||||
if r.status_code == 404:
|
logger.warn("Discord user ID %s could not be found in server." % user_id)
|
||||||
logger.warn("Discord user ID %s could not be found in server." % user_id)
|
|
||||||
return True
|
|
||||||
r.raise_for_status()
|
|
||||||
return True
|
return True
|
||||||
except:
|
r.raise_for_status()
|
||||||
logger.exception("Failed to set nickname for Discord user ID %s (%s)" % (user_id, nickname))
|
return True
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_user(user_id):
|
def delete_user(user_id):
|
||||||
@@ -301,13 +297,38 @@ class DiscordOAuthManager:
|
|||||||
def _create_group(name):
|
def _create_group(name):
|
||||||
return DiscordOAuthManager.__generate_role(name)
|
return DiscordOAuthManager.__generate_role(name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_user(user_id):
|
||||||
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||||
|
r = requests.get(path, headers=custom_headers)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_user_roles(user_id):
|
||||||
|
user = DiscordOAuthManager._get_user(user_id)
|
||||||
|
return user['roles']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _modify_user_role(user_id, role_id, method):
|
||||||
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id) + "/roles/" + str(
|
||||||
|
role_id)
|
||||||
|
r = getattr(requests, method)(path, headers=custom_headers)
|
||||||
|
r.raise_for_status()
|
||||||
|
logger.debug("%s role %s for user %s" % (method, role_id, user_id))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@api_backoff
|
@api_backoff
|
||||||
def update_groups(user_id, groups):
|
def update_groups(user_id, groups):
|
||||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
|
||||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups]
|
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups]
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
user_group_ids = DiscordOAuthManager._get_user_roles(user_id)
|
||||||
data = {'roles': group_ids}
|
for g in group_ids:
|
||||||
r = requests.patch(path, headers=custom_headers, json=data)
|
if g not in user_group_ids:
|
||||||
logger.debug("Received status code %s after setting user roles" % r.status_code)
|
DiscordOAuthManager._modify_user_role(user_id, g, 'put')
|
||||||
r.raise_for_status()
|
time.sleep(1) # we're gonna be hammering the API here
|
||||||
|
for g in user_group_ids:
|
||||||
|
if g not in group_ids:
|
||||||
|
DiscordOAuthManager._modify_user_role(user_id, g, 'delete')
|
||||||
|
time.sleep(1)
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='discorduser',
|
name='discorduser',
|
||||||
options={'permissions': (('access_discord', 'Can access the Discord service'),)},
|
options={'permissions': (('access_discord', 'Can access the Discord service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from requests.exceptions import HTTPError
|
|||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import DiscordOAuthManager, DiscordApiBackoff
|
from .manager import DiscordOAuthManager, DiscordApiBackoff
|
||||||
from .models import DiscordUser
|
from .models import DiscordUser
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -58,8 +59,8 @@ class DiscordTasks:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name='discord.update_groups')
|
@shared_task(bind=True, name='discord.update_groups', base=QueueOnce)
|
||||||
def update_groups(task_self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating discord groups for user %s" % user)
|
logger.debug("Updating discord groups for user %s" % user)
|
||||||
if DiscordTasks.has_account(user):
|
if DiscordTasks.has_account(user):
|
||||||
@@ -70,7 +71,7 @@ class DiscordTasks:
|
|||||||
except DiscordApiBackoff as bo:
|
except DiscordApiBackoff as bo:
|
||||||
logger.info("Discord group sync API back off for %s, "
|
logger.info("Discord group sync API back off for %s, "
|
||||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
raise self.retry(countdown=bo.retry_after_seconds)
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
if e.response.status_code == 404:
|
if e.response.status_code == 404:
|
||||||
try:
|
try:
|
||||||
@@ -81,9 +82,9 @@ class DiscordTasks:
|
|||||||
finally:
|
finally:
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if task_self:
|
if self:
|
||||||
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)
|
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)
|
||||||
raise task_self.retry(countdown=60 * 10)
|
raise self.retry(countdown=60 * 10)
|
||||||
else:
|
else:
|
||||||
# Rethrow
|
# Rethrow
|
||||||
raise e
|
raise e
|
||||||
@@ -99,8 +100,8 @@ class DiscordTasks:
|
|||||||
DiscordTasks.update_groups.delay(discord_user.user.pk)
|
DiscordTasks.update_groups.delay(discord_user.user.pk)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name='discord.update_nickname')
|
@shared_task(bind=True, name='discord.update_nickname', base=QueueOnce)
|
||||||
def update_nickname(task_self, pk):
|
def update_nickname(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating discord nickname for user %s" % user)
|
logger.debug("Updating discord nickname for user %s" % user)
|
||||||
if DiscordTasks.has_account(user):
|
if DiscordTasks.has_account(user):
|
||||||
@@ -112,11 +113,11 @@ class DiscordTasks:
|
|||||||
except DiscordApiBackoff as bo:
|
except DiscordApiBackoff as bo:
|
||||||
logger.info("Discord nickname update API back off for %s, "
|
logger.info("Discord nickname update API back off for %s, "
|
||||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
raise self.retry(countdown=bo.retry_after_seconds)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if task_self:
|
if self:
|
||||||
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
||||||
raise task_self.retry(countdown=60 * 10)
|
raise self.retry(countdown=60 * 10)
|
||||||
else:
|
else:
|
||||||
# Rethrow
|
# Rethrow
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
@@ -327,54 +327,54 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
# Assert
|
# Assert
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups(self, group_cache, m):
|
def test_update_groups(self, group_cache, user_roles, m):
|
||||||
# Arrange
|
# Arrange
|
||||||
groups = ['Member', 'Blue', 'SpecialGroup']
|
groups = ['Member', 'Blue', 'SpecialGroup']
|
||||||
|
|
||||||
group_cache.return_value = [{'id': 111, 'name': 'Member'},
|
group_cache.return_value = [{'id': '111', 'name': 'Member'},
|
||||||
{'id': 222, 'name': 'Blue'},
|
{'id': '222', 'name': 'Blue'},
|
||||||
{'id': 333, 'name': 'SpecialGroup'},
|
{'id': '333', 'name': 'SpecialGroup'},
|
||||||
{'id': 444, 'name': 'NotYourGroup'}]
|
{'id': '444', 'name': 'NotYourGroup'}]
|
||||||
|
user_roles.return_value = ['444']
|
||||||
|
|
||||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
user_id = 12345
|
user_id = 12345
|
||||||
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
user_request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
||||||
|
group_request_urls = ['{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, g['id']) for g in group_cache.return_value]
|
||||||
|
|
||||||
m.patch(request_url,
|
m.patch(user_request_url, request_headers=headers)
|
||||||
request_headers=headers)
|
[m.put(url, request_headers=headers) for url in group_request_urls[:-1]]
|
||||||
|
m.delete(group_request_urls[-1], request_headers=headers)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
DiscordOAuthManager.update_groups(user_id, groups)
|
DiscordOAuthManager.update_groups(user_id, groups)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(len(m.request_history), 1, 'Must be one HTTP call made')
|
self.assertEqual(len(m.request_history), 4, 'Must be 4 HTTP calls made')
|
||||||
history = json.loads(m.request_history[0].text)
|
|
||||||
self.assertIn('roles', history, "'The request must send JSON object with the 'roles' key")
|
|
||||||
self.assertIn(111, history['roles'], 'The group id 111 must be added to the request')
|
|
||||||
self.assertIn(222, history['roles'], 'The group id 222 must be added to the request')
|
|
||||||
self.assertIn(333, history['roles'], 'The group id 333 must be added to the request')
|
|
||||||
self.assertNotIn(444, history['roles'], 'The group id 444 must NOT be added to the request')
|
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.cache')
|
@mock.patch(MODULE_PATH + '.manager.cache')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._group_name_to_id')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups_backoff(self, group_cache, djcache, m):
|
def test_update_groups_backoff(self, name_to_id, user_groups, djcache, m):
|
||||||
# Arrange
|
# Arrange
|
||||||
groups = ['Member']
|
groups = ['Member']
|
||||||
group_cache.return_value = [{'id': 111, 'name': 'Member'}]
|
user_groups.return_value = []
|
||||||
|
name_to_id.return_value = '111'
|
||||||
|
|
||||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
user_id = 12345
|
user_id = 12345
|
||||||
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
request_url = '{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, name_to_id.return_value)
|
||||||
|
|
||||||
djcache.get.return_value = None # No existing backoffs in cache
|
djcache.get.return_value = None # No existing backoffs in cache
|
||||||
|
|
||||||
m.patch(request_url,
|
m.put(request_url,
|
||||||
request_headers=headers,
|
request_headers=headers,
|
||||||
headers={'Retry-After': '200000'},
|
headers={'Retry-After': '200000'},
|
||||||
status_code=429)
|
status_code=429)
|
||||||
|
|
||||||
# Act & Assert
|
# Act & Assert
|
||||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||||
@@ -391,23 +391,25 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
|
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.cache')
|
@mock.patch(MODULE_PATH + '.manager.cache')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._group_name_to_id')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups_global_backoff(self, group_cache, djcache, m):
|
def test_update_groups_global_backoff(self, name_to_id, user_groups, djcache, m):
|
||||||
# Arrange
|
# Arrange
|
||||||
groups = ['Member']
|
groups = ['Member']
|
||||||
group_cache.return_value = [{'id': 111, 'name': 'Member'}]
|
user_groups.return_value = []
|
||||||
|
name_to_id.return_value = '111'
|
||||||
|
|
||||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
user_id = 12345
|
user_id = 12345
|
||||||
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
|
request_url = '{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, name_to_id.return_value)
|
||||||
|
|
||||||
djcache.get.return_value = None # No existing backoffs in cache
|
djcache.get.return_value = None # No existing backoffs in cache
|
||||||
|
|
||||||
m.patch(request_url,
|
m.put(request_url,
|
||||||
request_headers=headers,
|
request_headers=headers,
|
||||||
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
||||||
status_code=429)
|
status_code=429)
|
||||||
|
|
||||||
# Act & Assert
|
# Act & Assert
|
||||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from hashlib import md5
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # default 2 hours
|
GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # default 2 hours
|
||||||
|
|
||||||
|
|
||||||
class DiscourseError(Exception):
|
class DiscourseError(Exception):
|
||||||
@@ -23,7 +23,7 @@ class DiscourseError(Exception):
|
|||||||
ENDPOINTS = {
|
ENDPOINTS = {
|
||||||
'groups': {
|
'groups': {
|
||||||
'list': {
|
'list': {
|
||||||
'path': "/admin/groups.json",
|
'path': "/groups/search.json",
|
||||||
'method': 'get',
|
'method': 'get',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
@@ -235,7 +235,7 @@ class DiscourseManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def __add_user_to_group(g_id, username):
|
def __add_user_to_group(g_id, username):
|
||||||
endpoint = ENDPOINTS['groups']['add_user']
|
endpoint = ENDPOINTS['groups']['add_user']
|
||||||
DiscourseManager.__exc(endpoint, g_id, usernames=[username])
|
DiscourseManager.__exc(endpoint, g_id, usernames=username)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __remove_user_from_group(g_id, username):
|
def __remove_user_from_group(g_id, username):
|
||||||
@@ -355,11 +355,14 @@ class DiscourseManager:
|
|||||||
user_groups = DiscourseManager.__get_user_groups(username)
|
user_groups = DiscourseManager.__get_user_groups(username)
|
||||||
add_groups = [group_dict[x] for x in group_dict if not group_dict[x] in user_groups]
|
add_groups = [group_dict[x] for x in group_dict if not group_dict[x] in user_groups]
|
||||||
rem_groups = [x for x in user_groups if x not in inv_group_dict]
|
rem_groups = [x for x in user_groups if x not in inv_group_dict]
|
||||||
if add_groups or rem_groups:
|
if add_groups:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Updating discourse user %s groups: adding %s, removing %s" % (username, add_groups, rem_groups))
|
"Updating discourse user %s groups: adding %s" % (username, add_groups))
|
||||||
for g in add_groups:
|
for g in add_groups:
|
||||||
DiscourseManager.__add_user_to_group(g, username)
|
DiscourseManager.__add_user_to_group(g, username)
|
||||||
|
if rem_groups:
|
||||||
|
logger.info(
|
||||||
|
"Updating discourse user %s groups: removing %s" % (username, rem_groups))
|
||||||
for g in rem_groups:
|
for g in rem_groups:
|
||||||
DiscourseManager.__remove_user_from_group(g, username)
|
DiscourseManager.__remove_user_from_group(g, username)
|
||||||
|
|
||||||
|
|||||||
@@ -58,5 +58,5 @@ class Migration(migrations.Migration):
|
|||||||
name='discourseuser',
|
name='discourseuser',
|
||||||
options={'permissions': (('access_discourse', 'Can access the Discourse service'),)},
|
options={'permissions': (('access_discourse', 'Can access the Discourse service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from celery import shared_task
|
|||||||
|
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from .manager import DiscourseManager
|
from .manager import DiscourseManager
|
||||||
from .models import DiscourseUser
|
from .models import DiscourseUser
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class DiscourseTasks:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name='discourse.update_groups')
|
@shared_task(bind=True, name='discourse.update_groups', base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating discourse groups for user %s" % user)
|
logger.debug("Updating discourse groups for user %s" % user)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<td class="text-center">Discourse</td>
|
<tr>
|
||||||
<td class="text-center">{{ char.character_name }}</td>
|
<td class="text-center">Discourse</td>
|
||||||
<td class="text-center"><a href="{{ DISCOURSE_URL }}">{{ DISCOURSE_URL }}</a></td>
|
<td class="text-center">{{ char.character_name }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center"><a href="{{ DISCOURSE_URL }}">{{ DISCOURSE_URL }}</a></td>
|
||||||
<a title="Go To Forums" class="btn btn-success" href="{{ DISCOURSE_URL }}"><span class="glyphicon glyphicon-arrow-right"></span></a>
|
<td class="text-center">
|
||||||
</td>
|
<a title="Go To Forums" class="btn btn-success" href="{{ DISCOURSE_URL }}"><span class="glyphicon glyphicon-arrow-right"></span></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='ips4user',
|
name='ips4user',
|
||||||
options={'permissions': (('access_ips4', 'Can access the IPS4 service'),)},
|
options={'permissions': (('access_ips4', 'Can access the IPS4 service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -51,31 +51,31 @@ class MarketManager:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_username(cls, username):
|
def check_username(cls, username):
|
||||||
logger.debug("Checking alliance market username %%s" % username)
|
logger.debug("Checking alliance market username %s" % username)
|
||||||
cursor = connections['market'].cursor()
|
cursor = connections['market'].cursor()
|
||||||
cursor.execute(cls.SQL_CHECK_USERNAME, [cls.__santatize_username(username)])
|
cursor.execute(cls.SQL_CHECK_USERNAME, [cls.__santatize_username(username)])
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row:
|
if row:
|
||||||
logger.debug("Found user %%s on alliance market" % username)
|
logger.debug("Found user %s on alliance market" % username)
|
||||||
return True
|
return True
|
||||||
logger.debug("User %%s not found on alliance market" % username)
|
logger.debug("User %s not found on alliance market" % username)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_user_email(cls, username, email):
|
def check_user_email(cls, username, email):
|
||||||
logger.debug("Checking if alliance market email exists for user %%s" % username)
|
logger.debug("Checking if alliance market email exists for user %s" % username)
|
||||||
cursor = connections['market'].cursor()
|
cursor = connections['market'].cursor()
|
||||||
cursor.execute(cls.SQL_CHECK_EMAIL, [email])
|
cursor.execute(cls.SQL_CHECK_EMAIL, [email])
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row:
|
if row:
|
||||||
logger.debug("Found user %%s email address on alliance market" % username)
|
logger.debug("Found user %s email address on alliance market" % username)
|
||||||
return True
|
return True
|
||||||
logger.debug("User %%s email address not found on alliance market" % username)
|
logger.debug("User %s email address not found on alliance market" % username)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_user(cls, username, email, characterid, charactername):
|
def add_user(cls, username, email, characterid, charactername):
|
||||||
logger.debug("Adding new market user %%s" % username)
|
logger.debug("Adding new market user %s" % username)
|
||||||
plain_password = cls.__generate_random_pass()
|
plain_password = cls.__generate_random_pass()
|
||||||
hash = cls._gen_pwhash(plain_password)
|
hash = cls._gen_pwhash(plain_password)
|
||||||
salt = cls._get_salt(hash)
|
salt = cls._get_salt(hash)
|
||||||
@@ -83,33 +83,33 @@ class MarketManager:
|
|||||||
if not cls.check_username(username):
|
if not cls.check_username(username):
|
||||||
if not cls.check_user_email(username, email):
|
if not cls.check_user_email(username, email):
|
||||||
try:
|
try:
|
||||||
logger.debug("Adding user %%s to alliance market" % username)
|
logger.debug("Adding user %s to alliance market" % username)
|
||||||
cursor = connections['market'].cursor()
|
cursor = connections['market'].cursor()
|
||||||
cursor.execute(cls.SQL_ADD_USER, [username_clean, username_clean, email, email, salt,
|
cursor.execute(cls.SQL_ADD_USER, [username_clean, username_clean, email, email, salt,
|
||||||
hash, characterid, charactername])
|
hash, characterid, charactername])
|
||||||
return username_clean, plain_password
|
return username_clean, plain_password
|
||||||
except:
|
except:
|
||||||
logger.debug("Unsuccessful attempt to add market user %%s" % username)
|
logger.debug("Unsuccessful attempt to add market user %s" % username)
|
||||||
return "", ""
|
return "", ""
|
||||||
else:
|
else:
|
||||||
logger.debug("Alliance market email %%s already exists Updating instead" % email)
|
logger.debug("Alliance market email %s already exists Updating instead" % email)
|
||||||
username_clean, password = cls.update_user_info(username)
|
username_clean, password = cls.update_user_info(username)
|
||||||
return username_clean, password
|
return username_clean, password
|
||||||
else:
|
else:
|
||||||
logger.debug("Alliance market username %%s already exists Updating instead" % username)
|
logger.debug("Alliance market username %s already exists Updating instead" % username)
|
||||||
username_clean, password = cls.update_user_info(username)
|
username_clean, password = cls.update_user_info(username)
|
||||||
return username_clean, password
|
return username_clean, password
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def disable_user(cls, username):
|
def disable_user(cls, username):
|
||||||
logger.debug("Disabling alliance market user %%s " % username)
|
logger.debug("Disabling alliance market user %s " % username)
|
||||||
cursor = connections['market'].cursor()
|
cursor = connections['market'].cursor()
|
||||||
cursor.execute(cls.SQL_DISABLE_USER, [username])
|
cursor.execute(cls.SQL_DISABLE_USER, [username])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_custom_password(cls, username, plain_password):
|
def update_custom_password(cls, username, plain_password):
|
||||||
logger.debug("Updating alliance market user %%s password" % username)
|
logger.debug("Updating alliance market user %s password" % username)
|
||||||
if cls.check_username(username):
|
if cls.check_username(username):
|
||||||
username_clean = cls.__santatize_username(username)
|
username_clean = cls.__santatize_username(username)
|
||||||
hash = cls._gen_pwhash(plain_password)
|
hash = cls._gen_pwhash(plain_password)
|
||||||
@@ -118,12 +118,12 @@ class MarketManager:
|
|||||||
cursor.execute(cls.SQL_UPDATE_PASSWORD, [hash, salt, username_clean])
|
cursor.execute(cls.SQL_UPDATE_PASSWORD, [hash, salt, username_clean])
|
||||||
return plain_password
|
return plain_password
|
||||||
else:
|
else:
|
||||||
logger.error("Unable to update alliance market user %%s password" % username)
|
logger.error("Unable to update alliance market user %s password" % username)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_user_password(cls, username):
|
def update_user_password(cls, username):
|
||||||
logger.debug("Updating alliance market user %%s password" % username)
|
logger.debug("Updating alliance market user %s password" % username)
|
||||||
if cls.check_username(username):
|
if cls.check_username(username):
|
||||||
username_clean = cls.__santatize_username(username)
|
username_clean = cls.__santatize_username(username)
|
||||||
plain_password = cls.__generate_random_pass()
|
plain_password = cls.__generate_random_pass()
|
||||||
@@ -133,12 +133,12 @@ class MarketManager:
|
|||||||
cursor.execute(cls.SQL_UPDATE_PASSWORD, [hash, salt, username_clean])
|
cursor.execute(cls.SQL_UPDATE_PASSWORD, [hash, salt, username_clean])
|
||||||
return plain_password
|
return plain_password
|
||||||
else:
|
else:
|
||||||
logger.error("Unable to update alliance market user %%s password" % username)
|
logger.error("Unable to update alliance market user %s password" % username)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_user_info(cls, username):
|
def update_user_info(cls, username):
|
||||||
logger.debug("Updating alliance market user %%s" % username)
|
logger.debug("Updating alliance market user %s" % username)
|
||||||
try:
|
try:
|
||||||
username_clean = cls.__santatize_username(username)
|
username_clean = cls.__santatize_username(username)
|
||||||
plain_password = cls.__generate_random_pass()
|
plain_password = cls.__generate_random_pass()
|
||||||
@@ -148,5 +148,5 @@ class MarketManager:
|
|||||||
cursor.execute(cls.SQL_UPDATE_USER, [hash, salt, username_clean])
|
cursor.execute(cls.SQL_UPDATE_USER, [hash, salt, username_clean])
|
||||||
return username_clean, plain_password
|
return username_clean, plain_password
|
||||||
except:
|
except:
|
||||||
logger.debug("Alliance market update user failed for %%s" % username)
|
logger.debug("Alliance market update user failed for %s" % username)
|
||||||
return "", ""
|
return "", ""
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='marketuser',
|
name='marketuser',
|
||||||
options={'permissions': (('access_market', 'Can access the Evernus Market service'),)},
|
options={'permissions': (('access_market', 'Can access the Evernus Market service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import urllib
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
@@ -61,7 +62,7 @@ class MumbleService(ServicesHook):
|
|||||||
'service_name': self.title,
|
'service_name': self.title,
|
||||||
'urls': urls,
|
'urls': urls,
|
||||||
'service_url': self.service_url,
|
'service_url': self.service_url,
|
||||||
'connect_url': request.user.mumble.username + '@' + self.service_url if MumbleTasks.has_account(request.user) else self.service_url,
|
'connect_url': urllib.parse.quote(request.user.mumble.username, safe="") + '@' + self.service_url if MumbleTasks.has_account(request.user) else self.service_url,
|
||||||
'username': request.user.mumble.username if MumbleTasks.has_account(request.user) else '',
|
'username': request.user.mumble.username if MumbleTasks.has_account(request.user) else '',
|
||||||
}, request=request)
|
}, request=request)
|
||||||
|
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='mumbleuser',
|
name='mumbleuser',
|
||||||
options={'permissions': (('access_mumble', 'Can access the Mumble service'),)},
|
options={'permissions': (('access_mumble', 'Can access the Mumble service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from .models import MumbleUser
|
from .models import MumbleUser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -26,7 +26,7 @@ class MumbleTasks:
|
|||||||
MumbleUser.objects.all().delete()
|
MumbleUser.objects.all().delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="mumble.update_groups")
|
@shared_task(bind=True, name="mumble.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating mumble groups for user %s" % user)
|
logger.debug("Updating mumble groups for user %s" % user)
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='openfireuser',
|
name='openfireuser',
|
||||||
options={'permissions': (('access_openfire', 'Can access the Openfire service'),)},
|
options={'permissions': (('access_openfire', 'Can access the Openfire service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.services.modules.openfire.manager import OpenfireManager
|
from allianceauth.services.modules.openfire.manager import OpenfireManager
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .models import OpenfireUser
|
from .models import OpenfireUser
|
||||||
@@ -40,7 +40,7 @@ class OpenfireTasks:
|
|||||||
OpenfireUser.objects.all().delete()
|
OpenfireUser.objects.all().delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="openfire.update_groups")
|
@shared_task(bind=True, name="openfire.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating jabber groups for user %s" % user)
|
logger.debug("Updating jabber groups for user %s" % user)
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='phpbb3user',
|
name='phpbb3user',
|
||||||
options={'permissions': (('access_phpbb3', 'Can access the phpBB3 service'),)},
|
options={'permissions': (('access_phpbb3', 'Can access the phpBB3 service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import Phpbb3Manager
|
from .manager import Phpbb3Manager
|
||||||
@@ -35,7 +35,7 @@ class Phpbb3Tasks:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="phpbb3.update_groups")
|
@shared_task(bind=True, name="phpbb3.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating phpbb3 groups for user %s" % user)
|
logger.debug("Updating phpbb3 groups for user %s" % user)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import SeatManager
|
from .manager import SeatManager
|
||||||
@@ -34,7 +34,7 @@ class SeatTasks:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True)
|
@shared_task(bind=True, name='seat.update_roles', base=QueueOnce)
|
||||||
def update_roles(self, pk):
|
def update_roles(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating SeAT roles for user %s" % user)
|
logger.debug("Updating SeAT roles for user %s" % user)
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='smfuser',
|
name='smfuser',
|
||||||
options={'permissions': (('access_smf', 'Can access the SMF service'),)},
|
options={'permissions': (('access_smf', 'Can access the SMF service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import SmfManager
|
from .manager import SmfManager
|
||||||
@@ -39,7 +39,7 @@ class SmfTasks:
|
|||||||
SmfUser.objects.all().delete()
|
SmfUser.objects.all().delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="smf.update_groups")
|
@shared_task(bind=True, name="smf.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating smf groups for user %s" % user)
|
logger.debug("Updating smf groups for user %s" % user)
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ class Teamspeak3Manager:
|
|||||||
group_cache = self.server.send_command('servergrouplist')
|
group_cache = self.server.send_command('servergrouplist')
|
||||||
logger.debug("Received group cache from server: %s" % group_cache)
|
logger.debug("Received group cache from server: %s" % group_cache)
|
||||||
for group in group_cache:
|
for group in group_cache:
|
||||||
|
if group['keys']['type'] != '1':
|
||||||
|
continue
|
||||||
logger.debug("Checking group %s" % group)
|
logger.debug("Checking group %s" % group)
|
||||||
if group['keys']['name'] == groupname:
|
if group['keys']['name'] == groupname:
|
||||||
logger.debug("Found group %s, returning id %s" % (groupname, group['keys']['sgid']))
|
logger.debug("Found group %s, returning id %s" % (groupname, group['keys']['sgid']))
|
||||||
@@ -124,6 +126,8 @@ class Teamspeak3Manager:
|
|||||||
outlist = {}
|
outlist = {}
|
||||||
if group_cache:
|
if group_cache:
|
||||||
for group in group_cache:
|
for group in group_cache:
|
||||||
|
if group['keys']['type'] != '1':
|
||||||
|
continue
|
||||||
logger.debug("Assigning name/id dict: %s = %s" % (group['keys']['name'], group['keys']['sgid']))
|
logger.debug("Assigning name/id dict: %s = %s" % (group['keys']['name'], group['keys']['sgid']))
|
||||||
outlist[group['keys']['name']] = group['keys']['sgid']
|
outlist[group['keys']['name']] = group['keys']['sgid']
|
||||||
else:
|
else:
|
||||||
@@ -179,18 +183,19 @@ class Teamspeak3Manager:
|
|||||||
except:
|
except:
|
||||||
logger.exception("An unhandled exception has occured while syncing TS groups.")
|
logger.exception("An unhandled exception has occured while syncing TS groups.")
|
||||||
|
|
||||||
def add_user(self, username):
|
def add_user(self, user, fmt_name):
|
||||||
username_clean = self.__santatize_username(username[:30])
|
username_clean = self.__santatize_username(fmt_name[:30])
|
||||||
logger.debug("Adding user to TS3 server with cleaned username %s" % username_clean)
|
logger.debug("Adding user to TS3 server with cleaned username %s" % username_clean)
|
||||||
server_groups = self._group_list()
|
server_groups = self._group_list()
|
||||||
|
|
||||||
if 'Member' not in server_groups:
|
state = user.profile.state.name
|
||||||
self._create_group('Member')
|
if state not in server_groups:
|
||||||
|
self._create_group(state)
|
||||||
|
|
||||||
alliance_group_id = self._group_id_by_name('Member')
|
state_group_id = self._group_id_by_name(state)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = self.server.send_command('tokenadd', {'tokentype': 0, 'tokenid1': alliance_group_id, 'tokenid2': 0,
|
ret = self.server.send_command('tokenadd', {'tokentype': 0, 'tokenid1': state_group_id, 'tokenid2': 0,
|
||||||
'tokendescription': username_clean,
|
'tokendescription': username_clean,
|
||||||
'tokencustomset': "ident=sso_uid value=%s" % username_clean})
|
'tokencustomset': "ident=sso_uid value=%s" % username_clean})
|
||||||
except TeamspeakError as e:
|
except TeamspeakError as e:
|
||||||
@@ -213,7 +218,7 @@ class Teamspeak3Manager:
|
|||||||
if isinstance(clients, dict):
|
if isinstance(clients, dict):
|
||||||
# Rewrap list
|
# Rewrap list
|
||||||
clients = [clients]
|
clients = [clients]
|
||||||
|
|
||||||
for client in clients:
|
for client in clients:
|
||||||
try:
|
try:
|
||||||
if client['keys']['client_database_id'] == user:
|
if client['keys']['client_database_id'] == user:
|
||||||
@@ -244,10 +249,10 @@ class Teamspeak3Manager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def generate_new_permissionkey(self, uid, username):
|
def generate_new_permissionkey(self, uid, user, username):
|
||||||
logger.debug("Re-issuing permission key for user id %s" % uid)
|
logger.debug("Re-issuing permission key for user id %s" % uid)
|
||||||
self.delete_user(uid)
|
self.delete_user(uid)
|
||||||
return self.add_user(username)
|
return self.add_user(user, username)
|
||||||
|
|
||||||
def update_groups(self, uid, ts_groups):
|
def update_groups(self, uid, ts_groups):
|
||||||
logger.debug("Updating uid %s TS3 groups %s" % (uid, ts_groups))
|
logger.debug("Updating uid %s TS3 groups %s" % (uid, ts_groups))
|
||||||
|
|||||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
|||||||
name='teamspeak3user',
|
name='teamspeak3user',
|
||||||
options={'permissions': (('access_teamspeak3', 'Can access the Teamspeak3 service'),)},
|
options={'permissions': (('access_teamspeak3', 'Can access the Teamspeak3 service'),)},
|
||||||
),
|
),
|
||||||
migrations.RunPython(migrate_service_enabled),
|
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from allianceauth.services.tasks import QueueOnce
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
from allianceauth.services.hooks import NameFormatter
|
from allianceauth.services.hooks import NameFormatter
|
||||||
from .manager import Teamspeak3Manager
|
from .manager import Teamspeak3Manager
|
||||||
@@ -56,7 +56,7 @@ class Teamspeak3Tasks:
|
|||||||
logger.info("Teamspeak3 disabled")
|
logger.info("Teamspeak3 disabled")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@shared_task(bind=True, name="teamspeak3.update_groups")
|
@shared_task(bind=True, name="teamspeak3.update_groups", base=QueueOnce)
|
||||||
def update_groups(self, pk):
|
def update_groups(self, pk):
|
||||||
user = User.objects.get(pk=pk)
|
user = User.objects.get(pk=pk)
|
||||||
logger.debug("Updating user %s teamspeak3 groups" % user)
|
logger.debug("Updating user %s teamspeak3 groups" % user)
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ module_urls = [
|
|||||||
name='activate'),
|
name='activate'),
|
||||||
url(r'^deactivate/$', views.deactivate_teamspeak3,
|
url(r'^deactivate/$', views.deactivate_teamspeak3,
|
||||||
name='deactivate'),
|
name='deactivate'),
|
||||||
url(r'reset_perm/$', views.reset_teamspeak3_perm,
|
url(r'^reset_perm/$', views.reset_teamspeak3_perm,
|
||||||
name='reset_perm'),
|
name='reset_perm'),
|
||||||
|
|
||||||
# Teamspeak Urls
|
# Teamspeak Urls
|
||||||
url(r'verify/$', views.verify_teamspeak3, name='verify'),
|
url(r'^verify/$', views.verify_teamspeak3, name='verify'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class TS3Proto:
|
|||||||
|
|
||||||
def connect(self, ip, port):
|
def connect(self, ip, port):
|
||||||
try:
|
try:
|
||||||
self._conn = telnetlib.Telnet(host=ip, port=port)
|
self._conn = telnetlib.Telnet(host=ip, port=port, timeout=5)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
except:
|
except:
|
||||||
# raise ConnectionError(ip, port)
|
# raise ConnectionError(ip, port)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user