mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 06:06:19 +01:00
Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d6a3a3d6b | ||
|
|
5006246cf1 | ||
|
|
6187fb9b86 | ||
|
|
86f57ccd56 | ||
|
|
854096bac7 | ||
|
|
9d2b3bb157 | ||
|
|
22bda62e59 | ||
|
|
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 | ||
|
|
ad79b4f77c | ||
|
|
fd876b8636 | ||
|
|
21e896642a | ||
|
|
b4c395f116 | ||
|
|
a38116014d | ||
|
|
54223db1e9 | ||
|
|
8a897abc7b | ||
|
|
fe7b078ec8 | ||
|
|
ce66bdcbd4 | ||
|
|
f65e563c0c | ||
|
|
e860ba6c22 | ||
|
|
50b6605a43 | ||
|
|
d181200642 | ||
|
|
386ba25a44 | ||
|
|
5331d194df | ||
|
|
814ecd233e | ||
|
|
f9a8ac4e9b | ||
|
|
1bd5eecd54 | ||
|
|
2fa1d9998d | ||
|
|
9d9cfebd9e | ||
|
|
cc8a7a18d2 | ||
|
|
552c795041 | ||
|
|
3d757e8d90 | ||
|
|
1b5ecaed80 | ||
|
|
77c93ed96b | ||
|
|
3eeed99af2 | ||
|
|
a143dfbb37 | ||
|
|
6b1da3b18a | ||
|
|
f0894f3415 | ||
|
|
539295c1b7 | ||
|
|
54f91a5bfb | ||
|
|
f3c0d05c39 | ||
|
|
9f9cc7ed42 | ||
|
|
814b2da0ca | ||
|
|
7a9bb0c84b | ||
|
|
36ae2af29b | ||
|
|
d192f23e6e | ||
|
|
67cd0cd55c | ||
|
|
9e53d8b429 | ||
|
|
f5abf82b95 | ||
|
|
8dd3a25b52 | ||
|
|
d0aa46db08 | ||
|
|
f0ff70566b | ||
|
|
efecf5113b | ||
|
|
980569de68 | ||
|
|
9c74952607 | ||
|
|
70c2a4a6e4 | ||
|
|
99b136b824 | ||
|
|
ae4116c0f6 | ||
|
|
3080d7d868 | ||
|
|
08cf8ae1d6 | ||
|
|
3ed0f873f3 | ||
|
|
5060d3f408 | ||
|
|
ef24bea562 | ||
|
|
c18efaa33d | ||
|
|
b6b14f6f1c | ||
|
|
a90a52f426 | ||
|
|
bd5ea38446 | ||
|
|
f8248f46e5 | ||
|
|
b09c454bf0 | ||
|
|
d825689da4 | ||
|
|
a64dda2a2e | ||
|
|
8ce8789631 | ||
|
|
2b2f367c30 | ||
|
|
4d194457d8 | ||
|
|
6f7cf8805d | ||
|
|
36e39503c8 | ||
|
|
e7a24c9cd4 | ||
|
|
bd8a8922cc | ||
|
|
396b2e0fb6 | ||
|
|
36e382fadb | ||
|
|
d2666f2440 | ||
|
|
397ca97f0f | ||
|
|
631bb439a4 | ||
|
|
a4003e188e | ||
|
|
f4a9ba2db8 | ||
|
|
895a62c475 | ||
|
|
ac5a0d9dcb | ||
|
|
b8644d5c93 | ||
|
|
4d8baf1af0 | ||
|
|
f70987de09 | ||
|
|
9d02b1530c | ||
|
|
3d532dae01 | ||
|
|
02247b067f | ||
|
|
63c2668171 | ||
|
|
5575039126 | ||
|
|
f97c8f2ce4 | ||
|
|
6baab1d006 | ||
|
|
17adf04860 | ||
|
|
f871ecb425 | ||
|
|
4a425cde78 | ||
|
|
f56252b0cc | ||
|
|
7ae6c66beb | ||
|
|
be90fb96ea | ||
|
|
dd3350b169 | ||
|
|
cdd1ba1fe3 | ||
|
|
10ea12c867 | ||
|
|
e6358d948a | ||
|
|
1101572f78 | ||
|
|
0cf8836832 | ||
|
|
6e4562b0e6 | ||
|
|
856f1e176a | ||
|
|
1653a57e7b | ||
|
|
5f03e580c2 | ||
|
|
d370ae48a2 | ||
|
|
38baeba254 | ||
|
|
478f9b9390 | ||
|
|
82ad3821c4 | ||
|
|
07afaf12d5 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,6 +8,7 @@ __pycache__/
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
@@ -41,7 +42,6 @@ nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
|
||||
27
README.md
27
README.md
@@ -7,8 +7,7 @@ Alliance Auth
|
||||
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
||||
|
||||
|
||||
EVE service auth to help corps, alliances, and coalitions manage services.
|
||||
Built for "The 99 Percent" open for anyone to use.
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||
|
||||
@@ -17,21 +16,19 @@ Built for "The 99 Percent" open for anyone to use.
|
||||
|
||||
Active Developers:
|
||||
|
||||
- [Adarnof](https://github.com/Adarnof)
|
||||
- [Basraah](https://github.com/basraah)
|
||||
|
||||
- [Adarnof](https://github.com/adarnof/)
|
||||
- [Basraah](https://github.com/basraah/)
|
||||
|
||||
Beta Testers / Bug Fixers:
|
||||
|
||||
- [ghoti](https://github.com/ghoti)
|
||||
- [ghoti](https://github.com/ghoti/)
|
||||
- [mmolitor87](https://github.com/mmolitor87/)
|
||||
- [TargetZ3R0](https://github.com/TargetZ3R0)
|
||||
- [kaezon](https://github.com/kaezon/)
|
||||
- [orbitroom](https://github.com/orbitroom/)
|
||||
- [tehfiend](https://github.com/tehfiend/)
|
||||
|
||||
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.
|
||||
|
||||
Past Beta Testers / Bug Fixers:
|
||||
|
||||
- TrentBartlem (Testing and Bug Fixes)
|
||||
- IskFiend (Bug Fixes and Server Configuration)
|
||||
- Mr McClain (Bug Fixes and server configuration)
|
||||
|
||||
Special Thanks:
|
||||
|
||||
- Thanks to Nikdoof, without his old auth implementation this project wouldn't be as far as it is now.
|
||||
### 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.
|
||||
@@ -1,7 +1,7 @@
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '2.0-dev'
|
||||
__version__ = '2.0.2'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.auth.models import User as BaseUser, Permission as BasePermission
|
||||
from django.utils.text import slugify
|
||||
from django.db.models import Q
|
||||
from allianceauth.services.hooks import ServicesHook
|
||||
|
||||
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile, OwnershipRecord
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from django.forms import ModelForm
|
||||
|
||||
|
||||
def make_service_hooks_update_groups_action(service):
|
||||
@@ -38,6 +42,47 @@ def make_service_hooks_sync_nickname_action(service):
|
||||
return sync_nickname
|
||||
|
||||
|
||||
class QuerysetModelForm(ModelForm):
|
||||
# allows specifying FK querysets through kwarg
|
||||
def __init__(self, querysets=None, *args, **kwargs):
|
||||
querysets = querysets or {}
|
||||
super().__init__(*args, **kwargs)
|
||||
for field, qs in querysets.items():
|
||||
self.fields[field].queryset = qs
|
||||
|
||||
|
||||
class UserProfileInline(admin.StackedInline):
|
||||
model = UserProfile
|
||||
readonly_fields = ('state',)
|
||||
form = QuerysetModelForm
|
||||
verbose_name = ''
|
||||
verbose_name_plural = 'Profile'
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
# main_character field can only show current value or unclaimed alts
|
||||
# if superuser, allow selecting from any unclaimed main
|
||||
query = Q()
|
||||
if obj and obj.profile.main_character:
|
||||
query |= Q(pk=obj.profile.main_character_id)
|
||||
if request.user.is_superuser:
|
||||
query |= Q(userprofile__isnull=True)
|
||||
else:
|
||||
query |= Q(character_ownership__user=obj)
|
||||
qs = EveCharacter.objects.filter(query)
|
||||
formset = super().get_formset(request, obj=obj, **kwargs)
|
||||
|
||||
def get_kwargs(self, index):
|
||||
return {'querysets': {'main_character': EveCharacter.objects.filter(query)}}
|
||||
formset.get_form_kwargs = get_kwargs
|
||||
return formset
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""
|
||||
Extending Django's UserAdmin model
|
||||
@@ -62,6 +107,25 @@ class UserAdmin(BaseUserAdmin):
|
||||
|
||||
return actions
|
||||
list_filter = BaseUserAdmin.list_filter + ('profile__state',)
|
||||
inlines = BaseUserAdmin.inlines + [UserProfileInline]
|
||||
list_display = ('username', 'email', 'get_main_character', 'get_state', 'is_active')
|
||||
|
||||
def get_main_character(self, obj):
|
||||
return obj.profile.main_character
|
||||
get_main_character.short_description = "Main Character"
|
||||
|
||||
def get_state(self, obj):
|
||||
return obj.profile.state
|
||||
get_state.short_description = "State"
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.change_user')
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.add_user')
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.delete_user')
|
||||
|
||||
|
||||
@admin.register(State)
|
||||
@@ -96,31 +160,30 @@ class StateAdmin(admin.ModelAdmin):
|
||||
return obj.userprofile_set.all().count()
|
||||
|
||||
|
||||
@admin.register(UserProfile)
|
||||
class UserProfileAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ('user', 'state')
|
||||
search_fields = ('user__username', 'main_character__character_name')
|
||||
list_filter = ('state',)
|
||||
list_display = ('user', 'main_character')
|
||||
actions = None
|
||||
class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'character')
|
||||
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.pk:
|
||||
return 'owner_hash', 'character'
|
||||
return tuple()
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
@admin.register(OwnershipRecord)
|
||||
class OwnershipRecordAdmin(BaseOwnershipAdmin):
|
||||
list_display = BaseOwnershipAdmin.list_display + ('created',)
|
||||
|
||||
|
||||
@admin.register(CharacterOwnership)
|
||||
class CharacterOwnershipAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'character')
|
||||
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
||||
readonly_fields = ('owner_hash', 'character')
|
||||
class CharacterOwnershipAdmin(BaseOwnershipAdmin):
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
|
||||
class PermissionAdmin(admin.ModelAdmin):
|
||||
actions = None
|
||||
readonly_fields = [field.name for field in Permission._meta.fields]
|
||||
readonly_fields = [field.name for field in BasePermission._meta.fields]
|
||||
list_display = ('admin_name', 'name', 'codename', 'content_type')
|
||||
list_filter = ('content_type__app_label',)
|
||||
|
||||
@@ -134,23 +197,61 @@ class PermissionAdmin(admin.ModelAdmin):
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_module_permission(self, request):
|
||||
return True
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
# can see list but not edit it
|
||||
return not obj
|
||||
|
||||
|
||||
# Hack to allow registration of django.contrib.auth models in our authentication app
|
||||
class ProxyUser(User):
|
||||
class User(BaseUser):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = User._meta.verbose_name
|
||||
verbose_name_plural = User._meta.verbose_name_plural
|
||||
verbose_name = BaseUser._meta.verbose_name
|
||||
verbose_name_plural = BaseUser._meta.verbose_name_plural
|
||||
|
||||
|
||||
class ProxyPermission(Permission):
|
||||
class Permission(BasePermission):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = Permission._meta.verbose_name
|
||||
verbose_name_plural = Permission._meta.verbose_name_plural
|
||||
verbose_name = BasePermission._meta.verbose_name
|
||||
verbose_name_plural = BasePermission._meta.verbose_name_plural
|
||||
|
||||
|
||||
try:
|
||||
admin.site.unregister(User)
|
||||
admin.site.unregister(BaseUser)
|
||||
finally:
|
||||
admin.site.register(ProxyUser, UserAdmin)
|
||||
admin.site.register(ProxyPermission, PermissionAdmin)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(Permission, PermissionAdmin)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=User)
|
||||
def redirect_pre_save(sender, signal=None, *args, **kwargs):
|
||||
pre_save.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def redirect_post_save(sender, signal=None, *args, **kwargs):
|
||||
post_save.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=User)
|
||||
def redirect_pre_delete(sender, signal=None, *args, **kwargs):
|
||||
pre_delete.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=User)
|
||||
def redirect_post_delete(sender, signal=None, *args, **kwargs):
|
||||
post_delete.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.groups.through)
|
||||
def redirect_m2m_changed_groups(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.user_permissions.through)
|
||||
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseUser, *args, **kwargs)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.models import Permission
|
||||
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):
|
||||
@@ -30,31 +33,48 @@ class StateBackend(ModelBackend):
|
||||
try:
|
||||
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
||||
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
|
||||
else:
|
||||
logger.debug('{0} has changed ownership. Creating new user account.'.format(token.character_name))
|
||||
ownership.delete()
|
||||
return self.create_user(token)
|
||||
except CharacterOwnership.DoesNotExist:
|
||||
try:
|
||||
# insecure legacy main check for pre-sso registration auth installs
|
||||
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
|
||||
token.user = profile.user
|
||||
CharacterOwnership.objects.create_by_token(token)
|
||||
return profile.user
|
||||
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)
|
||||
|
||||
def create_user(self, token):
|
||||
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.is_active = False # prevent login until email set
|
||||
user.save()
|
||||
token.user = 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.save()
|
||||
logger.debug('Created new user {0}'.format(user))
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
|
||||
37
allianceauth/authentication/decorators.py
Normal file
37
allianceauth/authentication/decorators.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from django.conf.urls import include
|
||||
from functools import wraps
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
|
||||
def user_has_main_character(user):
|
||||
return bool(user.profile.main_character)
|
||||
|
||||
|
||||
def decorate_url_patterns(urls, decorator):
|
||||
url_list, app_name, namespace = include(urls)
|
||||
|
||||
def process_patterns(url_patterns):
|
||||
for pattern in url_patterns:
|
||||
if hasattr(pattern, 'url_patterns'):
|
||||
# this is an include - apply to all nested patterns
|
||||
process_patterns(pattern.url_patterns)
|
||||
else:
|
||||
# this is a pattern
|
||||
pattern.callback = decorator(pattern.callback)
|
||||
|
||||
process_patterns(url_list)
|
||||
return url_list, app_name, namespace
|
||||
|
||||
|
||||
def main_character_required(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if user_has_main_character(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
messages.error(request, _('A main character is required to perform that action. Add one below.'))
|
||||
return redirect('authentication:dashboard')
|
||||
return login_required(_wrapped_view)
|
||||
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')
|
||||
|
||||
try:
|
||||
g = Group.objects.get(name=member_state_name)
|
||||
g, _ = Group.objects.get_or_create(name=member_state_name)
|
||||
# move permissions back
|
||||
state = State.objects.get(name=member_state_name)
|
||||
[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
|
||||
for profile in state.userprofile_set.all().select_related('user'):
|
||||
profile.user.groups.add(g.pk)
|
||||
except (Group.DoesNotExist, State.DoesNotExist):
|
||||
except State.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ def create_blue_state(apps, schema_editor):
|
||||
# move group permissions to state
|
||||
g = Group.objects.get(name=blue_state_name)
|
||||
[s.permissions.add(p.pk) for p in g.permissions.all()]
|
||||
g.permissions.clear()
|
||||
g.delete()
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -84,7 +84,7 @@ def create_blue_group(apps, schema_editor):
|
||||
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||
|
||||
try:
|
||||
g = Group.objects.get(name=blue_state_name)
|
||||
g, _ = Group.objects.get_or_create(name=blue_state_name)
|
||||
# move permissions back
|
||||
state = State.objects.get(name=blue_state_name)
|
||||
[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
|
||||
for profile in state.userprofile_set.all().select_related('user'):
|
||||
profile.user.groups.add(g.pk)
|
||||
except (Group.DoesNotExist, State.DoesNotExist):
|
||||
except State.DoesNotExist:
|
||||
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):
|
||||
Token = apps.get_model('esi', 'Token')
|
||||
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||
@@ -103,16 +108,16 @@ def populate_ownerships(apps, schema_editor):
|
||||
|
||||
unique_character_owners = [t['character_id'] for t in
|
||||
Token.objects.all().values('character_id').annotate(n=models.Count('user')) if
|
||||
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id'].exists())]
|
||||
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id']).exists()]
|
||||
|
||||
tokens = Token.objects.filter(character_id__in=unique_character_owners)
|
||||
for c_id in unique_character_owners:
|
||||
ts = tokens.filter(character_id=c_id).order_by('created')
|
||||
for t in ts:
|
||||
if t.can_refresh:
|
||||
# find newest refreshable token and use it as basis for CharacterOwnership
|
||||
CharacterOwnership.objecs.create_by_token(t)
|
||||
break
|
||||
# find newest refreshable token and use it as basis for CharacterOwnership
|
||||
ts = tokens.filter(character_id=c_id).exclude(refresh_token__isnull=True).order_by('created')
|
||||
if ts.exists():
|
||||
token = ts[0]
|
||||
char = EveCharacter.objects.get(character_id=token.character_id)
|
||||
CharacterOwnership.objects.create(user_id=token.user_id, character_id=char.id, owner_hash=token.character_owner_hash)
|
||||
|
||||
|
||||
def create_profiles(apps, schema_editor):
|
||||
@@ -128,15 +133,24 @@ def create_profiles(apps, schema_editor):
|
||||
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')
|
||||
|
||||
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:
|
||||
# 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)
|
||||
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'):
|
||||
# prepare empty profiles
|
||||
state = State.objects.get(name='Guest')
|
||||
UserProfile.objects.create(user=auth.user, state=state)
|
||||
UserProfile.objects.create(user=auth.user, state=guest_state)
|
||||
|
||||
|
||||
def recreate_authservicesinfo(apps, schema_editor):
|
||||
@@ -144,8 +158,16 @@ def recreate_authservicesinfo(apps, schema_editor):
|
||||
UserProfile = apps.get_model('authentication', 'UserProfile')
|
||||
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
|
||||
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user=u.pk) for u in User.objects.all()])
|
||||
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()])
|
||||
|
||||
# repopulate main characters
|
||||
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
||||
@@ -154,8 +176,8 @@ def recreate_authservicesinfo(apps, schema_editor):
|
||||
|
||||
# repopulate states we understand
|
||||
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
|
||||
state__name__in=['Member', 'Blue']).select_related('user', 'state'):
|
||||
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name})
|
||||
state__name__in=[member_state_name, blue_state_name]).select_related('user', 'state'):
|
||||
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': states[profile.state.name]})
|
||||
|
||||
|
||||
def disable_passwords(apps, schema_editor):
|
||||
@@ -203,7 +225,6 @@ class Migration(migrations.Migration):
|
||||
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
|
||||
],
|
||||
options={
|
||||
'default_permissions': ('change',),
|
||||
'ordering': ['-priority'],
|
||||
},
|
||||
),
|
||||
@@ -222,6 +243,7 @@ class Migration(migrations.Migration):
|
||||
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
|
||||
migrations.RunPython(create_member_state, create_member_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(create_profiles, recreate_authservicesinfo),
|
||||
migrations.RemoveField(
|
||||
@@ -233,7 +255,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
|
||||
migrations.CreateModel(
|
||||
name='ProxyPermission',
|
||||
name='Permission',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
@@ -247,7 +269,7 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProxyUser',
|
||||
name='User',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -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):
|
||||
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
|
||||
|
||||
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.db.models import Q
|
||||
from django.db.models.signals import post_save, pre_delete, m2m_changed, pre_save
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver, Signal
|
||||
from esi.models import Token
|
||||
|
||||
@@ -11,7 +11,6 @@ from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
state_changed = Signal(providing_args=['user', 'state'])
|
||||
|
||||
|
||||
@@ -32,23 +31,27 @@ def trigger_state_check(state):
|
||||
@receiver(m2m_changed, sender=State.member_characters.through)
|
||||
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member characters changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=State.member_corporations.through)
|
||||
def state_member_corporations_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member corporations changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=State.member_alliances.through)
|
||||
def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member alliances changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=State)
|
||||
def state_saved(sender, instance, *args, **kwargs):
|
||||
logger.debug('State {} saved. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@@ -59,6 +62,7 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
|
||||
if not created:
|
||||
update_fields = kwargs.pop('update_fields', []) or []
|
||||
if 'state' not in update_fields:
|
||||
logger.debug('Profile for {} saved without state change. Re-evaluating state.'.format(instance.user))
|
||||
instance.assign_state()
|
||||
|
||||
|
||||
@@ -66,12 +70,15 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
|
||||
def create_required_models(sender, instance, created, *args, **kwargs):
|
||||
# ensure all users have a model
|
||||
if created:
|
||||
logger.debug('User {} created. Creating default UserProfile.'.format(instance))
|
||||
UserProfile.objects.get_or_create(user=instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Token)
|
||||
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
if created:
|
||||
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
|
||||
instance.character_name))
|
||||
if instance.user:
|
||||
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
||||
else:
|
||||
@@ -80,10 +87,15 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
|
||||
# create character if needed
|
||||
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
|
||||
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name,
|
||||
instance.character_id))
|
||||
EveCharacter.objects.create_character(instance.character_id)
|
||||
char = EveCharacter.objects.get(character_id=instance.character_id)
|
||||
# check if we need to create ownership
|
||||
if instance.user and not CharacterOwnership.objects.filter(character__character_id=instance.character_id).exists():
|
||||
if instance.user and not CharacterOwnership.objects.filter(
|
||||
character__character_id=instance.character_id).exists():
|
||||
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name,
|
||||
instance.user))
|
||||
CharacterOwnership.objects.update_or_create(character=char,
|
||||
defaults={'owner_hash': instance.character_owner_hash,
|
||||
'user': instance.user})
|
||||
@@ -91,20 +103,23 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
|
||||
@receiver(pre_delete, sender=CharacterOwnership)
|
||||
def validate_main_character(sender, instance, *args, **kwargs):
|
||||
if instance.user.profile.main_character == instance.character:
|
||||
# clear main character as user no longer owns them
|
||||
instance.user.profile.main_character = None
|
||||
instance.user.profile.save()
|
||||
try:
|
||||
if instance.user.profile.main_character == instance.character:
|
||||
logger.info("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format(
|
||||
instance.character, instance.user))
|
||||
# clear main character as user no longer owns them
|
||||
instance.user.profile.main_character = None
|
||||
instance.user.profile.save()
|
||||
except UserProfile.DoesNotExist:
|
||||
# a user is being deleted
|
||||
pass
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Token)
|
||||
def validate_main_character_token(sender, instance, *args, **kwargs):
|
||||
if UserProfile.objects.filter(main_character__character_id=instance.character_id).exists():
|
||||
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).exists():
|
||||
# clear main character as we can no longer verify ownership
|
||||
profile.main_character = None
|
||||
profile.save()
|
||||
@receiver(post_delete, sender=Token)
|
||||
def validate_ownership(sender, instance, *args, **kwargs):
|
||||
if not Token.objects.filter(character_owner_hash=instance.character_owner_hash).filter(refresh_token__isnull=False).exists():
|
||||
logger.info("No remaining tokens to validate ownership of character {0}. Revoking ownership.".format(instance.character_name))
|
||||
CharacterOwnership.objects.filter(owner_hash=instance.character_owner_hash).delete()
|
||||
|
||||
|
||||
@receiver(pre_save, sender=User)
|
||||
@@ -114,8 +129,11 @@ def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
||||
old_instance = User.objects.get(pk=instance.pk)
|
||||
if old_instance.is_active != instance.is_active:
|
||||
if instance.is_active:
|
||||
logger.debug("User {0} has been activated. Assigning state.".format(instance))
|
||||
instance.profile.assign_state()
|
||||
else:
|
||||
logger.debug(
|
||||
"User {0} has been deactivated. Revoking state and assigning to guest state.".format(instance))
|
||||
instance.profile.state = get_guest_state()
|
||||
instance.profile.save(update_fields=['state'])
|
||||
|
||||
@@ -124,6 +142,20 @@ def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
||||
def check_state_on_character_update(sender, instance, *args, **kwargs):
|
||||
# if this is a main character updating, check that user's state
|
||||
try:
|
||||
logger.debug("Character {0} has been saved. Assessing owner's state for changes.".format(instance))
|
||||
instance.userprofile.assign_state()
|
||||
except UserProfile.DoesNotExist:
|
||||
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
|
||||
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
|
||||
|
||||
from esi.errors import TokenExpiredError, TokenInvalidError
|
||||
from esi.errors import TokenExpiredError, TokenInvalidError, IncompleteResponseError
|
||||
from esi.models import Token
|
||||
from celery import shared_task
|
||||
|
||||
@@ -20,13 +20,19 @@ def check_character_ownership(owner_hash):
|
||||
except (TokenExpiredError, TokenInvalidError):
|
||||
t.delete()
|
||||
continue
|
||||
|
||||
if t.character_owner_hash == old_hash:
|
||||
except (KeyError, IncompleteResponseError):
|
||||
# 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
|
||||
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()
|
||||
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)
|
||||
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar"
|
||||
src="https://image.eveonline.com/Character/{{ main.character_id }}_128.jpg">
|
||||
src="{{ main.portrait_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">{% trans "Missing main character model." %}</div>
|
||||
<div class="alert alert-danger" role="alert">{% trans "No main character set." %}</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="col-xs-6">
|
||||
@@ -102,8 +102,7 @@
|
||||
{% for ownership in request.user.character_ownerships.all %}
|
||||
{% with ownership.character as char %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle"
|
||||
src="https://image.eveonline.com/Character/{{ char.character_id }}_32.jpg">
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat scroll;
|
||||
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat center center fixed;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
@@ -48,4 +48,4 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<div class="dropdown">
|
||||
<form action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
|
||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
@@ -12,4 +11,4 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ You're receiving this email because someone has entered this email address while
|
||||
|
||||
If this was you, please go to the following URL to confirm your email address:
|
||||
|
||||
{{ url }}
|
||||
{{ scheme }}://{{ url }}
|
||||
|
||||
This link will expire in {{ expiration_days }} day(s).
|
||||
|
||||
|
||||
@@ -1,13 +1,70 @@
|
||||
from unittest import mock
|
||||
|
||||
from io import StringIO
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
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 .tasks import check_character_ownership
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from esi.models import Token
|
||||
from esi.errors import IncompleteResponseError
|
||||
from allianceauth.authentication.decorators import main_character_required
|
||||
from django.test.client import RequestFactory
|
||||
from django.http.response import HttpResponse
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.conf import settings
|
||||
from django.shortcuts import reverse
|
||||
from django.core.management import call_command
|
||||
from urllib import parse
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
|
||||
class DecoratorTestCase(TestCase):
|
||||
@staticmethod
|
||||
@main_character_required
|
||||
def dummy_view(*args, **kwargs):
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
|
||||
cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True)
|
||||
main_character = EveCharacter.objects.create(
|
||||
character_id=1,
|
||||
character_name='Main Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1')
|
||||
cls.main_user.profile.main_character = main_character
|
||||
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/test/')
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_login_redirect(self, m):
|
||||
setattr(self.request, 'user', AnonymousUser())
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
url = getattr(response, 'url', None)
|
||||
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_main_character_redirect(self, m):
|
||||
setattr(self.request, 'user', self.no_main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
url = getattr(response, 'url', None)
|
||||
self.assertEqual(url, reverse('authentication:dashboard'))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_successful_request(self, m):
|
||||
setattr(self.request, 'user', self.main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class BackendTestCase(TestCase):
|
||||
@@ -35,6 +92,7 @@ class BackendTestCase(TestCase):
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
|
||||
AuthUtils.disconnect_signals()
|
||||
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')
|
||||
@@ -58,6 +116,14 @@ class BackendTestCase(TestCase):
|
||||
self.assertEqual(user.username, '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(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):
|
||||
t = Token(character_id=self.unclaimed_character.character_id,
|
||||
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||
@@ -130,28 +196,6 @@ class CharacterOwnershipTestCase(TestCase):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
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):
|
||||
@classmethod
|
||||
@@ -286,3 +330,73 @@ class StateTestCase(TestCase):
|
||||
self.user.save()
|
||||
self._refresh_user()
|
||||
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())
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.urls import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from esi.decorators import token_required
|
||||
from esi.models import Token
|
||||
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
|
||||
ActivationView as BaseActivationView, REGISTRATION_SALT
|
||||
from registration.signals import user_registered
|
||||
@@ -71,17 +72,22 @@ have the email address embedded much like the username. Key creation and decodin
|
||||
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def sso_login(request, token):
|
||||
user = authenticate(token=token)
|
||||
if user and user.is_active:
|
||||
login(request, user)
|
||||
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
|
||||
elif user and not user.email:
|
||||
# Store the new user PK in the session to enable us to identify the registering user in Step 2
|
||||
request.session['registration_uid'] = user.pk
|
||||
# Go to Step 2
|
||||
return redirect('registration_register')
|
||||
else:
|
||||
messages.error(request, _('Unable to authenticate as the selected character.'))
|
||||
return redirect(settings.LOGIN_URL)
|
||||
if user:
|
||||
token.user = user
|
||||
if Token.objects.exclude(pk=token.pk).equivalent_to(token).require_valid().exists():
|
||||
token.delete()
|
||||
else:
|
||||
token.save()
|
||||
if user.is_active:
|
||||
login(request, user)
|
||||
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
|
||||
elif not user.email:
|
||||
# Store the new user PK in the session to enable us to identify the registering user in Step 2
|
||||
request.session['registration_uid'] = user.pk
|
||||
# Go to Step 2
|
||||
return redirect('registration_register')
|
||||
messages.error(request, _('Unable to authenticate as the selected character.'))
|
||||
return redirect(settings.LOGIN_URL)
|
||||
|
||||
|
||||
# Step 2
|
||||
@@ -89,20 +95,33 @@ class RegistrationView(BaseRegistrationView):
|
||||
form_class = RegistrationForm
|
||||
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.
|
||||
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
|
||||
pk=self.request.session.get('registration_uid')).exists():
|
||||
messages.error(self.request, _('Registration token has expired.'))
|
||||
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):
|
||||
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
||||
user.email = form.cleaned_data['email']
|
||||
user_registered.send(self.__class__, user=user, request=self.request)
|
||||
# Go to Step 3
|
||||
self.send_activation_email(user)
|
||||
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||
# 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
|
||||
|
||||
def get_activation_key(self, user):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from allianceauth.corputils import urls
|
||||
|
||||
@@ -7,7 +7,7 @@ from allianceauth.corputils import urls
|
||||
class CorpStats(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Corporation Stats',
|
||||
_('Corporation Stats'),
|
||||
'fa fa-share-alt fa-fw',
|
||||
'corputils:view',
|
||||
navactive=['corputils:'])
|
||||
|
||||
@@ -40,9 +40,8 @@ class CorpStats(models.Model):
|
||||
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()[
|
||||
'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()
|
||||
member_ids = [m['character_id'] for m in members]
|
||||
|
||||
# requesting too many ids per call results in a HTTP400
|
||||
# the swagger spec doesn't have a maxItems count
|
||||
@@ -121,8 +120,11 @@ class CorpStats(models.Model):
|
||||
m.main_character and int(m.main_character.character_id) == int(
|
||||
m.character_id)])
|
||||
|
||||
def visible_to(self, user):
|
||||
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
|
||||
|
||||
def can_update(self, user):
|
||||
return user.is_superuser or user == self.token.user
|
||||
return self.token.user == user or self.visible_to(user)
|
||||
|
||||
def corp_logo(self, size=128):
|
||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
|
||||
@@ -179,4 +181,4 @@ class CorpMember(models.Model):
|
||||
if item.startswith('portrait_url_'):
|
||||
size = item.strip('portrait_url_')
|
||||
return self.portrait_url(size)
|
||||
return super(CorpMember, self).__getattr__(item)
|
||||
return self.__getattribute__(item)
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
from celery import shared_task
|
||||
from allianceauth.corputils import CorpStats
|
||||
from allianceauth.corputils.models import CorpStats
|
||||
|
||||
|
||||
@shared_task
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<tr>
|
||||
<td class="text-center col-lg-6
|
||||
{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img
|
||||
class="ra-avatar" src="{{ corpstats.corp_logo }}"></td>
|
||||
class="ra-avatar" src="{{ corpstats.corp.logo_url_128 }}"></td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.alliance_logo }}">
|
||||
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_128 }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
@@ -70,6 +70,7 @@
|
||||
{% for alt in main.alts %}
|
||||
{% if forloop.first %}
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "Corporation" %}</th>
|
||||
<th class="text-center">{% trans "Alliance" %}</th>
|
||||
@@ -77,10 +78,15 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="text-center">{{ alt.character_name }}</td>
|
||||
<td class="text-center">{{ alt.corporation_name }}</td>
|
||||
<td class="text-center">{{ alt.alliance_name }}</td>
|
||||
<td class="text-center">
|
||||
<td class="text-center" style="width:5%">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="https://image.eveonline.com/Character/{{ alt.character_id }}_32.jpg" class="img-circle">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
||||
<td class="text-center" style="width:5%">
|
||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/"
|
||||
class="label label-danger" target="_blank">
|
||||
{% trans "Killboard" %}
|
||||
@@ -175,9 +181,25 @@
|
||||
{% endblock %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$('#table-mains').DataTable();
|
||||
$('#table-members').DataTable();
|
||||
$('#table-unregistered').DataTable();
|
||||
$('#table-mains').DataTable({
|
||||
"columnDefs": [
|
||||
{ "sortable": false, "targets": [1] },
|
||||
],
|
||||
});
|
||||
$('#table-members').DataTable({
|
||||
"columnDefs": [
|
||||
{ "searchable": false, "targets": [0, 2] },
|
||||
{ "sortable": false, "targets": [0, 2] },
|
||||
],
|
||||
"order": [[ 1, "asc" ]],
|
||||
});
|
||||
$('#table-unregistered').DataTable({
|
||||
"columnDefs": [
|
||||
{ "searchable": false, "targets": [0, 2] },
|
||||
{ "sortable": false, "targets": [0, 2] },
|
||||
],
|
||||
"order": [[ 1, "asc" ]],
|
||||
});
|
||||
|
||||
});
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -85,7 +85,7 @@ class CorpStatsUpdateTestCase(TestCase):
|
||||
@mock.patch('esi.clients.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.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'}]
|
||||
self.corpstats.update()
|
||||
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists())
|
||||
@@ -94,7 +94,7 @@ class CorpStatsUpdateTestCase(TestCase):
|
||||
def test_update_remove_member(self, SwaggerClient):
|
||||
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.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'}]
|
||||
self.corpstats.update()
|
||||
self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists())
|
||||
|
||||
@@ -17,7 +17,8 @@ SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sw
|
||||
|
||||
def access_corpstats_test(user):
|
||||
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
|
||||
@@ -62,7 +63,7 @@ def corpstats_view(request, corp_id=None):
|
||||
corpstats = get_object_or_404(CorpStats, corp=corp)
|
||||
|
||||
# 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
|
||||
if corpstats and corpstats not in available:
|
||||
|
||||
@@ -4,3 +4,6 @@ from django.apps import AppConfig
|
||||
class EveAutogroupsConfig(AppConfig):
|
||||
name = 'allianceauth.eveonline.autogroups'
|
||||
label = 'eve_autogroups'
|
||||
|
||||
def ready(self):
|
||||
import allianceauth.eveonline.autogroups.signals
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.5 on 2017-09-29 14:44
|
||||
# Generated by Django 1.11.6 on 2017-12-23 04:30
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
@@ -29,40 +29,31 @@ class Migration(migrations.Migration):
|
||||
('alliance_name_source', models.CharField(choices=[('ticker', 'Ticker'), ('name', 'Full name')], default='name', max_length=20)),
|
||||
('replace_spaces', models.BooleanField(default=False)),
|
||||
('replace_spaces_with', models.CharField(blank=True, default='', help_text='Any spaces in the group name will be replaced with this.', max_length=10)),
|
||||
('states', models.ManyToManyField(related_name='autogroups', to='authentication.State')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ManagedGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ManagedAllianceGroup',
|
||||
fields=[
|
||||
('managedgroup_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eve_autogroups.ManagedGroup')),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('alliance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eveonline.EveAllianceInfo')),
|
||||
('config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eve_autogroups.AutogroupsConfig')),
|
||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
|
||||
],
|
||||
bases=('eve_autogroups.managedgroup',),
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ManagedCorpGroup',
|
||||
fields=[
|
||||
('managedgroup_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eve_autogroups.ManagedGroup')),
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eve_autogroups.AutogroupsConfig')),
|
||||
('corp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eveonline.EveCorporationInfo')),
|
||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
|
||||
],
|
||||
bases=('eve_autogroups.managedgroup',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='managedgroup',
|
||||
name='config',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eve_autogroups.AutogroupsConfig'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='managedgroup',
|
||||
name='group',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group'),
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='autogroupsconfig',
|
||||
@@ -74,4 +65,9 @@ class Migration(migrations.Migration):
|
||||
name='corp_managed_groups',
|
||||
field=models.ManyToManyField(help_text="A list of corporation groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you're doing.", related_name='corp_managed_config', through='eve_autogroups.ManagedCorpGroup', to='auth.Group'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='autogroupsconfig',
|
||||
name='states',
|
||||
field=models.ManyToManyField(related_name='autogroups', to='authentication.State'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def get_users_for_state(state: State):
|
||||
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):
|
||||
@@ -39,7 +39,12 @@ class AutogroupsConfigManager(models.Manager):
|
||||
if state is None:
|
||||
state = user.profile.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):
|
||||
@@ -110,21 +115,24 @@ class AutogroupsConfig(models.Model):
|
||||
group = None
|
||||
try:
|
||||
if not self.alliance_groups or not self.user_entitled_to_groups(user):
|
||||
logger.debug('User {} does not have required state'.format(user))
|
||||
logger.debug('User {} does not have required state for alliance group membership'.format(user))
|
||||
return
|
||||
else:
|
||||
alliance = user.profile.main_character.alliance
|
||||
if alliance is None:
|
||||
logger.debug('User {} alliance is None, cannot update group membership'.format(user))
|
||||
return
|
||||
group = self.get_alliance_group(alliance)
|
||||
except EveAllianceInfo.DoesNotExist:
|
||||
logger.warning('User {} main characters alliance does not exist in the database.'
|
||||
' Group membership not updated'.format(user))
|
||||
logger.debug('User {} main characters alliance does not exist in the database. Creating.'.format(user))
|
||||
alliance = EveAllianceInfo.objects.create_alliance(user.profile.main_character.alliance_id)
|
||||
group = self.get_alliance_group(alliance)
|
||||
except AttributeError:
|
||||
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
||||
finally:
|
||||
self.remove_user_from_corp_groups(user, except_group=group)
|
||||
self.remove_user_from_alliance_groups(user, except_group=group)
|
||||
if group is not None:
|
||||
logger.debug('Adding user {} to alliance group {}'.format(user, group))
|
||||
user.groups.add(group)
|
||||
|
||||
@transaction.atomic
|
||||
@@ -132,18 +140,20 @@ class AutogroupsConfig(models.Model):
|
||||
group = None
|
||||
try:
|
||||
if not self.corp_groups or not self.user_entitled_to_groups(user):
|
||||
logger.debug('User {} does not have required state'.format(user))
|
||||
logger.debug('User {} does not have required state for corp group membership'.format(user))
|
||||
else:
|
||||
corp = user.profile.main_character.corporation
|
||||
group = self.get_corp_group(corp)
|
||||
except EveCorporationInfo.DoesNotExist:
|
||||
logger.warning('User {} main characters corporation does not exist in the database.'
|
||||
' Group membership not updated'.format(user))
|
||||
logger.debug('User {} main characters corporation does not exist in the database. Creating.'.format(user))
|
||||
corp = EveCorporationInfo.objects.create_corporation(user.profile.main_character.corporation_id)
|
||||
group = self.get_corp_group(corp)
|
||||
except AttributeError:
|
||||
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
|
||||
finally:
|
||||
self.remove_user_from_corp_groups(user, except_group=group)
|
||||
if group is not None:
|
||||
logger.debug('Adding user {} to corp group {}'.format(user, group))
|
||||
user.groups.add(group)
|
||||
|
||||
@transaction.atomic
|
||||
@@ -184,13 +194,15 @@ class AutogroupsConfig(models.Model):
|
||||
"""
|
||||
Deletes ALL managed alliance groups
|
||||
"""
|
||||
self.alliance_managed_groups.all().delete()
|
||||
for g in self.alliance_managed_groups.all():
|
||||
g.delete()
|
||||
|
||||
def delete_corp_managed_groups(self):
|
||||
"""
|
||||
Deletes ALL managed corp groups
|
||||
"""
|
||||
self.corp_managed_groups.all().delete()
|
||||
for g in self.corp_managed_groups.all():
|
||||
g.delete()
|
||||
|
||||
def get_alliance_group_name(self, alliance: EveAllianceInfo) -> str:
|
||||
if self.alliance_name_source == self.OPT_TICKER:
|
||||
@@ -225,6 +237,9 @@ class ManagedGroup(models.Model):
|
||||
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||
config = models.ForeignKey(AutogroupsConfig, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class ManagedCorpGroup(ManagedGroup):
|
||||
corp = models.ForeignKey(EveCorporationInfo, on_delete=models.CASCADE)
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||
from allianceauth.authentication.models import UserProfile, State
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
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.
|
||||
"""
|
||||
update_fields = kwargs.pop('update_fields', []) or []
|
||||
if 'main_character' in update_fields or 'state' in update_fields:
|
||||
AutogroupsConfig.objects.update_groups_for_user(instance.user)
|
||||
AutogroupsConfig.objects.update_groups_for_user(instance.user)
|
||||
|
||||
|
||||
@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:
|
||||
# Deleted States handled by the profile state change
|
||||
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 django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||
from allianceauth.authentication.models import UserProfile
|
||||
from allianceauth.authentication.signals import state_changed
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from allianceauth.authentication.signals import reassess_on_profile_save
|
||||
from .. import signals
|
||||
from ..models import AutogroupsConfig
|
||||
|
||||
@@ -14,6 +13,7 @@ def patch(target, *args, **kwargs):
|
||||
|
||||
|
||||
def connect_signals():
|
||||
post_save.connect(receiver=reassess_on_profile_save, sender=UserProfile)
|
||||
pre_save.connect(receiver=signals.pre_save_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)
|
||||
@@ -21,6 +21,7 @@ def connect_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_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
|
||||
post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
|
||||
|
||||
@@ -7,7 +7,7 @@ from . import patch
|
||||
|
||||
class AutogroupsConfigManagerTestCase(TestCase):
|
||||
|
||||
def test_update_groups_for_state(self, ):
|
||||
def test_update_groups_for_state(self):
|
||||
member = AuthUtils.create_member('test member')
|
||||
obj = AutogroupsConfig.objects.create()
|
||||
obj.states.add(member.profile.state)
|
||||
@@ -32,3 +32,23 @@ class AutogroupsConfigManagerTestCase(TestCase):
|
||||
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
||||
args, kwargs = update_group_membership_for_user.call_args
|
||||
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()
|
||||
|
||||
self.member = AuthUtils.create_member('test user')
|
||||
|
||||
state = AuthUtils.get_member_state()
|
||||
|
||||
self.alliance = EveAllianceInfo.objects.create(
|
||||
@@ -38,6 +36,8 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
state.member_alliances.add(self.alliance)
|
||||
state.member_corporations.add(self.corp)
|
||||
|
||||
self.member = AuthUtils.create_member('test user')
|
||||
|
||||
def tearDown(self):
|
||||
# Reconnect signals
|
||||
connect_signals()
|
||||
@@ -82,6 +82,7 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
|
||||
# Act
|
||||
obj.update_alliance_group_membership(self.member)
|
||||
obj.update_corp_group_membership(self.member) # check for no side effects
|
||||
|
||||
group = obj.create_alliance_group(self.alliance)
|
||||
group_qs = Group.objects.filter(pk=group.pk)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
|
||||
from ..models import AutogroupsConfig, ManagedAllianceGroup
|
||||
from ..models import AutogroupsConfig
|
||||
|
||||
from . import patch, disconnect_signals, connect_signals
|
||||
|
||||
@@ -13,8 +13,6 @@ from . import patch, disconnect_signals, connect_signals
|
||||
class SignalsTestCase(TestCase):
|
||||
def setUp(self):
|
||||
disconnect_signals()
|
||||
self.member = AuthUtils.create_member('test user')
|
||||
|
||||
state = AuthUtils.get_member_state()
|
||||
|
||||
self.char = EveCharacter.objects.create(
|
||||
@@ -27,9 +25,6 @@ class SignalsTestCase(TestCase):
|
||||
alliance_name='alliance name',
|
||||
)
|
||||
|
||||
self.member.profile.main_character = self.char
|
||||
self.member.profile.save()
|
||||
|
||||
self.alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3456',
|
||||
alliance_name='alliance name',
|
||||
@@ -47,13 +42,17 @@ class SignalsTestCase(TestCase):
|
||||
|
||||
state.member_alliances.add(self.alliance)
|
||||
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()
|
||||
|
||||
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
|
||||
def test_check_groups_on_profile_update_state(self, update_groups_for_user):
|
||||
# Trigger signal
|
||||
self.member.profile.state = AuthUtils.get_guest_state()
|
||||
self.member.profile.save()
|
||||
self.member.profile.assign_state(state=AuthUtils.get_guest_state())
|
||||
|
||||
self.assertTrue(update_groups_for_user.called)
|
||||
self.assertEqual(update_groups_for_user.call_count, 1)
|
||||
@@ -71,10 +70,10 @@ class SignalsTestCase(TestCase):
|
||||
alliance_id='3456',
|
||||
alliance_name='alliance name',
|
||||
)
|
||||
|
||||
# Trigger signal
|
||||
self.member.profile.main_character = char
|
||||
self.member.profile.save()
|
||||
|
||||
self.assertTrue(update_groups_for_user.called)
|
||||
self.assertEqual(update_groups_for_user.call_count, 1)
|
||||
args, kwargs = update_groups_for_user.call_args
|
||||
|
||||
@@ -26,6 +26,7 @@ class EveCharacterManager(models.Manager):
|
||||
corporation_ticker=character.corp.ticker,
|
||||
alliance_id=character.alliance.id,
|
||||
alliance_name=character.alliance.name,
|
||||
alliance_ticker=getattr(character.alliance, 'ticker', None),
|
||||
)
|
||||
|
||||
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),
|
||||
),
|
||||
]
|
||||
@@ -35,6 +35,15 @@ class EveAllianceInfo(models.Model):
|
||||
def __str__(self):
|
||||
return self.alliance_name
|
||||
|
||||
def logo_url(self, size=32):
|
||||
return "https://image.eveonline.com/Alliance/%s_%s.png" % (self.alliance_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('logo_url_'):
|
||||
size = item.strip('logo_url_')
|
||||
return self.logo_url(size)
|
||||
return self.__getattribute__(item)
|
||||
|
||||
|
||||
class EveCorporationInfo(models.Model):
|
||||
corporation_id = models.CharField(max_length=254, unique=True)
|
||||
@@ -60,15 +69,25 @@ class EveCorporationInfo(models.Model):
|
||||
def __str__(self):
|
||||
return self.corporation_name
|
||||
|
||||
def logo_url(self, size=32):
|
||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corporation_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('logo_url_'):
|
||||
size = item.strip('logo_url_')
|
||||
return self.logo_url(size)
|
||||
return self.__getattribute__(item)
|
||||
|
||||
|
||||
class EveCharacter(models.Model):
|
||||
character_id = models.CharField(max_length=254, unique=True)
|
||||
character_name = models.CharField(max_length=254, unique=True)
|
||||
corporation_id = 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_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()
|
||||
provider = EveCharacterProviderManager()
|
||||
@@ -102,8 +121,18 @@ class EveCharacter(models.Model):
|
||||
self.corporation_ticker = character.corp.ticker
|
||||
self.alliance_id = character.alliance.id
|
||||
self.alliance_name = character.alliance.name
|
||||
self.alliance_ticker = getattr(character.alliance, 'ticker', None)
|
||||
self.save()
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
return self.character_name
|
||||
|
||||
def portrait_url(self, size=32):
|
||||
return "https://image.eveonline.com/Character/%s_%s.jpg" % (self.character_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('portrait_url_'):
|
||||
size = item.strip('portrait_url_')
|
||||
return self.portrait_url(size)
|
||||
return self.__getattribute__(item)
|
||||
|
||||
@@ -81,7 +81,9 @@ class Alliance(Entity):
|
||||
|
||||
@property
|
||||
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):
|
||||
@@ -150,10 +152,10 @@ class EveSwaggerProvider(EveProvider):
|
||||
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
|
||||
model = Alliance(
|
||||
id=alliance_id,
|
||||
name=data['alliance_name'],
|
||||
name=data['name'],
|
||||
ticker=data['ticker'],
|
||||
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
|
||||
except HTTPNotFound:
|
||||
@@ -164,7 +166,7 @@ class EveSwaggerProvider(EveProvider):
|
||||
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
|
||||
model = Corporation(
|
||||
id=corp_id,
|
||||
name=data['corporation_name'],
|
||||
name=data['name'],
|
||||
ticker=data['ticker'],
|
||||
ceo_id=data['ceo_id'],
|
||||
members=data['member_count'],
|
||||
@@ -177,12 +179,11 @@ class EveSwaggerProvider(EveProvider):
|
||||
def get_character(self, character_id):
|
||||
try:
|
||||
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(
|
||||
id=character_id,
|
||||
name=data['name'],
|
||||
corp_id=data['corporation_id'],
|
||||
alliance_id=alliance_id,
|
||||
alliance_id=data['alliance_id'] if 'alliance_id' in data else None,
|
||||
)
|
||||
return model
|
||||
except (HTTPNotFound, HTTPUnprocessableEntity):
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,12 @@
|
||||
from . import urls
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
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:'])
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from allianceauth.optimer.models import OpTimer
|
||||
|
||||
|
||||
class FatlinkForm(forms.Form):
|
||||
fatname = forms.CharField(label=_('Name of fat-link'), required=True)
|
||||
fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
|
||||
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1,
|
||||
max_value=2147483647)
|
||||
fleet = forms.ModelChoiceField(label=_("Fleet"), queryset=OpTimer.objects.all().order_by('operation_name'))
|
||||
max_value=2147483647, help_text=_('minutes'))
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
from django.utils import timezone
|
||||
|
||||
import allianceauth.fleetactivitytracking.models
|
||||
|
||||
@@ -36,9 +34,9 @@ class Migration(migrations.Migration):
|
||||
name='Fatlink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('fatdatetime', models.DateTimeField(default=datetime.datetime(2016, 9, 5, 21, 39, 17, 307954, tzinfo=utc))),
|
||||
('fatdatetime', models.DateTimeField(default=timezone.now)),
|
||||
('duration', models.PositiveIntegerField()),
|
||||
('fleet', models.CharField(default=b'', max_length=254)),
|
||||
('fleet', models.CharField(default='', max_length=254)),
|
||||
('name', models.CharField(max_length=254)),
|
||||
('hash', models.CharField(max_length=254, unique=True)),
|
||||
('creator', models.ForeignKey(on_delete=models.SET(
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 2.0.2 on 2018-02-28 18:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('fleetactivitytracking', '0004_make_strings_more_stringy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='fatlink',
|
||||
name='name',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fatlink',
|
||||
name='fleet',
|
||||
field=models.CharField(max_length=254),
|
||||
),
|
||||
]
|
||||
@@ -12,13 +12,12 @@ def get_sentinel_user():
|
||||
class Fatlink(models.Model):
|
||||
fatdatetime = models.DateTimeField(default=timezone.now)
|
||||
duration = models.PositiveIntegerField()
|
||||
fleet = models.CharField(max_length=254, default="")
|
||||
name = models.CharField(max_length=254)
|
||||
fleet = models.CharField(max_length=254)
|
||||
hash = models.CharField(max_length=254, unique=True)
|
||||
creator = models.ForeignKey(User, on_delete=models.SET(get_sentinel_user))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return self.fleet
|
||||
|
||||
|
||||
class Fat(models.Model):
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">{% trans "Character not registered!" %}</div>
|
||||
{% trans "This character is not part of any registered API-key. You must go to" %} <a href=" {% url 'auth_api_key_management' %}">{% trans "API key management</a> and add an API with the character on before being able to click fleet attendance links." %}
|
||||
{% trans "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% trans "Add it here" %}</a> {% trans "before attempting to click fleet attendance links." %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
{% if char_id %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% trans "Previous month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% trans "Next month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% trans "Previous month" %}</a>
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% trans "Next month" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} links this month.{% endblocktrans %}</h2>
|
||||
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} link{{ n_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-2 text-center">{% trans "Ship" %}</th>
|
||||
@@ -29,26 +29,24 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} links this month.{% endblocktrans %}</h2>
|
||||
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} link{{ n_created_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Name" %}</th>
|
||||
<th class="text-center">{% trans "Creator" %}</th>
|
||||
<th class="text-center">{% trans "Fleet" %}</th>
|
||||
<th class="text-center">{% trans "Creator" %}</th>
|
||||
<th class="text-center">{% trans "Eve Time" %}</th>
|
||||
<th class="text-center">{% trans "Duration" %}</th>
|
||||
<th class="text-center">{% trans "Edit" %}</th>
|
||||
</tr>
|
||||
{% for link in created_fats %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'auth_click_fatlink_view' %}{{ link.hash }}/{{ link.name }}">{{ link.name }}</a></td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'auth_modify_fatlink_view' %}{{ link.hash }}/{{ link.name }}">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}">
|
||||
<button type="button" class="btn btn-info"><span
|
||||
class="glyphicon glyphicon-edit"></span></button>
|
||||
</a>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{% if fats %}
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "fatname" %}</th>
|
||||
<th class="text-center">{% trans "Fleet" %}</th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "System" %}</th>
|
||||
<th class="text-center">{% trans "Ship" %}</th>
|
||||
@@ -32,7 +32,7 @@
|
||||
</tr>
|
||||
{% for fat in fats %}
|
||||
<tr>
|
||||
<td class="text-center">{{ fat.fatlink.name }}</td>
|
||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||
@@ -79,13 +79,13 @@
|
||||
</tr>
|
||||
{% for link in fatlinks %}
|
||||
<tr>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click_fatlink' %}{{ link.hash }}/{{ link.name }}">{{ link.name }}</a></td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
||||
<td class="text-center">{{ link.creator.username }}</td>
|
||||
<td class="text-center">{{ link.fleet }}</td>
|
||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||
<td class="text-center">{{ link.duration }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'fatlink:modify' %}{{ link.hash }}/{{ link.name }}" class="btn btn-info">
|
||||
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-edit"></span>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -25,10 +25,6 @@ urlpatterns = [
|
||||
views.fatlink_monthly_personal_statistics_view,
|
||||
name='user_statistics_month'),
|
||||
url(r'^create/$', views.create_fatlink_view, name='create'),
|
||||
url(r'^modify/$', views.modify_fatlink_view, name='modify'),
|
||||
url(r'^modify/(?P<hash>[a-zA-Z0-9_-]+)/([a-z0-9_-]+)$',
|
||||
views.modify_fatlink_view),
|
||||
url(r'^link/$', views.fatlink_view, name='click_fatlink'),
|
||||
url(r'^link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$',
|
||||
views.click_fatlink_view),
|
||||
url(r'^modify/(?P<fat_hash>[a-zA-Z0-9_-]+)/$', views.modify_fatlink_view, name='modify'),
|
||||
url(r'^link/(?P<fat_hash>[a-zA-Z0-9]+)/$', views.click_fatlink_view, name='click'),
|
||||
]
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership
|
||||
from django.contrib import messages
|
||||
@@ -17,7 +15,7 @@ from esi.decorators import token_required
|
||||
from allianceauth.eveonline.providers import provider
|
||||
from .forms import FatlinkForm
|
||||
from .models import Fatlink, Fat
|
||||
from slugify import slugify
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
from allianceauth.eveonline.models import EveAllianceInfo
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
@@ -181,7 +179,7 @@ def fatlink_personal_statistics_view(request, year=datetime.date.today().year):
|
||||
|
||||
personal_fats = Fat.objects.select_related('fatlink').filter(user=user).order_by('id')
|
||||
|
||||
monthlystats = [0 for month in range(1, 13)]
|
||||
monthlystats = [0 for i in range(1, 13)]
|
||||
|
||||
for fat in personal_fats:
|
||||
fatdate = fat.fatlink.fatdatetime
|
||||
@@ -236,8 +234,8 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
||||
@login_required
|
||||
@token_required(
|
||||
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
||||
def click_fatlink_view(request, token, hash, fatname):
|
||||
fatlink = get_object_or_404(Fatlink, hash=hash, name=fatname)
|
||||
def click_fatlink_view(request, token, fat_hash=None):
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
|
||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
||||
|
||||
@@ -298,12 +296,11 @@ def create_fatlink_view(request):
|
||||
logger.debug("Submitting fleetactivitytracking by user %s" % request.user)
|
||||
if form.is_valid():
|
||||
fatlink = Fatlink()
|
||||
fatlink.name = slugify(form.cleaned_data["fatname"])
|
||||
fatlink.fleet = form.cleaned_data["fleet"]
|
||||
fatlink.duration = form.cleaned_data["duration"]
|
||||
fatlink.fatdatetime = timezone.now()
|
||||
fatlink.creator = request.user
|
||||
fatlink.hash = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(10))
|
||||
fatlink.hash = get_random_string(length=15)
|
||||
try:
|
||||
fatlink.full_clean()
|
||||
fatlink.save()
|
||||
@@ -331,25 +328,19 @@ def create_fatlink_view(request):
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.fleetactivitytracking')
|
||||
def modify_fatlink_view(request, hash=""):
|
||||
def modify_fatlink_view(request, fat_hash=None):
|
||||
logger.debug("modify_fatlink_view called by user %s" % request.user)
|
||||
if not hash:
|
||||
return redirect('fatlink:view')
|
||||
|
||||
try:
|
||||
fatlink = Fatlink.objects.get(hash=hash)
|
||||
except Fatlink.DoesNotExist:
|
||||
raise Http404
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
|
||||
if request.GET.get('removechar', None):
|
||||
character_id = request.GET.get('removechar')
|
||||
character = EveCharacter.objects.get(character_id=character_id)
|
||||
logger.debug("Removing character %s from fleetactivitytracking %s" % (character.character_name, fatlink.name))
|
||||
logger.debug("Removing character %s from fleetactivitytracking %s" % (character.character_name, fatlink))
|
||||
|
||||
Fat.objects.filter(fatlink=fatlink).filter(character=character).delete()
|
||||
|
||||
if request.GET.get('deletefat', None):
|
||||
logger.debug("Removing fleetactivitytracking %s" % fatlink.name)
|
||||
logger.debug("Removing fleetactivitytracking %s" % fatlink)
|
||||
fatlink.delete()
|
||||
return redirect('fatlink:view')
|
||||
|
||||
|
||||
@@ -1,28 +1,68 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from django.contrib.auth.models import Group as BaseGroup
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
from .models import AuthGroup
|
||||
from .models import GroupRequest
|
||||
|
||||
|
||||
class AuthGroupAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin model for AuthGroup
|
||||
"""
|
||||
class AuthGroupInlineAdmin(admin.StackedInline):
|
||||
model = AuthGroup
|
||||
filter_horizontal = ('group_leaders',)
|
||||
fields = ('description', 'group_leaders', 'internal', 'hidden', 'open', 'public')
|
||||
verbose_name_plural = 'Auth Settings'
|
||||
verbose_name = ''
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.change_group')
|
||||
|
||||
|
||||
class ProxyGroup(Group):
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
filter_horizontal = ('permissions',)
|
||||
inlines = (AuthGroupInlineAdmin,)
|
||||
|
||||
|
||||
class Group(BaseGroup):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = Group._meta.verbose_name
|
||||
verbose_name_plural = Group._meta.verbose_name_plural
|
||||
verbose_name = BaseGroup._meta.verbose_name
|
||||
verbose_name_plural = BaseGroup._meta.verbose_name_plural
|
||||
|
||||
try:
|
||||
admin.site.unregister(Group)
|
||||
admin.site.unregister(BaseGroup)
|
||||
finally:
|
||||
admin.site.register(ProxyGroup)
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
|
||||
|
||||
admin.site.register(GroupRequest)
|
||||
admin.site.register(AuthGroup, AuthGroupAdmin)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Group)
|
||||
def redirect_pre_save(sender, signal=None, *args, **kwargs):
|
||||
pre_save.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group)
|
||||
def redirect_post_save(sender, signal=None, *args, **kwargs):
|
||||
post_save.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Group)
|
||||
def redirect_pre_delete(sender, signal=None, *args, **kwargs):
|
||||
pre_delete.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Group)
|
||||
def redirect_post_delete(sender, signal=None, *args, **kwargs):
|
||||
post_delete.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Group.permissions.through)
|
||||
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseGroup, *args, **kwargs)
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class GroupManagementConfig(AppConfig):
|
||||
name = 'allianceauth.groupmanagement'
|
||||
label = 'groupmanagement'
|
||||
verbose_name = 'Group Management'
|
||||
|
||||
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProxyGroup',
|
||||
name='Group',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.10 on 2018-02-23 23:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def delete_permissions(apps, schema_editor):
|
||||
AuthGroup = apps.get_model('groupmanagement', 'AuthGroup')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(AuthGroup)
|
||||
Permission.objects.filter(content_type=ct).delete()
|
||||
|
||||
|
||||
def recreate_permissions(apps, schema_editor):
|
||||
AuthGroup = apps.get_model('groupmanagement', 'AuthGroup')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(AuthGroup)
|
||||
Permission.objects.create(content_type=ct, name='Can add auth group', codename='add_authgroup')
|
||||
Permission.objects.create(content_type=ct, name='Can delete auth group', codename='delete_authgroup')
|
||||
Permission.objects.create(content_type=ct, name='Can change auth group', codename='change_authgroup')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('groupmanagement', '0007_on_delete'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='authgroup',
|
||||
options={'default_permissions': (), 'permissions': (('request_groups', 'Can request non-public groups'),)},
|
||||
),
|
||||
migrations.RunPython(delete_permissions, recreate_permissions)
|
||||
]
|
||||
@@ -4,8 +4,6 @@ from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
|
||||
class GroupRequest(models.Model):
|
||||
status = models.CharField(max_length=254)
|
||||
@@ -76,6 +74,7 @@ class AuthGroup(models.Model):
|
||||
permissions = (
|
||||
("request_groups", u"Can request non-public groups"),
|
||||
)
|
||||
default_permissions = tuple()
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group)
|
||||
|
||||
@@ -303,7 +303,6 @@ def group_request_add(request, group_id):
|
||||
grouprequest.status = _('Pending')
|
||||
grouprequest.group = group
|
||||
grouprequest.user = request.user
|
||||
grouprequest.main_char = request.user.profile.main_character
|
||||
grouprequest.leave_request = False
|
||||
grouprequest.save()
|
||||
logger.info("Created group request for user %s to group %s" % (request.user, Group.objects.get(id=group_id)))
|
||||
@@ -333,7 +332,6 @@ def group_request_leave(request, group_id):
|
||||
grouprequest.status = _('Pending')
|
||||
grouprequest.group = group
|
||||
grouprequest.user = request.user
|
||||
grouprequest.main_char = request.user.profile.main_character
|
||||
grouprequest.leave_request = True
|
||||
grouprequest.save()
|
||||
logger.info("Created group leave request for user %s to group %s" % (request.user, Group.objects.get(id=group_id)))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from allianceauth.hrapplications import urls
|
||||
|
||||
@@ -7,7 +7,7 @@ from allianceauth.hrapplications import urls
|
||||
class ApplicationsMenu(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
'Applications',
|
||||
_('Applications'),
|
||||
'fa fa-file-o fa-fw',
|
||||
'hrapplications:index',
|
||||
navactive=['hrapplications:'])
|
||||
|
||||
@@ -154,6 +154,12 @@
|
||||
class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-eye-open"></span>
|
||||
</a>
|
||||
{% if perms.hrapplications.delete_application %}
|
||||
<a href="{% url 'hrapplications:remove' app.id %}"
|
||||
class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -13,19 +13,19 @@ urlpatterns = [
|
||||
name="create_view"),
|
||||
url(r'^remove/(\w+)', views.hr_application_remove,
|
||||
name="remove"),
|
||||
url(r'view/(\w+)', views.hr_application_view,
|
||||
url(r'^view/(\w+)', views.hr_application_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"),
|
||||
url(r'personal/removal/(\w+)',
|
||||
url(r'^personal/removal/(\w+)',
|
||||
views.hr_application_personal_removal,
|
||||
name="personal_removal"),
|
||||
url(r'approve/(\w+)', views.hr_application_approve,
|
||||
url(r'^approve/(\w+)', views.hr_application_approve,
|
||||
name="approve"),
|
||||
url(r'reject/(\w+)', views.hr_application_reject,
|
||||
url(r'^reject/(\w+)', views.hr_application_reject,
|
||||
name="reject"),
|
||||
url(r'search/', views.hr_application_search,
|
||||
url(r'^search/', views.hr_application_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"),
|
||||
]
|
||||
|
||||
@@ -67,7 +67,7 @@ def hr_application_create_view(request, form_id=None):
|
||||
""))
|
||||
response.save()
|
||||
logger.info("%s created %s" % (request.user, application))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:personal_view', application.id)
|
||||
else:
|
||||
questions = app_form.questions.all()
|
||||
return render(request, 'hrapplications/create.html',
|
||||
@@ -95,7 +95,7 @@ def hr_application_personal_view(request, app_id):
|
||||
return render(request, 'hrapplications/view.html', context=context)
|
||||
else:
|
||||
logger.warn("User %s not authorized to view %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:personal_view')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -110,7 +110,7 @@ def hr_application_personal_removal(request, app_id):
|
||||
logger.warn("User %s attempting to delete reviewed app %s" % (request.user, app))
|
||||
else:
|
||||
logger.warn("User %s not authorized to delete %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -158,7 +158,7 @@ def hr_application_remove(request, app_id):
|
||||
logger.info("User %s deleting %s" % (request.user, app))
|
||||
app.delete()
|
||||
notify(app.user, "Application Deleted", message="Your application to %s was deleted." % app.form.corp)
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -175,7 +175,7 @@ def hr_application_approve(request, app_id):
|
||||
level="success")
|
||||
else:
|
||||
logger.warn("User %s not authorized to approve %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -192,7 +192,7 @@ def hr_application_reject(request, app_id):
|
||||
level="danger")
|
||||
else:
|
||||
logger.warn("User %s not authorized to reject %s" % (request.user, app))
|
||||
return redirect('hrapplications:view')
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
BIN
allianceauth/locale/de/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/de/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
@@ -1096,52 +1096,57 @@ msgid "Main Navigation"
|
||||
msgstr "Haupmenü"
|
||||
|
||||
#: stock/templates/registered/base.html:106
|
||||
msgid " Dashboard"
|
||||
msgstr " Dashboard"
|
||||
#: stock/templates/registered/dashboard.html:5
|
||||
#: stock/templates/registered/dashboard.html:9
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
|
||||
#: stock/templates/registered/base.html:113
|
||||
msgid " Groups"
|
||||
msgstr " Gruppen"
|
||||
#: stock/templates/registered/dashboard.html:52
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: stock/templates/registered/base.html:120
|
||||
msgid " Help"
|
||||
msgstr " Hilfe"
|
||||
msgid "Help"
|
||||
msgstr "Hilfe"
|
||||
|
||||
#: stock/templates/registered/base.html:126
|
||||
msgid "Aux Navigation"
|
||||
msgstr "Zusatz Navigation"
|
||||
|
||||
#: stock/templates/registered/base.html:131
|
||||
msgid " Services"
|
||||
msgstr " Dienste"
|
||||
msgid "Services"
|
||||
msgstr "Dienste"
|
||||
|
||||
#: stock/templates/registered/base.html:140
|
||||
msgid " Applications"
|
||||
msgstr " Bewerbungen"
|
||||
msgid "Applications"
|
||||
msgstr "Bewerbungen"
|
||||
|
||||
#: stock/templates/registered/base.html:148
|
||||
msgid " Corporation Stats"
|
||||
msgstr " Korporationsstatistiken"
|
||||
msgid "Corporation Stats"
|
||||
msgstr "Korporationsstatistiken"
|
||||
|
||||
#: stock/templates/registered/base.html:156
|
||||
msgid " Group Management"
|
||||
msgstr " Gruppenverwaltung"
|
||||
#: stock/templates/registered/groupmanagementmenu.html:14
|
||||
msgid "Group Management"
|
||||
msgstr "Gruppenverwaltung"
|
||||
|
||||
#: stock/templates/registered/base.html:174
|
||||
msgid " Fleet Operations"
|
||||
msgstr " Flottenoperationen"
|
||||
msgid "Fleet Operations"
|
||||
msgstr "Flottenoperationen"
|
||||
|
||||
#: stock/templates/registered/base.html:181
|
||||
msgid " Structure Timers"
|
||||
msgstr " Strukturen Timer"
|
||||
#: stock/templates/registered/timermanagement.html:13
|
||||
msgid "Structure Timers"
|
||||
msgstr "Strukturen Timer"
|
||||
|
||||
#: stock/templates/registered/base.html:188
|
||||
msgid " Fleet Activity Tracking"
|
||||
msgstr " Flottenaktivitäts-Tracking"
|
||||
msgid "Fleet Activity Tracking"
|
||||
msgstr "Flottenaktivitäts-Tracking"
|
||||
|
||||
#: stock/templates/registered/base.html:194
|
||||
msgid " Ship Replacement"
|
||||
msgstr " Schiff’s erstattung"
|
||||
msgid "Ship Replacement"
|
||||
msgstr "Schiff’s erstattung"
|
||||
|
||||
#: stock/templates/registered/base.html:200
|
||||
msgid "Util"
|
||||
@@ -1156,7 +1161,7 @@ msgid "Change Password"
|
||||
msgstr "Passwort ändern"
|
||||
|
||||
#: stock/templates/registered/base.html:211
|
||||
msgid " Fleet Broadcast Formatter"
|
||||
msgid "Fleet Broadcast Formatter"
|
||||
msgstr "Flottenübertragungen Formatierer "
|
||||
|
||||
#: stock/templates/public/login.html:57
|
||||
@@ -1293,11 +1298,6 @@ msgstr "Corporation: "
|
||||
msgid "Corporation Ticker: "
|
||||
msgstr "Corporation Ticker: "
|
||||
|
||||
#: stock/templates/registered/dashboard.html:5
|
||||
#: stock/templates/registered/dashboard.html:9
|
||||
msgid "Dashboard"
|
||||
msgstr "Dashboard"
|
||||
|
||||
#: stock/templates/registered/dashboard.html:38
|
||||
msgid "Missing main character model."
|
||||
msgstr "Hauptcharakter model fehlt"
|
||||
@@ -1310,10 +1310,6 @@ msgstr "API Key hinzufügen"
|
||||
msgid "Change Main"
|
||||
msgstr "Hauptcharakter ändern"
|
||||
|
||||
#: stock/templates/registered/dashboard.html:52
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: stock/templates/registered/dashboard.html:71
|
||||
msgid "API Key requires EVE SSO verification"
|
||||
msgstr "API Key benötigt EVE SSO bestätigung"
|
||||
@@ -1601,10 +1597,6 @@ msgstr "Keine Gruppenbeitrittsanfragen."
|
||||
msgid "No group leave requests."
|
||||
msgstr "Keine Gruppenaustrittsanfragen"
|
||||
|
||||
#: stock/templates/registered/groupmanagementmenu.html:14
|
||||
msgid "Group Management"
|
||||
msgstr "Gruppenmanagement"
|
||||
|
||||
#: stock/templates/registered/groupmanagementmenu.html:19
|
||||
msgid "Group Requests"
|
||||
msgstr "Gruppen anfragen"
|
||||
@@ -2171,10 +2163,6 @@ msgstr "Server beitreten"
|
||||
msgid "Structure Timer Management"
|
||||
msgstr "Struktur-Timer Verwaltung"
|
||||
|
||||
#: stock/templates/registered/timermanagement.html:13
|
||||
msgid "Structure Timers"
|
||||
msgstr "Struktur-Timer"
|
||||
|
||||
#: stock/templates/registered/timermanagement.html:26
|
||||
msgid "Corp Timers"
|
||||
msgstr "Corp Timer"
|
||||
|
||||
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 django.utils.translation import ugettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from . import urls
|
||||
|
||||
|
||||
class OpTimerboardMenu(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self, 'Fleet Operations',
|
||||
MenuItemHook.__init__(self, _('Fleet Operations'),
|
||||
'fa fa-exclamation fa-fw',
|
||||
'optimer:view',
|
||||
navactive=['optimer:'])
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
<div class="col-lg-12 text-center row">
|
||||
<div class="label label-info text-left">
|
||||
<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 />
|
||||
</div>
|
||||
|
||||
@@ -111,7 +112,7 @@
|
||||
}
|
||||
|
||||
function updateClock() {
|
||||
document.getElementById("current-time").innerHTML = "<b>" + moment.utc().format('LLLL') + "</b>";
|
||||
document.getElementById("current-time").innerHTML = getCurrentEveTimeString();
|
||||
}
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -8,6 +8,7 @@ from allianceauth.tests.auth_utils import AuthUtils
|
||||
class PermissionsToolViewsTestCase(WebTest):
|
||||
def setUp(self):
|
||||
self.member = AuthUtils.create_member('auth_member')
|
||||
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
|
||||
self.member.email = 'auth_member@example.com'
|
||||
self.member.save()
|
||||
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
from .celery import app as celery_app
|
||||
|
||||
@@ -11,6 +11,10 @@ app = Celery('{{ project_name }}')
|
||||
# Using a string here means the worker don't have to serialize
|
||||
# the configuration object to child processes.
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.conf.ONCE = {
|
||||
'backend': 'allianceauth.services.tasks.DjangoBackend',
|
||||
'settings': {}
|
||||
}
|
||||
|
||||
# Load task modules from all registered Django app configs.
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
Django settings for alliance_auth project.
|
||||
DO NOT EDIT THIS FILE
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.10.1.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
This settings file contains everything needed for Alliance Auth projects to function.
|
||||
It gets overwritten by the 'allianceauth update' command.
|
||||
If you wish to make changes, overload the setting in your project's settings file (local.py).
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -17,7 +13,6 @@ from django.contrib import messages
|
||||
from celery.schedules import crontab
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# Core apps - required to function
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
@@ -38,17 +33,19 @@ INSTALLED_APPS = [
|
||||
'allianceauth.thirdparty.navhelper',
|
||||
]
|
||||
|
||||
SECRET_KEY = "wow I'm a really bad default secret key"
|
||||
|
||||
# Celery configuration
|
||||
BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'esi_cleanup_callbackredirect': {
|
||||
'task': 'esi.tasks.cleanup_callbackredirect',
|
||||
'schedule': crontab(hour='*/4'),
|
||||
'schedule': crontab(minute=0, hour='*/4'),
|
||||
},
|
||||
'esi_cleanup_token': {
|
||||
'task': 'esi.tasks.cleanup_token',
|
||||
'schedule': crontab(day_of_month='*/1'),
|
||||
'schedule': crontab(minute=0, hour=0),
|
||||
},
|
||||
'run_model_update': {
|
||||
'task': 'allianceauth.eveonline.tasks.run_model_update',
|
||||
@@ -56,7 +53,7 @@ CELERYBEAT_SCHEDULE = {
|
||||
},
|
||||
'check_all_character_ownership': {
|
||||
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
|
||||
'schedule': crontab(hour='*/4'),
|
||||
'schedule': crontab(minute=0, hour='*/4'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,12 +82,13 @@ ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
('es', ugettext('Spanish')),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [os.path.join(PROJECT_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
@@ -148,8 +146,11 @@ USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(PROJECT_DIR, 'static'),
|
||||
]
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
# Bootstrap messaging css workaround
|
||||
MESSAGE_TAGS = {
|
||||
@@ -166,7 +167,6 @@ CACHES = {
|
||||
}
|
||||
}
|
||||
|
||||
SECRET_KEY = 'this is a very bad secret key you should change'
|
||||
DEBUG = True
|
||||
ALLOWED_HOSTS = ['*']
|
||||
DATABASES = {
|
||||
@@ -178,40 +178,22 @@ DATABASES = {
|
||||
|
||||
SITE_NAME = 'Alliance Auth'
|
||||
|
||||
#################
|
||||
# Login Settings
|
||||
#################
|
||||
# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified
|
||||
# LOGOUT_REDIRECT_URL - destination after logging out
|
||||
LOGIN_URL = 'auth_login_user' # view that handles login logic
|
||||
|
||||
LOGIN_REDIRECT_URL = 'authentication:dashboard' # default destination when logging in if no redirect specified
|
||||
LOGOUT_REDIRECT_URL = 'authentication:dashboard' # destination after logging out
|
||||
# Both of these redirects accept values as per the django redirect shortcut
|
||||
# https://docs.djangoproject.com/en/1.11/topics/http/shortcuts/#redirect
|
||||
# - url names eg 'authentication:dashboard'
|
||||
# - relative urls eg '/dashboard'
|
||||
# - absolute urls eg 'http://example.com/dashboard'
|
||||
# LOGIN_TOKEN_SCOPES - scopes required on new tokens when logging in. Cannot be blank.
|
||||
# ACCOUNT_ACTIVATION_DAYS - number of days email verification tokens are valid for
|
||||
##################
|
||||
LOGIN_URL = 'auth_login_user'
|
||||
LOGIN_REDIRECT_URL = 'authentication:dashboard'
|
||||
LOGOUT_REDIRECT_URL = 'authentication:dashboard'
|
||||
LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1']
|
||||
|
||||
# scopes required on new tokens when logging in. Cannot be blank.
|
||||
LOGIN_TOKEN_SCOPES = ['publicData']
|
||||
|
||||
# number of days email verification links are valid for
|
||||
ACCOUNT_ACTIVATION_DAYS = 1
|
||||
|
||||
#####################################################
|
||||
##
|
||||
## Logging Configuration
|
||||
##
|
||||
#####################################################
|
||||
# Set log_file and console level to desired state:
|
||||
# DEBUG - basically stack trace, explains every step
|
||||
# INFO - model creation, deletion, updates, etc
|
||||
# WARN - unexpected function outcomes that do not impact user
|
||||
# ERROR - unexcpeted function outcomes which prevent user from achieving desired outcome
|
||||
# EXCEPTION - something critical went wrong, unhandled
|
||||
#####################################
|
||||
# Recommended level for log_file is INFO, console is DEBUG
|
||||
# Change log level of individual apps below to narrow your debugging
|
||||
#####################################
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
@@ -253,5 +235,9 @@ LOGGING = {
|
||||
'handlers': ['log_file', 'console'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
'esi': {
|
||||
'handlers': ['log_file', 'console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,60 @@
|
||||
# Every setting in base.py can be overloaded by redefining it here.
|
||||
from .base import *
|
||||
|
||||
# These are required for Django to function properly
|
||||
# These are required for Django to function properly. Don't touch.
|
||||
ROOT_URLCONF = '{{ project_name }}.urls'
|
||||
WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(PROJECT_DIR, 'static'),
|
||||
]
|
||||
STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||
TEMPLATES[0]['DIRS'] += [os.path.join(PROJECT_DIR, 'templates')]
|
||||
SECRET_KEY = '{{ secret_key }}'
|
||||
|
||||
# Change this to change the name of the auth site
|
||||
# This is where css/images will be placed for your webserver to read
|
||||
STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||
|
||||
# Change this to change the name of the auth site displayed
|
||||
# in page titles and the site header.
|
||||
SITE_NAME = '{{ project_name }}'
|
||||
|
||||
# Change this to enable/disable debug mode
|
||||
# Change this to enable/disable debug mode, which displays
|
||||
# useful error messages but can leak sensitive data.
|
||||
DEBUG = False
|
||||
|
||||
#######################################
|
||||
# Database Settings #
|
||||
#######################################
|
||||
# Uncomment and change the database name
|
||||
# and credentials to use MySQL/MariaDB.
|
||||
# Leave commented to use sqlite3
|
||||
#######################################
|
||||
"""
|
||||
# Add any additional apps to this list.
|
||||
INSTALLED_APPS += [
|
||||
|
||||
]
|
||||
|
||||
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_auth',
|
||||
'USER': os.environ.get('AA_DB_DEFAULT_USER', ''),
|
||||
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', ''),
|
||||
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_DEFAULT_PORT', '3306'),
|
||||
'USER': '',
|
||||
'PASSWORD': '',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '3306',
|
||||
}
|
||||
"""
|
||||
|
||||
######################################
|
||||
# SSO Settings #
|
||||
######################################
|
||||
# Register an application at
|
||||
# https://developers.eveonline.com
|
||||
# and fill out these settings.
|
||||
# Be sure to set the callback URL to
|
||||
# https://example.com/sso/callback
|
||||
# substituting your domain for example.com
|
||||
######################################
|
||||
# Register an application at https://developers.eveonline.com for Authentication
|
||||
# & API Access and fill out these settings. Be sure to set the callback URL
|
||||
# to https://example.com/sso/callback substituting your domain for example.com
|
||||
# Logging in to auth requires the publicData scope (can be overridden through the
|
||||
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
|
||||
ESI_SSO_CLIENT_ID = ''
|
||||
ESI_SSO_CLIENT_SECRET = ''
|
||||
ESI_SSO_CALLBACK_URL = ''
|
||||
|
||||
######################################
|
||||
# Email Settings #
|
||||
######################################
|
||||
# Alliance Auth validates emails before
|
||||
# new users can log in.
|
||||
# It's recommended to use a free service
|
||||
# like SparkPost or Mailgun to send email.
|
||||
# By default emails are validated before new users can log in.
|
||||
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
|
||||
# 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'
|
||||
# Email validation can be turned off by uncommenting the line below. This can break some services.
|
||||
# REGISTRATION_VERIFY_EMAIL = False
|
||||
EMAIL_HOST = ''
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_HOST_USER = ''
|
||||
EMAIL_HOST_PASSWORD = ''
|
||||
EMAIL_USE_TLS = True
|
||||
DEFAULT_FROM_EMAIL = ''
|
||||
|
||||
######################################
|
||||
# Add any custom settings below here #
|
||||
######################################
|
||||
#######################################
|
||||
# Add any custom settings below here. #
|
||||
#######################################
|
||||
|
||||
@@ -9,12 +9,19 @@ class NameFormatConfigForm(forms.ModelForm):
|
||||
super(NameFormatConfigForm, self).__init__(*args, **kwargs)
|
||||
SERVICE_CHOICES = [(s.name, s.name) for h in hooks.get_hooks('services_hook') for s in [h()]]
|
||||
if self.instance.id:
|
||||
SERVICE_CHOICES.append((self.instance.field, self.instance.field))
|
||||
current_choice = (self.instance.service_name, self.instance.service_name)
|
||||
if current_choice not in SERVICE_CHOICES:
|
||||
SERVICE_CHOICES.append(current_choice)
|
||||
self.fields['service_name'] = forms.ChoiceField(choices=SERVICE_CHOICES)
|
||||
|
||||
|
||||
class NameFormatConfigAdmin(admin.ModelAdmin):
|
||||
form = NameFormatConfigForm
|
||||
list_display = ('service_name', 'get_state_display_string')
|
||||
|
||||
def get_state_display_string(self, obj):
|
||||
return ', '.join([state.name for state in obj.states.all()])
|
||||
get_state_display_string.short_description = 'States'
|
||||
|
||||
|
||||
admin.site.register(NameFormatConfig, NameFormatConfigAdmin)
|
||||
|
||||
@@ -6,4 +6,4 @@ class ServicesConfig(AppConfig):
|
||||
label = 'services'
|
||||
|
||||
def ready(self):
|
||||
pass
|
||||
from . import signals
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from django.conf.urls import include, url
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.conf import settings
|
||||
from string import Formatter
|
||||
|
||||
@@ -160,15 +159,12 @@ class NameFormatter:
|
||||
'corp_id': getattr(main_char, 'corporation_id', None),
|
||||
'alliance_name': getattr(main_char, 'alliance_name', None),
|
||||
'alliance_id': getattr(main_char, 'alliance_id', None),
|
||||
'alliance_ticker': getattr(main_char, 'alliance_ticker', None),
|
||||
'username': self.user.username,
|
||||
}
|
||||
|
||||
if main_char is not None and 'alliance_ticker' in self.string_formatter:
|
||||
# Reduces db lookups
|
||||
try:
|
||||
format_data['alliance_ticker'] = getattr(getattr(main_char, 'alliance', None), 'alliance_ticker', None)
|
||||
except ObjectDoesNotExist:
|
||||
format_data['alliance_ticker'] = None
|
||||
format_data['alliance_or_corp_name'] = format_data['alliance_name'] or format_data['corp_name']
|
||||
format_data['alliance_or_corp_ticker'] = format_data['alliance_ticker'] or format_data['corp_ticker']
|
||||
return format_data
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -9,5 +9,5 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for u in User.objects.all():
|
||||
validate_services(u)
|
||||
validate_services(u.pk)
|
||||
self.stdout.write(self.style.SUCCESS('Verified all user service accounts.'))
|
||||
|
||||
@@ -11,6 +11,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0001_squashed_0003_delete_groupcache'),
|
||||
('authentication', '0015_user_profiles'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import math
|
||||
from django.conf import settings
|
||||
from requests_oauthlib import OAuth2Session
|
||||
@@ -24,8 +22,8 @@ Previously all we asked for was permission to kick members, manage roles, and ma
|
||||
Users have reported weird unauthorized errors we don't understand. So now we ask for full server admin.
|
||||
It's almost fixed the problem.
|
||||
"""
|
||||
# kick members, manage roles, manage nicknames
|
||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
|
||||
# kick members, manage roles, manage nicknames, create instant invite
|
||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000 + 0x00000001
|
||||
BOT_PERMISSIONS = 0x00000008
|
||||
|
||||
# get user ID, accept invite
|
||||
@@ -34,7 +32,7 @@ SCOPES = [
|
||||
'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):
|
||||
@@ -109,7 +107,7 @@ def api_backoff(func):
|
||||
backoff_timer = datetime.datetime.strptime(existing_backoff, cache_time_format)
|
||||
if backoff_timer > datetime.datetime.utcnow():
|
||||
backoff_seconds = (backoff_timer - datetime.datetime.utcnow()).total_seconds()
|
||||
logger.debug("Still under backoff for {} seconds, backing off" % backoff_seconds)
|
||||
logger.debug("Still under backoff for %s seconds, backing off" % backoff_seconds)
|
||||
# Still under backoff
|
||||
raise PerformBackoff(
|
||||
retry_after=backoff_seconds,
|
||||
@@ -117,8 +115,7 @@ def api_backoff(func):
|
||||
global_ratelimit=bool(existing_global_backoff)
|
||||
)
|
||||
logger.debug("Calling API calling function")
|
||||
func(*args, **kwargs)
|
||||
break
|
||||
return func(*args, **kwargs)
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 429:
|
||||
try:
|
||||
@@ -163,12 +160,11 @@ class DiscordOAuthManager:
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_name(name):
|
||||
return re.sub('[^\w.-]', '', name)[:32]
|
||||
return name[:32]
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_groupname(name):
|
||||
name = name.strip(' _')
|
||||
return DiscordOAuthManager._sanitize_name(name)
|
||||
def _sanitize_group_name(name):
|
||||
return name[:100]
|
||||
|
||||
@staticmethod
|
||||
def generate_bot_add_url():
|
||||
@@ -187,23 +183,33 @@ class DiscordOAuthManager:
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def add_user(code):
|
||||
def add_user(code, groups, nickname=None):
|
||||
try:
|
||||
token = DiscordOAuthManager._process_callback_code(code)['access_token']
|
||||
logger.debug("Received token from OAuth")
|
||||
|
||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bearer ' + token}
|
||||
path = DISCORD_URL + "/invites/" + str(settings.DISCORD_INVITE_CODE)
|
||||
r = requests.post(path, headers=custom_headers)
|
||||
logger.debug("Got status code %s after accepting Discord invite" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
path = DISCORD_URL + "/users/@me"
|
||||
r = requests.get(path, headers=custom_headers)
|
||||
logger.debug("Got status code %s after retrieving Discord profile" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
user_id = r.json()['id']
|
||||
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in
|
||||
groups]
|
||||
data = {
|
||||
'roles': group_ids,
|
||||
'access_token': token,
|
||||
}
|
||||
if nickname:
|
||||
data['nick'] = DiscordOAuthManager._sanitize_name(nickname)
|
||||
custom_headers['authorization'] = 'Bot ' + settings.DISCORD_BOT_TOKEN
|
||||
r = requests.put(path, headers=custom_headers, json=data)
|
||||
logger.debug("Got status code %s after joining Discord server" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
logger.info("Added Discord user ID %s to server." % user_id)
|
||||
return user_id
|
||||
except:
|
||||
@@ -211,23 +217,20 @@ class DiscordOAuthManager:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@api_backoff
|
||||
def update_nickname(user_id, nickname):
|
||||
try:
|
||||
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
data = {'nick': nickname}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
r = requests.patch(path, headers=custom_headers, json=data)
|
||||
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
|
||||
r.status_code, user_id, nickname))
|
||||
if r.status_code == 404:
|
||||
logger.warn("Discord user ID %s could not be found in server." % user_id)
|
||||
return True
|
||||
r.raise_for_status()
|
||||
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
data = {'nick': nickname}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
r = requests.patch(path, headers=custom_headers, json=data)
|
||||
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
|
||||
r.status_code, user_id, nickname))
|
||||
if r.status_code == 404:
|
||||
logger.warn("Discord user ID %s could not be found in server." % user_id)
|
||||
return True
|
||||
except:
|
||||
logger.exception("Failed to set nickname for Discord user ID %s (%s)" % (user_id, nickname))
|
||||
return False
|
||||
r.raise_for_status()
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def delete_user(user_id):
|
||||
@@ -260,7 +263,7 @@ class DiscordOAuthManager:
|
||||
|
||||
@staticmethod
|
||||
def _group_name_to_id(name):
|
||||
name = DiscordOAuthManager._sanitize_groupname(name)
|
||||
name = DiscordOAuthManager._sanitize_group_name(name)
|
||||
|
||||
def get_or_make_role():
|
||||
groups = DiscordOAuthManager._get_groups()
|
||||
@@ -271,42 +274,61 @@ class DiscordOAuthManager:
|
||||
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
|
||||
|
||||
@staticmethod
|
||||
def __generate_role():
|
||||
def __generate_role(name, **kwargs):
|
||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
|
||||
r = requests.post(path, headers=custom_headers)
|
||||
data = {'name': name}
|
||||
data.update(kwargs)
|
||||
r = requests.post(path, headers=custom_headers, json=data)
|
||||
logger.debug("Received status code %s after generating new role." % r.status_code)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@staticmethod
|
||||
def __edit_role(role_id, name, color=0, hoist=True, permissions=36785152):
|
||||
def __edit_role(role_id, **kwargs):
|
||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
data = {
|
||||
'color': color,
|
||||
'hoist': hoist,
|
||||
'name': name,
|
||||
'permissions': permissions,
|
||||
}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles/" + str(role_id)
|
||||
r = requests.patch(path, headers=custom_headers, data=json.dumps(data))
|
||||
r = requests.patch(path, headers=custom_headers, json=kwargs)
|
||||
logger.debug("Received status code %s after editing role id %s" % (r.status_code, role_id))
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@staticmethod
|
||||
def _create_group(name):
|
||||
role = DiscordOAuthManager.__generate_role()
|
||||
return DiscordOAuthManager.__edit_role(role['id'], 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
|
||||
@api_backoff
|
||||
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_groupname(g)) for g in groups]
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
data = {'roles': group_ids}
|
||||
r = requests.patch(path, headers=custom_headers, json=data)
|
||||
logger.debug("Received status code %s after setting user roles" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups]
|
||||
user_group_ids = DiscordOAuthManager._get_user_roles(user_id)
|
||||
for g in group_ids:
|
||||
if g not in user_group_ids:
|
||||
DiscordOAuthManager._modify_user_role(user_id, g, 'put')
|
||||
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',
|
||||
options={'permissions': (('access_discord', 'Can access the Discord service'),)},
|
||||
),
|
||||
migrations.RunPython(migrate_service_enabled),
|
||||
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -5,10 +5,11 @@ from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from allianceauth.notifications import notify
|
||||
from celery import shared_task
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
from .manager import DiscordOAuthManager, DiscordApiBackoff
|
||||
from .models import DiscordUser
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,15 +20,16 @@ class DiscordTasks:
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, user, code):
|
||||
user_id = DiscordOAuthManager.add_user(code)
|
||||
groups = DiscordTasks.get_groups(user)
|
||||
nickname = None
|
||||
if settings.DISCORD_SYNC_NAMES:
|
||||
nickname = DiscordTasks.get_nickname(user)
|
||||
user_id = DiscordOAuthManager.add_user(code, groups, nickname=nickname)
|
||||
if user_id:
|
||||
discord_user = DiscordUser()
|
||||
discord_user.user = user
|
||||
discord_user.uid = user_id
|
||||
discord_user.save()
|
||||
if settings.DISCORD_SYNC_NAMES:
|
||||
cls.update_nickname.delay(user.pk)
|
||||
cls.update_groups.delay(user.pk)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -57,28 +59,32 @@ class DiscordTasks:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name='discord.update_groups')
|
||||
def update_groups(task_self, pk):
|
||||
@shared_task(bind=True, name='discord.update_groups', base=QueueOnce)
|
||||
def update_groups(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discord groups for user %s" % user)
|
||||
if DiscordTasks.has_account(user):
|
||||
groups = []
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
logger.debug("No syncgroups found for user. Adding empty group.")
|
||||
groups.append('empty')
|
||||
groups = DiscordTasks.get_groups(user)
|
||||
logger.debug("Updating user %s discord groups to %s" % (user, groups))
|
||||
try:
|
||||
DiscordOAuthManager.update_groups(user.discord.uid, groups)
|
||||
except DiscordApiBackoff as bo:
|
||||
logger.info("Discord group sync API back off for %s, "
|
||||
"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:
|
||||
if e.response.status_code == 404:
|
||||
try:
|
||||
if e.response.json()['code'] == 10007:
|
||||
# user has left the server
|
||||
DiscordTasks.delete_user(user)
|
||||
return
|
||||
finally:
|
||||
raise e
|
||||
except Exception as e:
|
||||
if task_self:
|
||||
if self:
|
||||
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:
|
||||
# Rethrow
|
||||
raise e
|
||||
@@ -94,7 +100,7 @@ class DiscordTasks:
|
||||
DiscordTasks.update_groups.delay(discord_user.user.pk)
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name='discord.update_nickname')
|
||||
@shared_task(bind=True, name='discord.update_nickname', base=QueueOnce)
|
||||
def update_nickname(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discord nickname for user %s" % user)
|
||||
@@ -104,6 +110,10 @@ class DiscordTasks:
|
||||
logger.debug("Updating user %s discord nickname to %s" % (user, character.character_name))
|
||||
try:
|
||||
DiscordOAuthManager.update_nickname(user.discord.uid, DiscordTasks.get_nickname(user))
|
||||
except DiscordApiBackoff as bo:
|
||||
logger.info("Discord nickname update API back off for %s, "
|
||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||
raise self.retry(countdown=bo.retry_after_seconds)
|
||||
except Exception as e:
|
||||
if self:
|
||||
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
||||
@@ -132,3 +142,7 @@ class DiscordTasks:
|
||||
def get_nickname(user):
|
||||
from .auth_hooks import DiscordService
|
||||
return NameFormatter(DiscordService(), user).format_name()
|
||||
|
||||
@staticmethod
|
||||
def get_groups(user):
|
||||
return [g.name for g in user.groups.all()] + [user.profile.state.name]
|
||||
|
||||
@@ -145,6 +145,7 @@ class DiscordHooksTestCase(TestCase):
|
||||
class DiscordViewsTestCase(WebTest):
|
||||
def setUp(self):
|
||||
self.member = AuthUtils.create_member('auth_member')
|
||||
AuthUtils.add_main_character(self.member, 'test character', '1234', '2345', 'test corp', 'testc')
|
||||
add_permissions()
|
||||
|
||||
def login(self):
|
||||
@@ -198,11 +199,11 @@ class DiscordManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test__sanitize_groupname(self):
|
||||
test_group_name = ' Group Name_Test_'
|
||||
group_name = DiscordOAuthManager._sanitize_groupname(test_group_name)
|
||||
def test__sanitize_group_name(self):
|
||||
test_group_name = str(10**103)
|
||||
group_name = DiscordOAuthManager._sanitize_group_name(test_group_name)
|
||||
|
||||
self.assertEqual(group_name, 'GroupName_Test')
|
||||
self.assertEqual(group_name, test_group_name[:100])
|
||||
|
||||
def test_generate_Bot_add_url(self):
|
||||
bot_add_url = DiscordOAuthManager.generate_bot_add_url()
|
||||
@@ -245,18 +246,20 @@ class DiscordManagerTestCase(TestCase):
|
||||
|
||||
headers = {'accept': 'application/json', 'authorization': 'Bearer accesstoken'}
|
||||
|
||||
m.register_uri('POST',
|
||||
manager.DISCORD_URL + '/invites/' + str(settings.DISCORD_INVITE_CODE),
|
||||
request_headers=headers,
|
||||
text='{}')
|
||||
|
||||
m.register_uri('GET',
|
||||
manager.DISCORD_URL + "/users/@me",
|
||||
request_headers=headers,
|
||||
text=json.dumps({'id': "123456"}))
|
||||
|
||||
headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
|
||||
m.register_uri('PUT',
|
||||
manager.DISCORD_URL + '/guilds/' + str(settings.DISCORD_GUILD_ID) + '/members/123456',
|
||||
request_headers=headers,
|
||||
text='{}')
|
||||
|
||||
# Act
|
||||
return_value = DiscordOAuthManager.add_user('abcdef')
|
||||
return_value = DiscordOAuthManager.add_user('abcdef', [])
|
||||
|
||||
# Assert
|
||||
self.assertEqual(return_value, '123456')
|
||||
@@ -324,54 +327,54 @@ class DiscordManagerTestCase(TestCase):
|
||||
# Assert
|
||||
self.assertTrue(result)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||
@requests_mock.Mocker()
|
||||
def test_update_groups(self, group_cache, m):
|
||||
def test_update_groups(self, group_cache, user_roles, m):
|
||||
# Arrange
|
||||
groups = ['Member', 'Blue', 'Special Group']
|
||||
groups = ['Member', 'Blue', 'SpecialGroup']
|
||||
|
||||
group_cache.return_value = [{'id': 111, 'name': 'Member'},
|
||||
{'id': 222, 'name': 'Blue'},
|
||||
{'id': 333, 'name': 'SpecialGroup'},
|
||||
{'id': 444, 'name': 'NotYourGroup'}]
|
||||
group_cache.return_value = [{'id': '111', 'name': 'Member'},
|
||||
{'id': '222', 'name': 'Blue'},
|
||||
{'id': '333', 'name': 'SpecialGroup'},
|
||||
{'id': '444', 'name': 'NotYourGroup'}]
|
||||
user_roles.return_value = ['444']
|
||||
|
||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
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,
|
||||
request_headers=headers)
|
||||
m.patch(user_request_url, 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
|
||||
DiscordOAuthManager.update_groups(user_id, groups)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(len(m.request_history), 1, 'Must be one HTTP call 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')
|
||||
self.assertEqual(len(m.request_history), 4, 'Must be 4 HTTP calls made')
|
||||
|
||||
@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()
|
||||
def test_update_groups_backoff(self, group_cache, djcache, m):
|
||||
def test_update_groups_backoff(self, name_to_id, user_groups, djcache, m):
|
||||
# Arrange
|
||||
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}
|
||||
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
|
||||
|
||||
m.patch(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000'},
|
||||
status_code=429)
|
||||
m.put(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000'},
|
||||
status_code=429)
|
||||
|
||||
# Act & Assert
|
||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||
@@ -388,23 +391,25 @@ class DiscordManagerTestCase(TestCase):
|
||||
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.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()
|
||||
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
|
||||
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}
|
||||
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
|
||||
|
||||
m.patch(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
||||
status_code=429)
|
||||
m.put(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
||||
status_code=429)
|
||||
|
||||
# Act & Assert
|
||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||
|
||||
@@ -7,7 +7,7 @@ from hashlib import md5
|
||||
|
||||
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):
|
||||
@@ -23,7 +23,7 @@ class DiscourseError(Exception):
|
||||
ENDPOINTS = {
|
||||
'groups': {
|
||||
'list': {
|
||||
'path': "/admin/groups.json",
|
||||
'path': "/groups/search.json",
|
||||
'method': 'get',
|
||||
'args': {
|
||||
'required': [],
|
||||
@@ -345,7 +345,7 @@ class DiscourseManager:
|
||||
|
||||
@staticmethod
|
||||
def update_groups(user):
|
||||
groups = []
|
||||
groups = [DiscourseManager._sanitize_groupname(user.profile.state.name)]
|
||||
for g in user.groups.all():
|
||||
groups.append(DiscourseManager._sanitize_groupname(str(g)))
|
||||
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
|
||||
|
||||
@@ -58,5 +58,5 @@ class Migration(migrations.Migration):
|
||||
name='discourseuser',
|
||||
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.services.hooks import NameFormatter
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
from .manager import DiscourseManager
|
||||
from .models import DiscourseUser
|
||||
|
||||
@@ -40,7 +41,7 @@ class DiscourseTasks:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name='discourse.update_groups')
|
||||
@shared_task(bind=True, name='discourse.update_groups', base=QueueOnce)
|
||||
def update_groups(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discourse groups for user %s" % user)
|
||||
|
||||
@@ -4,16 +4,20 @@ import string
|
||||
import re
|
||||
from django.db import connections
|
||||
from passlib.hash import bcrypt
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'IPS4_TABLE_PREFIX', '')
|
||||
|
||||
|
||||
class Ips4Manager:
|
||||
SQL_ADD_USER = r"INSERT INTO core_members (name, email, members_pass_hash, members_pass_salt, " \
|
||||
r"member_group_id) VALUES (%s, %s, %s, %s, %s)"
|
||||
SQL_GET_ID = r"SELECT member_id FROM core_members WHERE name = %s"
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE core_members SET members_pass_hash = %s, members_pass_salt = %s WHERE name = %s"
|
||||
SQL_DEL_USER = r"DELETE FROM core_members WHERE member_id = %s"
|
||||
SQL_ADD_USER = r"INSERT INTO %score_members (name, email, members_pass_hash, members_pass_salt, " \
|
||||
r"member_group_id) VALUES (%%s, %%s, %%s, %%s, %%s)" % TABLE_PREFIX
|
||||
SQL_GET_ID = r"SELECT member_id FROM %score_members WHERE name = %%s" % TABLE_PREFIX
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE %score_members SET members_pass_hash = %%s, members_pass_salt = %%s WHERE name = %%s" % TABLE_PREFIX
|
||||
SQL_DEL_USER = r"DELETE FROM %score_members WHERE member_id = %%s" % TABLE_PREFIX
|
||||
|
||||
MEMBER_GROUP_ID = 3
|
||||
|
||||
|
||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
||||
name='ips4user',
|
||||
options={'permissions': (('access_ips4', 'Can access the IPS4 service'),)},
|
||||
),
|
||||
migrations.RunPython(migrate_service_enabled),
|
||||
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -5,26 +5,30 @@ import re
|
||||
|
||||
from django.db import connections
|
||||
from passlib.hash import bcrypt
|
||||
from django.conf import settings
|
||||
|
||||
# requires yum install libffi-devel and pip install bcrypt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'MARKET_TABLE_PREFIX', 'fos_')
|
||||
|
||||
|
||||
class MarketManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
SQL_ADD_USER = r"INSERT INTO fos_user (username, username_canonical, email, email_canonical, enabled, salt," \
|
||||
SQL_ADD_USER = r"INSERT INTO %suser (username, username_canonical, email, email_canonical, enabled, salt," \
|
||||
r"password, locked, expired, roles, credentials_expired, characterid, characterName)" \
|
||||
r"VALUES (%s, %s, %s, %s, 1,%s, %s, 0, 0, 'a:0:{}', 0, %s, %s) "
|
||||
SQL_GET_USER_ID = r"SELECT id FROM fos_user WHERE username = %s"
|
||||
SQL_DISABLE_USER = r"UPDATE fos_user SET enabled = '0' WHERE username = %s"
|
||||
SQL_ENABLE_USER = r"UPDATE fos_user SET enabled = '1' WHERE username = %s"
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE fos_user SET password = %s, salt = %s WHERE username = %s"
|
||||
SQL_CHECK_EMAIL = r"SELECT email FROM fos_user WHERE email = %s"
|
||||
SQL_CHECK_USERNAME = r"SELECT username FROM fos_user WHERE username = %s"
|
||||
SQL_UPDATE_USER = r"UPDATE fos_user SET password = %s, salt = %s, enabled = '1' WHERE username = %s"
|
||||
r"VALUES (%%s, %%s, %%s, %%s, 1,%%s, %%s, 0, 0, 'a:0:{}', 0, %%s, %%s) " % TABLE_PREFIX
|
||||
SQL_GET_USER_ID = r"SELECT id FROM %suser WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_DISABLE_USER = r"UPDATE %suser SET enabled = '0' WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_ENABLE_USER = r"UPDATE %suser SET enabled = '1' WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_UPDATE_PASSWORD = r"UPDATE %suser SET password = %%s, salt = %%s WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_CHECK_EMAIL = r"SELECT email FROM %suser WHERE email = %%s" % TABLE_PREFIX
|
||||
SQL_CHECK_USERNAME = r"SELECT username FROM %suser WHERE username = %%s" % TABLE_PREFIX
|
||||
SQL_UPDATE_USER = r"UPDATE %suser SET password = %%s, salt = %%s, enabled = '1' WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
@staticmethod
|
||||
def __santatize_username(username):
|
||||
|
||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
||||
name='marketuser',
|
||||
options={'permissions': (('access_market', 'Can access the Evernus Market service'),)},
|
||||
),
|
||||
migrations.RunPython(migrate_service_enabled),
|
||||
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
||||
name='mumbleuser',
|
||||
options={'permissions': (('access_mumble', 'Can access the Mumble service'),)},
|
||||
),
|
||||
migrations.RunPython(migrate_service_enabled),
|
||||
migrations.RunPython(migrate_service_enabled, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -82,11 +82,9 @@ class MumbleUser(AbstractServiceModel):
|
||||
def update_groups(self, groups: Group=None):
|
||||
if groups is None:
|
||||
groups = self.user.groups.all()
|
||||
groups_str = []
|
||||
groups_str = [self.user.profile.state.name]
|
||||
for group in groups:
|
||||
groups_str.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups_str.append('empty')
|
||||
safe_groups = ','.join(set([g.replace(' ', '-') for g in groups_str]))
|
||||
logger.info("Updating mumble user {} groups to {}".format(self.user, safe_groups))
|
||||
self.groups = safe_groups
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from celery import shared_task
|
||||
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
from .models import MumbleUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -26,7 +26,7 @@ class MumbleTasks:
|
||||
MumbleUser.objects.all().delete()
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name="mumble.update_groups")
|
||||
@shared_task(bind=True, name="mumble.update_groups", base=QueueOnce)
|
||||
def update_groups(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating mumble groups for user %s" % user)
|
||||
|
||||
@@ -136,7 +136,9 @@ class MumbleViewsTestCase(TestCase):
|
||||
mumble_user = MumbleUser.objects.get(user=self.member)
|
||||
self.assertEqual(mumble_user.username, expected_username)
|
||||
self.assertTrue(mumble_user.pwhash)
|
||||
self.assertEqual('Member', mumble_user.groups)
|
||||
self.assertIn('Guest', mumble_user.groups)
|
||||
self.assertIn('Member', mumble_user.groups)
|
||||
self.assertIn(',', mumble_user.groups)
|
||||
|
||||
def test_deactivate_post(self):
|
||||
self.login()
|
||||
|
||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
||||
name='openfireuser',
|
||||
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 allianceauth.notifications import notify
|
||||
from celery import shared_task
|
||||
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
from allianceauth.services.modules.openfire.manager import OpenfireManager
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
from .models import OpenfireUser
|
||||
@@ -40,16 +40,14 @@ class OpenfireTasks:
|
||||
OpenfireUser.objects.all().delete()
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name="openfire.update_groups")
|
||||
@shared_task(bind=True, name="openfire.update_groups", base=QueueOnce)
|
||||
def update_groups(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating jabber groups for user %s" % user)
|
||||
if OpenfireTasks.has_account(user):
|
||||
groups = []
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s jabber groups to %s" % (user, groups))
|
||||
try:
|
||||
OpenfireManager.update_user_groups(user.openfire.username, groups)
|
||||
|
||||
@@ -14,40 +14,43 @@ from django.conf import settings
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TABLE_PREFIX = getattr(settings, 'PHPBB3_TABLE_PREFIX', 'phpbb_')
|
||||
|
||||
|
||||
class Phpbb3Manager:
|
||||
SQL_ADD_USER = r"INSERT INTO phpbb_users (username, username_clean, " \
|
||||
SQL_ADD_USER = r"INSERT INTO %susers (username, username_clean, " \
|
||||
r"user_password, user_email, group_id, user_regdate, user_permissions, " \
|
||||
r"user_sig, user_lang) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'en')"
|
||||
r"user_sig, user_lang) VALUES (%%s, %%s, %%s, %%s, %%s, %%s, %%s, %%s, 'en')" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_USER = r"DELETE FROM phpbb_users where username = %s"
|
||||
SQL_DEL_USER = r"DELETE FROM %susers where username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DIS_USER = r"UPDATE phpbb_users SET user_email= %s, user_password=%s WHERE username = %s"
|
||||
SQL_DIS_USER = r"UPDATE %susers SET user_email= %%s, user_password=%%s WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT user_id from phpbb_users WHERE username = %s"
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT user_id from %susers WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_USER_GROUP = r"INSERT INTO phpbb_user_group (group_id, user_id, user_pending) VALUES (%s, %s, %s)"
|
||||
SQL_ADD_USER_GROUP = r"INSERT INTO %suser_group (group_id, user_id, user_pending) VALUES (%%s, %%s, %%s)" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_GROUP_ID = r"SELECT group_id from phpbb_groups WHERE group_name = %s"
|
||||
SQL_GET_GROUP_ID = r"SELECT group_id from %sgroups WHERE group_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_ADD_GROUP = r"INSERT INTO phpbb_groups (group_name,group_desc,group_legend) VALUES (%s,%s,0)"
|
||||
SQL_ADD_GROUP = r"INSERT INTO %sgroups (group_name,group_desc,group_legend) VALUES (%%s,%%s,0)" % TABLE_PREFIX
|
||||
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE phpbb_users SET user_password = %s WHERE username = %s"
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE %susers SET user_password = %%s WHERE username = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_REMOVE_USER_GROUP = r"DELETE FROM phpbb_user_group WHERE user_id=%s AND group_id=%s "
|
||||
SQL_REMOVE_USER_GROUP = r"DELETE FROM %suser_group WHERE user_id=%%s AND group_id=%%s " % TABLE_PREFIX
|
||||
|
||||
SQL_GET_ALL_GROUPS = r"SELECT group_id, group_name FROM phpbb_groups"
|
||||
SQL_GET_ALL_GROUPS = r"SELECT group_id, group_name FROM %sgroups" % TABLE_PREFIX
|
||||
|
||||
SQL_GET_USER_GROUPS = r"SELECT phpbb_groups.group_name FROM phpbb_groups , phpbb_user_group WHERE " \
|
||||
r"phpbb_user_group.group_id = phpbb_groups.group_id AND user_id=%s"
|
||||
SQL_GET_USER_GROUPS = r"SELECT %(prefix)sgroups.group_name FROM %(prefix)sgroups , %(prefix)suser_group WHERE " \
|
||||
r"%(prefix)suser_group.group_id = %(prefix)sgroups.group_id AND user_id=%%s" % {'prefix': TABLE_PREFIX}
|
||||
|
||||
SQL_ADD_USER_AVATAR = r"UPDATE phpbb_users SET user_avatar_type=2, user_avatar_width=64, user_avatar_height=64, " \
|
||||
"user_avatar=%s WHERE user_id = %s"
|
||||
SQL_ADD_USER_AVATAR = r"UPDATE %susers SET user_avatar_type=2, user_avatar_width=64, user_avatar_height=64, " \
|
||||
"user_avatar=%%s WHERE user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_CLEAR_USER_PERMISSIONS = r"UPDATE phpbb_users SET user_permissions = '' WHERE user_Id = %s"
|
||||
SQL_CLEAR_USER_PERMISSIONS = r"UPDATE %susers SET user_permissions = '' WHERE user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_SESSION = r"DELETE FROM phpbb_sessions where session_user_id = %s"
|
||||
SQL_DEL_SESSION = r"DELETE FROM %ssessions where session_user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DEL_AUTOLOGIN = r"DELETE FROM phpbb_sessions_keys where user_id = %s"
|
||||
SQL_DEL_AUTOLOGIN = r"DELETE FROM %ssessions_keys where user_id = %%s" % TABLE_PREFIX
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@@ -57,5 +57,5 @@ class Migration(migrations.Migration):
|
||||
name='phpbb3user',
|
||||
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.core.exceptions import ObjectDoesNotExist
|
||||
from celery import shared_task
|
||||
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
from allianceauth.notifications import notify
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
from .manager import Phpbb3Manager
|
||||
@@ -35,16 +35,14 @@ class Phpbb3Tasks:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name="phpbb3.update_groups")
|
||||
@shared_task(bind=True, name="phpbb3.update_groups", base=QueueOnce)
|
||||
def update_groups(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating phpbb3 groups for user %s" % user)
|
||||
if Phpbb3Tasks.has_account(user):
|
||||
groups = []
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s phpbb3 groups to %s" % (user, groups))
|
||||
try:
|
||||
Phpbb3Manager.update_groups(user.phpbb3.username, groups)
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from celery import shared_task
|
||||
|
||||
from allianceauth.services.tasks import QueueOnce
|
||||
from allianceauth.notifications import notify
|
||||
from allianceauth.services.hooks import NameFormatter
|
||||
from .manager import SeatManager
|
||||
@@ -34,17 +34,14 @@ class SeatTasks:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True)
|
||||
@shared_task(bind=True, name='seat.update_roles', base=QueueOnce)
|
||||
def update_roles(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating SeAT roles for user %s" % user)
|
||||
groups = []
|
||||
if SeatTasks.has_account(user):
|
||||
groups = [user.profile.state.name]
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
logger.debug("No syncgroups found for user. Adding empty group.")
|
||||
groups.append('empty')
|
||||
logger.debug("Updating user %s SeAT roles to %s" % (user, groups))
|
||||
try:
|
||||
SeatManager.update_roles(user.seat.username, groups)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user