Compare commits

...

55 Commits
v1.13 ... v1.14

Author SHA1 Message Date
Adarnof
02aaa7ff78 Version bump to 1.14 2017-01-03 00:44:42 -05:00
Adarnof
c205bff99f Remove references to optional SSO configuration 2017-01-02 20:55:12 -05:00
Adarnof
56082848a7 EVE Swagger Interface (#591)
FAT uses ESI tokens to get character location/ship
 - closes #564

 Pull corp memebrship data from ESI

Additional permissions for non-api viewing.
 - migration to convert permissions from old users.

Standardize EVE datasource responses.
 - allow different sources for EVE data types.

Allow empty values for character alliance id and name

Allow multiple corps and alliances to be considered 'members'
2017-01-02 20:50:21 -05:00
Adarnof
2816a5fa46 Change admin site header to version number (#611) 2017-01-02 20:24:41 -05:00
Adarnof
4f26fa03e6 Correct navbar highlighting for group management. 2017-01-02 18:54:37 -05:00
Basraah
4ae450f963 Group system overhaul (#588)
* Add open/hidden group membership display and remove

* Include requestable groups other than open

* Prevent users requesting or leaving non-joinable groups

I have not prevented users joining hidden groups however, as
there may be some use cases where the direct link is provided
for users to request access to the group.

Also prevent users generating leave requests for groups they
are not a member of.

* Refactor Group extension models into a single OneToOne model

Added group leader field

* Add blankable fields

* Switched to use navactive for menu highlighting

* Consolidate member state checking for easier code reuse

* Added support for group leaders to manage groups

* Added info log when a user removes someone from a group

* Add ordering to group member list
2017-01-02 18:52:20 -05:00
Adarnof
959e167987 Remove references to legacy character management. 2017-01-02 18:51:19 -05:00
Basraah
f96959d854 Replace API Key page with Dashboard profile (#582)
Shows characters on each API key, main character, API key
status and the user groups.
2017-01-02 18:47:10 -05:00
Adarnof
2f5529b582 Remove refernces to optional SSO configuration 2016-12-29 18:34:21 -05:00
Adarnof
04f2731517 Multitenant
Closes #604
2016-12-29 18:10:43 -05:00
Adarnof
71c1054328 EVE Swagger Interface
Closes #591

Conflicts:
	authentication/views.py
	eveonline/views.py
	stock/templates/public/characternotexisting.html
2016-12-29 17:53:33 -05:00
Adarnof
917707027e Correct navbar highlighting for group management 2016-12-29 17:49:40 -05:00
Adarnof
03aab76ab9 Group system overhaul
Closes #588
2016-12-29 17:42:10 -05:00
Adarnof
1e523d762e Remove references to legacy character management.
Correct group name spacing.
2016-12-29 17:41:00 -05:00
Adarnof
33e481fb12 Replace API Key page with Dashboard profile
Closes #582
2016-12-29 17:13:07 -05:00
Adarnof
16a4ed9bf2 Correct site name template syntax 2016-12-21 23:40:45 -05:00
Sebastian
286cb9e1d1 added citadels and engineering complexes to timerboard (#602) 2016-12-20 22:59:02 -05:00
Adarnof
2f2c2bd0e5 Complete removal of single tenant aspects. 2016-12-20 07:17:21 +00:00
Adarnof
3cdbff6b36 Merge branch 'esi' of https://github.com/r4stl1n/allianceauth into multitenant 2016-12-20 06:54:40 +00:00
Adarnof
54464c23cf Begin removal of IS_CORP 2016-12-20 06:54:29 +00:00
Adarnof
2a87eed059 Reduce API calls to speed up API checks 2016-12-19 21:13:45 -05:00
Adarnof
55f349c35b Remove legacy xml functions. 2016-12-19 21:07:45 -05:00
Adarnof
604808b195 Retrieve character objects from api keys. 2016-12-19 20:56:14 -05:00
Adarnof
ce35e72e44 Correct discourse group sync task calling. 2016-12-20 01:20:31 +00:00
Adarnof
8582584fd5 prevent redis connectionerror for async tasks 2016-12-19 17:33:11 -05:00
Adarnof
8379fdd7d5 Update corp/alliance models using adapters. 2016-12-17 20:51:59 -05:00
Adarnof
ec9b43b083 Allow different sources for EVE data types. 2016-12-17 14:46:02 -05:00
Adarnof
66240ad296 Standardize EVE datasource responses.
Remove alliance member count.
2016-12-16 22:13:29 -05:00
Adarnof
0fe5a1c5e3 Correct context processor for SSO address. 2016-12-16 17:43:44 -05:00
Adarnof
b514f8cbcc Correct missing migration dependency. 2016-12-14 20:57:56 -05:00
Adarnof
4ee10e0c31 Additional permissions for non-api viewing.
Migration to convert permissions from old users.
2016-12-14 20:40:12 -05:00
Adarnof
5f88e7e1a5 Correct permission check for showing APIs 2016-12-13 20:22:09 -05:00
Adarnof
5a9418d792 Visual indication of member registration. 2016-12-13 19:29:02 -05:00
Adarnof
02bd4570b0 Default to only available corpstats if no corp_id 2016-12-13 17:45:42 -05:00
Adarnof
6ba084c710 Cleanup FAT edit page tables with pagination.
Cleanup FAT statistics generation with smarter query.
2016-12-13 17:31:36 -05:00
Adarnof
6fd3c32ba0 Correct check if corputils is visible to user.
Add additional admin site permissions.
2016-12-13 17:30:06 -05:00
Adarnof
3d92e4c5c5 Perform first corpstats update on creation. 2016-12-13 16:26:20 -05:00
Adarnof
27fc8a373f Pull corp memebrship data from ESI 2016-12-13 16:11:06 -05:00
Adarnof
32009fd3ff Move FAT templates to app folder.
Provide feedback via messages.
2016-12-11 23:26:20 -05:00
Adarnof
b4b739ee61 FAT uses ESI tokens to get character location/ship
Closes #564
2016-12-11 22:10:36 -05:00
Adarnof
a630b5b397 Migrate to adarnauth-esi 2016-12-11 20:11:05 -05:00
Basraah
721708ee16 Add ordering to group member list 2016-12-08 12:50:45 +10:00
Basraah
4dabb3198d Merge branch 'group-membership' into group-refactor 2016-12-06 20:20:43 +10:00
Basraah
c0ca73f9ea Added info log when a user removes someone from a group 2016-12-06 20:18:13 +10:00
Basraah
83b62525eb Added support for group leaders to manage groups 2016-12-05 21:49:12 +10:00
Basraah
9eba5607d2 Consolidate member state checking for easier code reuse 2016-12-05 20:42:47 +10:00
Basraah
5fb64d8c06 Switched to use navactive for menu highlighting 2016-12-05 14:35:08 +10:00
Basraah
ed461d9e7a Add blankable fields 2016-12-04 23:41:26 +10:00
Basraah
bf345361b2 Refactor Group extension models into a single OneToOne model
Added group leader field
2016-12-04 23:05:04 +10:00
Basraah
648753a68a Prevent users requesting or leaving non-joinable groups
I have not prevented users joining hidden groups however, as
there may be some use cases where the direct link is provided
for users to request access to the group.

Also prevent users generating leave requests for groups they
are not a member of.
2016-12-04 13:02:25 +10:00
Basraah
42def30c91 Include requestable groups other than open 2016-12-04 09:21:43 +10:00
Basraah
25dadf81f6 Add open/hidden group membership display and remove 2016-12-03 21:45:55 +10:00
Basraah
2dd75b5100 Switched styling to warning, added tooltip 2016-11-24 10:56:36 +10:00
Basraah
eb6d7e9d9a Improve layout on small display resolutions 2016-11-23 22:35:08 +10:00
Basraah
1c4d2533b1 Replace API key management w/ ported profile page
Shows characters on each API key, main character, API key
status and the user groups.
2016-11-23 21:17:44 +10:00
63 changed files with 2246 additions and 1646 deletions

View File

@@ -1 +1,4 @@
from __future__ import unicode_literals
from __future__ import unicode_literals
__version__ = '1.14'
NAME = 'Alliance Auth v%s' % __version__

View File

@@ -61,8 +61,9 @@ INSTALLED_APPS = [
'corputils',
'fleetactivitytracking',
'notifications',
'eve_sso',
'esi',
'geelweb.django.navhelper',
'bootstrap_pagination',
]
MIDDLEWARE = [
@@ -110,7 +111,7 @@ TEMPLATES = [
'notifications.context_processors.user_notification_count',
'authentication.context_processors.states',
'authentication.context_processors.membership_state',
'authentication.context_processors.sso',
'groupmanagement.context_processors.can_manage_groups',
],
},
},
@@ -190,15 +191,6 @@ MESSAGE_TAGS = {
##
#####################################################
###########################
# ALLIANCE / CORP TOGGLE
###########################
# Specifies to run membership checks against corp or alliance
# Set to FALSE for alliance
# Set to TRUE for corp
###########################
IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True')
#################
# EMAIL SETTINGS
#################
@@ -222,24 +214,23 @@ EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
# KILLBOARD_URL - URL for your killboard. Blank to hide link
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
# FORUM_URL - URL for your forums. Blank to hide link
# SMF_URL - URL for your SMF forums.
# SITE_NAME - Name of the auth site.
####################
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Alliance Auth')
###################
# SSO Settings
###################
# Optional SSO.
# Get client ID and client secret from registering an app at
# https://developers.eveonline.com/
# Callback URL should be http://mydomain.com/sso/callback
# Leave callback blank to hide SSO button on login page
# Callback URL should be https://mydomain.com/sso/callback
###################
EVE_SSO_CLIENT_ID = os.environ.get('AA_EVE_SSO_CLIENT_ID', '')
EVE_SSO_CLIENT_SECRET = os.environ.get('AA_EVE_SSO_CLIENT_SECRET', '')
EVE_SSO_CALLBACK_URL = os.environ.get('AA_EVE_SSO_CALLBACK_URL', '')
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
#########################
# Default Group Settings
@@ -286,7 +277,6 @@ ENABLE_AUTH_XENFORO = 'True' == os.environ.get('AA_ENABLE_AUTH_XENFORO', 'False'
#####################
# Blue service Setup
#####################
# BLUE_STANDING - The default lowest standings setting to consider blue
# ENABLE_BLUE_FORUM - Enable forum support in the auth for blues
# ENABLE_BLUE_JABBER - Enable jabber support in the auth for blues
# ENABLE_BLUE_MUMBLE - Enable mumble support in the auth for blues
@@ -299,7 +289,6 @@ ENABLE_AUTH_XENFORO = 'True' == os.environ.get('AA_ENABLE_AUTH_XENFORO', 'False'
# ENABLE_BLUE_PATHFINDER = Enable Pathfinder support in the auth for blues
# ENABLE_BLUE_XENFORO = Enable XenForo forum support in the auth for blue
#####################
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
ENABLE_BLUE_FORUM = 'True' == os.environ.get('AA_ENABLE_BLUE_FORUM', 'False')
ENABLE_BLUE_JABBER = 'True' == os.environ.get('AA_ENABLE_BLUE_JABBER', 'False')
ENABLE_BLUE_MUMBLE = 'True' == os.environ.get('AA_ENABLE_BLUE_MUMBLE', 'False')
@@ -313,27 +302,28 @@ ENABLE_BLUE_MARKET = 'True' == os.environ.get('AA_ENABLE_BLUE_MARKET', 'False')
ENABLE_BLUE_XENFORO = 'True' == os.environ.get('AA_ENABLE_BLUE_XENFORO', 'False')
#########################
# Corp Configuration
# Tenant Configuration
#########################
# If running in alliance mode, the following should be for the executor corp#
# CORP_ID - Set this to your corp ID (get this from https://zkillboard.com/corporation/#######)
# CORP_NAME - Set this to your Corporation Name
# CORP_API_ID - Set this to the api id for the corp API key
# CORP_API_VCODE - Set this to the api vcode for the corp API key
########################
CORP_ID = os.environ.get('AA_CORP_ID', '')
CORP_NAME = os.environ.get('AA_CORP_NAME', '')
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
# CORP_IDS - A list of corporation IDs to treat as members.
# ALLIANCE_IDS - A list of alliance IDs to treat as members.
# Any corps in a specified alliance will be treated as members, so do not include them in CORP_IDS
#########################
CORP_IDS = []
ALLIANCE_IDS = []
#########################
# Alliance Configuration
# Standings Configuration
#########################
# ALLIANCE_ID - Set this to your Alliance ID (get this from https://zkillboard.com/alliance/#######)
# ALLIANCE_NAME - Set this to your Alliance Name
# Add a corp API key to add blue standings to grant access.
# CORP_API_ID - Set this to the api id for the corp API key
# CORP_API_VCODE - Set this to the api vcode for the corp API key
# BLUE_STANDING - The lowest standings value to consider blue
# STANDING_LEVEL - The level of standings to query. Accepted values are 'corp' and 'alliance'.
########################
ALLIANCE_ID = os.environ.get('AA_ALLIANCE_ID', '')
ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', '')
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
STANDING_LEVEL = os.environ.get('AA_STANDING_LEVEL', 'corp')
########################
# API Configuration
@@ -355,6 +345,19 @@ REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
#######################
# EVE Provider Settings
#######################
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
#
# Available soruces are 'esi' and 'xml'
#######################
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', 'esi')
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', 'esi')
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', 'esi')
#####################
# Alliance Market
#####################
@@ -653,3 +656,7 @@ if ENABLE_AUTH_MARKET or ENABLE_BLUE_MARKET:
DATABASES['market'] = MARKET_DB
if ENABLE_AUTH_IPS4 or ENABLE_BLUE_IPS4:
DATABASES['ips4'] = IPS4_DB
# Ensure corp/alliance IDs are expected types
STR_CORP_IDS = [str(id) for id in CORP_IDS]
STR_ALLIANCE_IDS = [str(id) for id in ALLIANCE_IDS]

View File

@@ -10,13 +10,16 @@ import services.views
import groupmanagement.views
import optimer.views
import timerboard.views
import corputils.views
import fleetactivitytracking.views
import fleetup.views
import srp.views
import notifications.views
import hrapplications.views
import eve_sso.urls
import corputils.urls
import esi.urls
from alliance_auth import NAME
admin.site.site_header = NAME
# Functional/Untranslated URL's
urlpatterns = [
@@ -27,8 +30,11 @@ urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
# SSO
url (r'^sso/', include(eve_sso.urls, namespace='eve_sso')),
url (r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'),
url(r'^sso/', include(esi.urls, namespace='esi')),
url(r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'),
# Corputils
url(r'^corpstats/', include(corputils.urls, namespace='corputils')),
# Index
url(_(r'^$'), authentication.views.index_view, name='auth_index'),
@@ -144,14 +150,6 @@ urlpatterns = [
# User viewed/translated URLS
urlpatterns += i18n_patterns(
# corputils
url(r'^corputils/$', corputils.views.corp_member_view, name='auth_corputils'),
url(r'^corputils/(?P<corpid>[0-9]+)/$', corputils.views.corp_member_view, name='auth_corputils_corp_view'),
url(r'^corputils/(?P<corpid>[0-9]+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', corputils.views.corp_member_view,
name='auth_corputils_month'),
url(r'^corputils/search/$', corputils.views.corputils_search, name="auth_corputils_search"),
url(r'^corputils/search/(?P<corpid>[0-9]+)/$', corputils.views.corputils_search, name='auth_corputils_search_corp'),
# Fleetup
url(r'^fleetup/$', fleetup.views.fleetup_view, name='auth_fleetup_view'),
url(r'^fleetup/fittings/$', fleetup.views.fleetup_fittings, name='auth_fleetup_fittings'),
@@ -177,13 +175,11 @@ urlpatterns += i18n_patterns(
django.contrib.auth.views.password_reset_confirm, name='password_reset_confirm'),
# Portal Urls
url(_(r'^dashboard/$'), authentication.views.dashboard_view, name='auth_dashboard'),
url(_(r'^dashboard/$'), eveonline.views.dashboard_view, name='auth_dashboard'),
url(_(r'^help/$'), authentication.views.help_view, name='auth_help'),
# Eveonline Urls
url(_(r'^add_api_key/'), eveonline.views.add_api_key, name='auth_add_api_key'),
url(_(r'^api_key_management/'), eveonline.views.api_key_management_view,
name='auth_api_key_management'),
url(_(r'^refresh_api_pair/([0-9]+)/$'), eveonline.views.user_refresh_api, name='auth_user_refresh_api'),
url(_(r'^delete_api_pair/(\w+)/$'), eveonline.views.api_key_removal, name='auth_api_key_removal'),
url(_(r'^characters/'), eveonline.views.characters_view, name='auth_characters'),
@@ -192,6 +188,12 @@ urlpatterns += i18n_patterns(
url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'),
url(_(r'^group/management/'), groupmanagement.views.group_management,
name='auth_group_management'),
url(_(r'^group/membership/$'), groupmanagement.views.group_membership,
name='auth_group_membership'),
url(_(r'^group/membership/(\w+)/$'), groupmanagement.views.group_membership_list,
name='auth_group_membership_list'),
url(_(r'^group/membership/(\w+)/remove/(\w+)/$'), groupmanagement.views.group_membership_remove,
name='auth_group_membership_remove'),
url(_(r'^group/request_add/(\w+)'), groupmanagement.views.group_request_add,
name='auth_group_request_add'),
url(_(r'^group/request/accept/(\w+)'), groupmanagement.views.group_accept_request,
@@ -245,12 +247,6 @@ urlpatterns += i18n_patterns(
# Teamspeak Urls
url(r'verify_teamspeak3/$', services.views.verify_teamspeak3, name='auth_verify_teamspeak3'),
# corputils
url(_(r'^corputils/$'), corputils.views.corp_member_view, name='auth_corputils'),
url(_(r'^corputils/(?P<corpid>[0-9]+)/$'), corputils.views.corp_member_view, name='auth_corputils_corp_view'),
url(_(r'^corputils/search/$'), corputils.views.corputils_search, name="auth_corputils_search"),
url(_(r'^corputils/search/(?P<corpid>[0-9]+)/$'), corputils.views.corputils_search, name='auth_corputils_search_corp'),
# Timer URLS
url(_(r'^timers/$'), timerboard.views.timer_view, name='auth_timer_view'),
url(_(r'^add_timer/$'), timerboard.views.add_timer_view, name='auth_add_timer_view'),

View File

@@ -1,14 +1,11 @@
from __future__ import unicode_literals
from authentication.models import AuthServicesInfo
from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE
from authentication.managers import UserState
from django.conf import settings
def membership_state(request):
if request.user.is_authenticated:
auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
return {'STATE': auth.state}
return {'STATE': NONE_STATE}
return UserState.get_membership_state(request)
def states(request):
@@ -18,8 +15,3 @@ def states(request):
'NONE_STATE': NONE_STATE,
'MEMBER_BLUE_STATE': [MEMBER_STATE, BLUE_STATE],
}
def sso(request):
return {
'EVE_SSO_CALLBACK_URL': settings.EVE_SSO_CALLBACK_URL,
}

View File

@@ -1,33 +1,23 @@
from __future__ import unicode_literals
from django.contrib.auth.decorators import user_passes_test
from authentication.models import AuthServicesInfo
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from django.conf import settings
from authentication.managers import UserState
def _state_required(states, *args, **kwargs):
def test_func(user):
if user.is_superuser and settings.SUPERUSER_STATE_BYPASS:
return True
if user.is_authenticated:
auth = AuthServicesInfo.objects.get_or_create(user=user)[0]
return auth.state in states
return False
return user_passes_test(test_func, *args, **kwargs)
def _state_required(state_test, *args, **kwargs):
return user_passes_test(state_test, *args, **kwargs)
def members(*args, **kwargs):
return _state_required([MEMBER_STATE], *args, **kwargs)
return _state_required(UserState.member_state, *args, **kwargs)
def blues(*args, **kwargs):
return _state_required([BLUE_STATE], *args, **kwargs)
return _state_required(UserState.blue_state, *args, **kwargs)
def members_and_blues(*args, **kwargs):
return _state_required([MEMBER_STATE, BLUE_STATE], *args, **kwargs)
return _state_required(UserState.member_or_blue_state, *args, **kwargs)
def none_state(*args, **kwargs):
return _state_required([NONE_STATE], *args, **kwargs)
return _state_required(UserState.none_state, *args, **kwargs)

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.conf import settings
from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE
from authentication.models import AuthServicesInfo
import logging
@@ -143,3 +144,44 @@ class AuthServicesInfoManager:
logger.info("Updated user %s market info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s market info: user does not exist." % user)
class UserState:
def __init__(self):
pass
MEMBER_STATE = MEMBER_STATE
BLUE_STATE = BLUE_STATE
NONE_STATE = NONE_STATE
@classmethod
def member_state(cls, user):
return cls.state_required(user, [cls.MEMBER_STATE])
@classmethod
def member_or_blue_state(cls, user):
return cls.state_required(user, [cls.MEMBER_STATE, cls.BLUE_STATE])
@classmethod
def blue_state(cls, user):
return cls.state_required(user, [cls.BLUE_STATE])
@classmethod
def none_state(cls, user):
return cls.state_required(user, [cls.NONE_STATE])
@classmethod
def get_membership_state(cls, request):
if request.user.is_authenticated:
auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
return {'STATE': auth.state}
return {'STATE': cls.NONE_STATE}
@staticmethod
def state_required(user, states):
if user.is_superuser and settings.SUPERUSER_STATE_BYPASS:
return True
if user.is_authenticated:
auth = AuthServicesInfo.objects.get_or_create(user=user)[0]
return auth.state in states
return False

View File

@@ -63,15 +63,13 @@ def make_blue(auth):
def determine_membership_by_character(char):
if settings.IS_CORP:
if int(char.corporation_id) == int(settings.CORP_ID):
logger.debug("Character %s in owning corp id %s" % (char, char.corporation_id))
return MEMBER_STATE
else:
if int(char.alliance_id) == int(settings.ALLIANCE_ID):
logger.debug("Character %s in owning alliance id %s" % (char, char.alliance_id))
return MEMBER_STATE
if EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists() is False:
if char.corporation_id in settings.STR_CORP_IDS:
logger.debug("Character %s in member corp id %s" % (char, char.corporation_id))
return MEMBER_STATE
elif char.alliance_id in settings.STR_ALLIANCE_IDS:
logger.debug("Character %s in member alliance id %s" % (char, char.alliance_id))
return MEMBER_STATE
elif not EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists():
logger.debug("No corp model for character %s corp id %s. Unable to check standings. Non-member." % (
char, char.corporation_id))
return NONE_STATE

View File

@@ -10,7 +10,7 @@ from authentication.models import AuthServicesInfo
from authentication.forms import LoginForm, RegistrationForm
from django.contrib.auth.models import User
from django.contrib import messages
from eve_sso.decorators import token_required
from esi.decorators import token_required
import logging
logger = logging.getLogger(__name__)
@@ -88,28 +88,22 @@ def index_view(request):
return render(request, 'public/index.html')
@login_required
def dashboard_view(request):
logger.debug("dashboard_view called by user %s" % request.user)
render_items = {'characters': EveManager.get_characters_by_owner_id(request.user.id),
'authinfo': AuthServicesInfo.objects.get_or_create(user=request.user)[0]}
return render(request, 'registered/dashboard.html', context=render_items)
@login_required
def help_view(request):
logger.debug("help_view called by user %s" % request.user)
return render(request, 'registered/help.html')
@token_required(new=True)
def sso_login(request, tokens=[]):
token = tokens[0]
def sso_login(request, token):
try:
char = EveCharacter.objects.get(character_id=token.character_id)
if char.user:
if char.user.is_active:
login(request, char.user)
return redirect(dashboard_view)
token.user = char.user
token.save()
return redirect('auth_dashboard')
else:
messages.error(request, 'Your account has been disabled.')
else:

View File

@@ -1 +1,5 @@
from __future__ import unicode_literals
from corputils.models import CorpStats
from django.contrib import admin
admin.site.register(CorpStats)

View File

@@ -1,8 +0,0 @@
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
class CorputilsSearchForm(forms.Form):
search_string = forms.CharField(max_length=254, required=True, label="",
widget=forms.TextInput(attrs={'placeholder': _('Search characters...')}))

42
corputils/managers.py Normal file
View File

@@ -0,0 +1,42 @@
from django.db import models
from authentication.models import AuthServicesInfo
from eveonline.models import EveCharacter
class CorpStatsQuerySet(models.QuerySet):
def visible_to(self, user):
# superusers get all visible
if user.is_superuser:
return self
auth = AuthServicesInfo.objects.get_or_create(user=user)[0]
try:
char = EveCharacter.objects.get(character_id=auth.main_char_id)
# build all accepted queries
queries = []
if user.has_perm('corputils.view_corp_corpstats'):
queries.append(models.Q(corp__corporation_id=char.corporation_id))
if user.has_perm('corputils.view_alliance_corpstats'):
queries.append(models.Q(corp__alliance__alliance_id=char.alliance_id))
if user.has_perm('corputils.view_blue_corpstats'):
queries.append(models.Q(corp__is_blue=True))
# filter based on queries
if queries:
query = queries.pop()
for q in queries:
query |= q
return self.filter(query)
else:
# not allowed to see any
return self.none()
except EveCharacter.DoesNotExist:
return self.none()
class CorpStatsManager(models.Manager):
def get_queryset(self):
return CorpStatsQuerySet(self.model, using=self._db)
def visible_to(self, user):
return self.get_queryset().visible_to(user)

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:36
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('esi', '0002_scopes_20161208'),
('eveonline', '0004_eveapikeypair_sso_verified'),
]
operations = [
migrations.CreateModel(
name='CorpStats',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_update', models.DateTimeField(auto_now=True)),
('_members', models.TextField(default='{}')),
('corp', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='eveonline.EveCorporationInfo')),
('token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='esi.Token')),
],
options={
'default_permissions': ('add', 'change', 'remove', 'view_corp', 'view_alliance', 'view_blue'),
'verbose_name': 'corp stats',
'verbose_name_plural': 'corp stats',
'permissions': (('corp_apis', 'Can view API keys of members of their corporation.'), ('alliance_apis', 'Can view API keys of members of their alliance.'), ('blue_apis', 'Can view API keys of members of blue corporations.')),
},
),
]

View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:48
from __future__ import unicode_literals
from django.db import migrations
PERMISSIONS = {
'user': [
'corp_apis',
'alliance_apis',
],
'corpstats': {
'corp_apis': 'Can view API keys of members of their corporation.',
'alliance_apis': 'Can view API keys of members of their alliance.',
'blue_apis': 'Can view API keys of members of blue corporations.',
'view_corp_corpstats': 'Can view_corp corpstats',
'view_alliance_corpstats': 'Can view_alliance corpstats',
'view_blue_corpstats': 'Can view_blue corpstats',
}
}
def user_permissions_dict(apps):
Permission = apps.get_model('auth', 'Permission')
ContentType = apps.get_model('contenttypes', 'ContentType')
User = apps.get_model('auth', 'User')
CorpStats = apps.get_model('corputils', 'CorpStats')
user_ct = ContentType.objects.get_for_model(User)
corpstats_ct = ContentType.objects.get_for_model(CorpStats)
return {
'user': {x: Permission.objects.get_or_create(name=x, codename=x, content_type=user_ct)[0] for x in PERMISSIONS['user']},
'corpstats': {x: Permission.objects.get_or_create(codename=x, content_type=corpstats_ct)[0] for x, y in PERMISSIONS['corpstats'].items()},
}
def users_with_permission(apps, perm):
User = apps.get_model('auth', 'User')
return User.objects.filter(user_permissions=perm.pk)
def groups_with_permission(apps, perm):
Group = apps.get_model('auth', 'Group')
return Group.objects.filter(permissions=perm.pk)
def forward(apps, schema_editor):
perm_dict = user_permissions_dict(apps)
corp_users = users_with_permission(apps, perm_dict['user']['corp_apis'])
for u in corp_users:
u.user_permissions.add(perm_dict['corpstats']['corp_apis'].pk)
u.user_permissions.add(perm_dict['corpstats']['view_corp_corpstats'].pk)
alliance_users = users_with_permission(apps, perm_dict['user']['alliance_apis'])
for u in alliance_users:
u.user_permissions.add(perm_dict['corpstats']['alliance_apis'].pk)
u.user_permissions.add(perm_dict['corpstats']['view_alliance_corpstats'].pk)
corp_groups = groups_with_permission(apps, perm_dict['user']['corp_apis'])
for g in corp_groups:
g.permissions.add(perm_dict['corpstats']['corp_apis'].pk)
g.permissions.add(perm_dict['corpstats']['view_corp_corpstats'].pk)
alliance_groups = groups_with_permission(apps, perm_dict['user']['alliance_apis'])
for g in alliance_groups:
g.permissions.add(perm_dict['corpstats']['alliance_apis'].pk)
g.permissions.add(perm_dict['corpstats']['view_alliance_corpstats'].pk)
for name, perm in perm_dict['user'].items():
perm.delete()
def reverse(apps, schema_editor):
perm_dict = user_permissions_dict(apps)
corp_users = users_with_permission(apps, perm_dict['corpstats']['view_corp_corpstats'])
corp_api_users = users_with_permission(apps, perm_dict['corpstats']['corp_apis'])
corp_us = corp_users | corp_api_users
for u in corp_us.distinct():
u.user_permissions.add(perm_dict['user']['corp_apis'].pk)
for u in corp_users:
u.user_permissions.remove(perm_dict['corpstats']['view_corp_corpstats'].pk)
for u in corp_api_users:
u.user_permissions.remove(perm_dict['corpstats']['corp_apis'].pk)
alliance_users = users_with_permission(apps, perm_dict['corpstats']['view_alliance_corpstats'])
alliance_api_users = users_with_permission(apps, perm_dict['corpstats']['alliance_apis'])
alliance_us = alliance_users | alliance_api_users
for u in alliance_us.distinct():
u.user_permissions.add(perm_dict['user']['alliance_apis'].pk)
for u in alliance_users:
u.user_permissions.remove(perm_dict['corpstats']['view_alliance_corpstats'].pk)
for u in alliance_api_users:
u.user_permissions.remove(perm_dict['corpstats']['alliance_apis'].pk)
corp_groups = groups_with_permission(apps, perm_dict['corpstats']['view_corp_corpstats'])
corp_api_groups = groups_with_permission(apps, perm_dict['corpstats']['corp_apis'])
corp_gs = corp_groups | corp_api_groups
for g in corp_groups.distinct():
g.permissions.add(perm_dict['user']['corp_apis'].pk)
for g in corp_groups:
g.permissions.remove(perm_dict['corpstats']['view_corp_corpstats'].pk)
for g in corp_api_groups:
g.permissions.remove(perm_dict['corpstats']['corp_apis'].pk)
alliance_groups = groups_with_permission(apps, perm_dict['corpstats']['view_alliance_corpstats'])
alliance_api_groups = groups_with_permission(apps, perm_dict['corpstats']['alliance_apis'])
alliance_gs = alliance_groups | alliance_api_groups
for g in alliance_gs.distinct():
g.permissions.add(perm_dict['user']['alliance_apis'].pk)
for g in alliance_groups:
g.permissions.remove(perm_dict['corpstats']['view_alliance_corpstats'].pk)
for g in alliance_api_groups:
g.permissions.remove(perm_dict['corpstats']['alliance_apis'].pk)
class Migration(migrations.Migration):
dependencies = [
('corputils', '0001_initial'),
('authentication', '0005_delete_perms'),
('auth', '0008_alter_user_username_max_length'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View File

View File

@@ -1 +1,174 @@
from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible
from django.db import models
from eveonline.models import EveCorporationInfo, EveCharacter, EveApiKeyPair
from esi.models import Token
from esi.errors import TokenError
from notifications import notify
from authentication.models import AuthServicesInfo
from bravado.exception import HTTPForbidden
from corputils.managers import CorpStatsManager
from operator import attrgetter
import json
import logging
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class CorpStats(models.Model):
token = models.ForeignKey(Token, on_delete=models.CASCADE)
corp = models.OneToOneField(EveCorporationInfo)
last_update = models.DateTimeField(auto_now=True)
_members = models.TextField(default='{}')
class Meta:
permissions = (
('corp_apis', 'Can view API keys of members of their corporation.'),
('alliance_apis', 'Can view API keys of members of their alliance.'),
('blue_apis', 'Can view API keys of members of blue corporations.'),
)
default_permissions = (
'add',
'change',
'remove',
'view_corp',
'view_alliance',
'view_blue',
)
verbose_name = "corp stats"
verbose_name_plural = "corp stats"
objects = CorpStatsManager()
def __str__(self):
return "%s for %s" % (self.__class__.__name__, self.corp)
def update(self):
try:
c = self.token.get_esi_client()
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(corporation_id=self.corp.corporation_id).result()
member_ids = [m['character_id'] for m in members]
member_names = c.Character.get_characters_names(character_ids=member_ids).result()
member_list = {m['character_id']:m['character_name'] for m in member_names}
self.members = member_list
self.save()
except TokenError as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self, message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.", level="error")
self.delete()
except HTTPForbidden as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self, message="%s: %s" % (e.status_code, e.message), level="error")
self.delete()
except AssertionError:
logger.warning("%s token character no longer in corp." % self)
if self.token.user:
notify(self.token.user, "%s cannot update with your ESI token." % self, message="%s cannot update with your ESI token as you have left corp." % self, level="error")
self.delete()
@property
def members(self):
return json.loads(self._members)
@members.setter
def members(self, dict):
self._members = json.dumps(dict)
@property
def member_ids(self):
return [id for id, name in self.members.items()]
@property
def member_names(self):
return [name for id, name in self.members.items()]
def show_apis(self, user):
auth = AuthServicesInfo.objects.get_or_create(user=user)[0]
if auth.main_char_id:
try:
char = EveCharacter.objects.get(character_id=auth.main_char_id)
if char.corporation_id == self.corp.corporation_id and user.has_perm('corputils.corp_apis'):
return True
if self.corp.alliance and char.alliance_id == self.corp.alliance.alliance_id and user.has_perm('corputils.alliance_apis'):
return True
if user.has_perm('corputils.blue_apis') and self.corp.is_blue:
return True
except EveCharacter.DoesNotExist:
pass
return user.is_superuser
def entered_apis(self):
return EveCharacter.objects.filter(character_id__in=self.member_ids).exclude(api_id__isnull=True).count()
def member_count(self):
return len(self.members)
@python_2_unicode_compatible
class MemberObject(object):
def __init__(self, character_id, character_name, show_apis=False):
self.character_id = character_id
self.character_name = character_name
try:
char = EveCharacter.objects.get(character_id=character_id)
auth = AuthServicesInfo.objects.get(user=char.user)
try:
self.main = EveCharacter.objects.get(character_id=auth.main_char_id)
except EveCharacter.DoesNotExist:
self.main = None
api = EveApiKeyPair.objects.get(api_id=char.api_id)
self.registered = True
if show_apis:
self.api = api
else:
self.api = None
except (EveCharacter.DoesNotExist, AuthServicesInfo.DoesNotExist):
self.main = None
self.api = None
self.registered = False
except EveApiKeyPair.DoesNotExist:
self.api = None
self.registered = False
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 get_member_objects(self, user):
show_apis = self.show_apis(user)
return sorted([CorpStats.MemberObject(id, name, show_apis=show_apis) for id, name in self.members.items()], key=attrgetter('character_name'))
def can_update(self, user):
return user.is_superuser or user == self.token.user
@python_2_unicode_compatible
class ViewModel(object):
def __init__(self, corpstats, user):
self.corp = corpstats.corp
self.members = corpstats.get_member_objects(user)
self.can_update = corpstats.can_update(user)
self.total_members = len(self.members)
self.registered_members = corpstats.entered_apis()
self.show_apis = corpstats.show_apis(user)
self.last_updated = corpstats.last_update
def __str__(self):
return str(self.corp)
def corp_logo(self, size=128):
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
def alliance_logo(self, size=128):
if self.corp.alliance:
return "https://image.eveonline.com/Alliance/%s_%s.png" % (self.corp.alliance.alliance_id, size)
else:
return "https://image.eveonline.com/Alliance/1_%s.png" % size
def get_view_model(self, user):
return CorpStats.ViewModel(self, user)

View File

@@ -0,0 +1,39 @@
{% extends 'public/base.html' %}
{% load i18n %}
{% block title %}{% trans "Corporation Member Data" %}{% endblock %}
{% block page_title %}{% trans "Corporation Member Data" %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Corporation Member Data" %}</h1>
<div class="col-lg-10 col-lg-offset-1 container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Corporations<span class="caret"></span></a>
<ul class="dropdown-menu">
{% for corpstat in available %}
<li>
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
</li>
{% endfor %}
</ul>
</li>
{% if perms.corputils.add_corpstats %}
<li>
<a href="{% url 'corputils:add' %}">Add</a>
</li>
{% endif %}
</ul>
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
<div class="form-group">
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}Search characters...{% endif %}">
</div>
</form>
</div>
</nav>
{% block member_data %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,92 @@
{% extends 'corputils/base.html' %}
{% load i18n %}
{% load humanize %}
{% load bootstrap_pagination %}
{% block member_data %}
{% if corpstats %}
<div class="row">
<div class="col-lg-12 text-center">
<table class="table">
<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>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.alliance_logo }}"></td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<b>{% trans "API Index:" %}</b>
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ corpstats.registered_members }}" aria-valuemin="0" aria-valuemax="{{ corpstats.total_members }}" style="width: {% widthratio corpstats.registered_members corpstats.total_members 100 %}%;">
{{ corpstats.registered_members }}/{{ corpstats.total_members }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="panel-title pull-left">
<h4>Members</h4>
</div>
<div class="panel-title pull-right">
Last update: {{ corpstats.last_updated|naturaltime }}
{% if corpstats.can_update %}
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
<span class="glyphicon glyphicon-refresh"></span>
</a>
{% endif %}
</div>
</div>
<div class="panel-body">
<div class="text-center">
{% bootstrap_paginate members range=10 %}
</div>
<div class="table-responsive">
<table class="table table-hover">
<tr>
<th></th>
<th class="text-center">Character</th>
{% if corpstats.show_apis %}
<th class="text-center">API</th>
{% endif %}
<th class="text-center">zKillboard</th>
<th class="text-center">Main Character</th>
<th class="text-center">Main Corporation</th>
<th class="text-center">Main Alliance</th>
</tr>
{% for member in members %}
<tr {% if not member.registered %}class="danger"{% endif %}>
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td>
{% if corpstats.show_apis %}
{% if member.api %}
<td class="text-center"><a href="{{ JACK_KNIFE_URL }}?usid={{ member.api.api_id }}&apik={{ member.api.api_key }}" target="_blank" class="label label-primary">{{ member.api.api_id }}</td>
{% else %}
<td></td>
{% endif %}
{% endif %}
<td class="text-center"><a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">{{ member.main.character_name }}</td>
<td class="text-center">{{ member.main.corporation_name }}</td>
<td class="text-center">{{ member.main.alliance_name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "corputils/base.html" %}
{% load i18n %}
{% load bootstrap_pagination %}
{% block member_data %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="panel-title pull-left">Search Results</div>
</div>
<div class="panel-body">
<div class="text-center">
{% bootstrap_paginate results range=10 %}
</div>
<table class="table table-hover">
<tr>
<th class="text-center"></th>
<th class="text-center">Character</th>
<th class="text-center">Corporation</th>
<th class="text-center">API</th>
<th class="text-center">zKillboard</th>
<th class="text-center">Main Character</th>
<th class="text-center">Main Corporation</th>
<th class="text-center">Main Alliance</th>
</tr>
{% for result in results %}
<tr {% if not result.1.registered %}class="danger"{% endif %}>
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ result.1.character_name }}</td>
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
{% if result.1.api %}
<td class="text-center"><a href="{{ JACK_KNIFE_URL }}?usid={{ result.1.api.api_id }}&apik={{ result.1.api.api_key }}" target="_blank" class="label label-primary">{{ result.1.api.api_id }}</td>
{% else %}
<td></td>
{% endif %}
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">{{ result.1.main.character_name }}</td>
<td class="text-center">{{ result.1.main.corporation_name }}</td>
<td class="text-center">{{ result.1.main.alliance_name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

11
corputils/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.conf.urls import url
import corputils.views
app_name='corputils'
urlpatterns = [
url(r'^$', corputils.views.corpstats_view, name='view'),
url(r'^add/$', corputils.views.corpstats_add, name='add'),
url(r'^(?P<corp_id>(\d)*)/$', corputils.views.corpstats_view, name='view_corp'),
url(r'^(?P<corp_id>(\d)+)/update/$', corputils.views.corpstats_update, name='update'),
url(r'^search/$', corputils.views.corpstats_search, name='search'),
]

View File

@@ -1,325 +1,124 @@
from __future__ import unicode_literals
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.conf import settings
from eveonline.models import EveCharacter, EveCorporationInfo
from corputils.models import CorpStats
from esi.decorators import token_required
from collections import namedtuple
MEMBERS_PER_PAGE = int(getattr(settings, 'CORPSTATS_MEMBERS_PER_PAGE', 20))
from authentication.models import AuthServicesInfo
from services.managers.eve_api_manager import EveApiManager
from services.managers.evewho_manager import EveWhoManager
from eveonline.models import EveCorporationInfo
from eveonline.models import EveAllianceInfo
from eveonline.models import EveCharacter
from eveonline.models import EveApiKeyPair
from fleetactivitytracking.models import Fat
from corputils.forms import CorputilsSearchForm
from evelink.api import APIError
import logging
import datetime
logger = logging.getLogger(__name__)
class Player(object):
def __init__(self, main, user, maincorp, maincorpid, altlist, apilist, n_fats):
self.main = main
self.user = user
self.maincorp = maincorp
self.maincorpid = maincorpid
self.altlist = altlist
self.apilist = apilist
self.n_fats = n_fats
def first_day_of_next_month(year, month):
if month == 12:
return datetime.datetime(year + 1, 1, 1)
else:
return datetime.datetime(year, month + 1, 1)
def first_day_of_previous_month(year, month):
if month == 1:
return datetime.datetime(year - 1, 12, 1)
else:
return datetime.datetime(year, month - 1, 1)
def get_page(model_list, page_num):
p = Paginator(model_list, MEMBERS_PER_PAGE)
try:
members = p.page(page_num)
except PageNotAnInteger:
members = p.page(1)
except EmptyPage:
members = p.page(p.num_pages)
return members
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_blue_corpstats')
@login_required
def corp_member_view(request, corpid=None, year=datetime.date.today().year, month=datetime.date.today().month):
year = int(year)
month = int(month)
start_of_month = datetime.datetime(year, month, 1)
start_of_next_month = first_day_of_next_month(year, month)
start_of_previous_month = first_day_of_previous_month(year, month)
logger.debug("corp_member_view called by user %s" % request.user)
@user_passes_test(access_corpstats_test)
@permission_required('corputils.add_corpstats')
@token_required(scopes='esi-corporations.read_corporation_membership.v1')
def corpstats_add(request, token):
try:
user_main = EveCharacter.objects.get(
character_id=AuthServicesInfo.objects.get_or_create(user=request.user)[0].main_char_id)
user_corp_id = user_main.corporation_id
except (ValueError, EveCharacter.DoesNotExist):
user_corp_id = settings.CORP_ID
if not settings.IS_CORP:
alliance = EveAllianceInfo.objects.get(alliance_id=settings.ALLIANCE_ID)
alliancecorps = EveCorporationInfo.objects.filter(alliance=alliance)
membercorplist = [(int(membercorp.corporation_id), str(membercorp.corporation_name)) for membercorp in
alliancecorps]
membercorplist.sort(key=lambda tup: tup[1])
membercorp_id_list = [int(membercorp.corporation_id) for membercorp in alliancecorps]
bluecorps = EveCorporationInfo.objects.filter(is_blue=True)
bluecorplist = [(int(bluecorp.corporation_id), str(bluecorp.corporation_name)) for bluecorp in bluecorps]
bluecorplist.sort(key=lambda tup: tup[1])
bluecorp_id_list = [int(bluecorp.corporation_id) for bluecorp in bluecorps]
if not (user_corp_id in membercorp_id_list or user_corp_id not in bluecorp_id_list):
user_corp_id = None
if not corpid:
if settings.IS_CORP:
corpid = settings.CORP_ID
elif user_corp_id:
corpid = user_corp_id
if EveCharacter.objects.filter(character_id=token.character_id).exists():
corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
else:
corpid = membercorplist[0][0]
corp = EveCorporationInfo.objects.get(corporation_id=corpid)
if request.user.has_perm('auth.alliance_apis') or (request.user.has_perm('auth.corp_apis') and user_corp_id == corpid):
logger.debug("Retreiving and sending API-information")
if settings.IS_CORP:
try:
member_list = EveApiManager.get_corp_membertracking(settings.CORP_API_ID, settings.CORP_API_VCODE)
except APIError:
logger.debug("Corp API does not have membertracking scope, using EveWho data instead.")
member_list = EveWhoManager.get_corporation_members(corpid)
else:
member_list = EveWhoManager.get_corporation_members(corpid)
characters_with_api = {}
characters_without_api = {}
num_registered_characters = 0
for char_id, member_data in member_list.items():
try:
char = EveCharacter.objects.get(character_id=char_id)
char_owner = char.user
try:
if not char_owner:
raise AttributeError("Character has no assigned user.")
mainid = int(AuthServicesInfo.objects.get_or_create(user=char_owner)[0].main_char_id)
mainchar = EveCharacter.objects.get(character_id=mainid)
mainname = mainchar.character_name
maincorp = mainchar.corporation_name
maincorpid = mainchar.corporation_id
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except (ValueError, EveCharacter.DoesNotExist, EveApiKeyPair.DoesNotExist):
logger.debug("No main character seem to be set for character %s" % char.character_name)
mainname = "User: " + char_owner.username
mainchar = char
maincorp = "Not set."
maincorpid = None
api_pair = None
except AttributeError:
logger.debug("No associated user for character %s" % char.character_name)
mainname = None
mainchar = char
maincorp = None
maincorpid = None
try:
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except EveApiKeyPair.DoesNotExist:
api_pair = None
num_registered_characters += 1
characters_with_api.setdefault(mainname, Player(main=mainchar,
user=char_owner,
maincorp=maincorp,
maincorpid=maincorpid,
altlist=[],
apilist=[],
n_fats=0)
).altlist.append(char)
if api_pair:
characters_with_api[mainname].apilist.append(api_pair)
except EveCharacter.DoesNotExist:
characters_without_api.update({member_data["name"]: member_data["id"]})
for char in EveCharacter.objects.filter(corporation_id=corpid):
if not int(char.character_id) in member_list:
logger.debug("Character '%s' does not exist in EveWho dump." % char.character_name)
char_owner = char.user
try:
if not char_owner:
raise AttributeError("Character has no assigned user.")
mainid = int(AuthServicesInfo.objects.get_or_create(user=char_owner)[0].main_char_id)
mainchar = EveCharacter.objects.get(character_id=mainid)
mainname = mainchar.character_name
maincorp = mainchar.corporation_name
maincorpid = mainchar.corporation_id
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except (ValueError, EveCharacter.DoesNotExist, EveApiKeyPair.DoesNotExist):
logger.debug("No main character seem to be set for character %s" % char.character_name)
mainname = "User: " + char_owner.username
mainchar = char
maincorp = "Not set."
maincorpid = None
api_pair = None
except AttributeError:
logger.debug("No associated user for character %s" % char.character_name)
mainname = None
mainchar = char
maincorp = None
maincorpid = None
try:
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except EveApiKeyPair.DoesNotExist:
api_pair = None
num_registered_characters += 1
characters_with_api.setdefault(mainname, Player(main=mainchar,
user=char_owner,
maincorp=maincorp,
maincorpid=maincorpid,
altlist=[],
apilist=[],
n_fats=0)
).altlist.append(char)
if api_pair:
characters_with_api[mainname].apilist.append(api_pair)
n_unacounted = corp.member_count - (num_registered_characters + len(characters_without_api))
for mainname, player in characters_with_api.items():
fats_this_month = Fat.objects.filter(user=player.user).filter(
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lt=start_of_next_month)
characters_with_api[mainname].n_fats = len(fats_this_month)
if start_of_next_month > datetime.datetime.now():
start_of_next_month = None
if not settings.IS_CORP:
context = {"membercorplist": membercorplist,
"corp": corp,
"characters_with_api": sorted(characters_with_api.items()),
'n_registered': num_registered_characters,
'n_unacounted': n_unacounted,
"characters_without_api": sorted(characters_without_api.items()),
"search_form": CorputilsSearchForm()}
else:
logger.debug("corp_member_view running in corportation mode")
context = {"corp": corp,
"characters_with_api": sorted(characters_with_api.items()),
'n_registered': num_registered_characters,
'n_unacounted': n_unacounted,
"characters_without_api": sorted(characters_without_api.items()),
"search_form": CorputilsSearchForm()}
context["next_month"] = start_of_next_month
context["previous_month"] = start_of_previous_month
context["this_month"] = start_of_month
return render(request, 'registered/corputils.html', context=context)
else:
logger.warn('User %s (%s) not authorized to view corp stats for corp id %s' % (request.user, user_corp_id, corpid))
return redirect("auth_dashboard")
def can_see_api(user, character):
if user.has_perm('auth.alliance_apis'):
return True
try:
user_main = EveCharacter.objects.get(
character_id=AuthServicesInfo.objects.get_or_create(user=user)[0].main_char_id)
if user.has_perm('auth.corp_apis') and user_main.corporation_id == character.corporation_id:
return True
except EveCharacter.DoesNotExist:
return False
return False
corp_id = token.get_esi_client().Character.get_characters_character_id(character_id=token.character_id).result()['corporation_id']
corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
cs = CorpStats.objects.create(token=token, corp=corp)
cs.update()
assert cs.pk # ensure update was succesful
if CorpStats.objects.filter(pk=cs.pk).visible_to(request.user).exists():
return redirect('corputils:view_corp', corp_id=corp.corporation_id)
except EveCorporationInfo.DoesNotExist:
messages.error(request, 'Unrecognized corporation. Please ensure it is a member of the alliance or a blue.')
except IntegrityError:
messages.error(request, 'Selected corp already has a statistics module.')
except AssertionError:
messages.error(request, 'Failed to gather corporation statistics with selected token.')
return redirect('corputils:view')
@login_required
def corputils_search(request, corpid=settings.CORP_ID):
logger.debug("corputils_search called by user %s" % request.user)
@user_passes_test(access_corpstats_test)
def corpstats_view(request, corp_id=None):
corpstats = None
show_apis = False
corp = EveCorporationInfo.objects.get(corporation_id=corpid)
# get requested model
if corp_id:
corp = get_object_or_404(EveCorporationInfo, corporation_id=corp_id)
corpstats = get_object_or_404(CorpStats, corp=corp)
authorized = False
try:
user_main = EveCharacter.objects.get(
character_id=AuthServicesInfo.objects.get_or_create(user=request.user)[0].main_char_id)
if request.user.has_perm('auth.alliance_apis') or (
request.user.has_perm('auth.corp_apis') and (user_main.corporation_id == corpid)):
logger.debug("Retreiving and sending API-information")
authorized = True
except (ValueError, EveCharacter.DoesNotExist):
if request.user.has_perm('auth.alliance_apis'):
logger.debug("Retrieving and sending API-information")
authorized = True
# get available models
available = CorpStats.objects.visible_to(request.user)
if authorized:
if request.method == 'POST':
form = CorputilsSearchForm(request.POST)
logger.debug("Request type POST contains form valid: %s" % form.is_valid())
if form.is_valid():
# Really dumb search and only checks character name
# This can be improved but it does the job for now
searchstring = form.cleaned_data['search_string']
logger.debug("Searching for player with character name %s for user %s" % (searchstring, request.user))
# ensure we can see the requested model
if corpstats and not corpstats in available:
raise PermissionDenied('You do not have permission to view the selected corporation statistics module.')
member_list = {}
if settings.IS_CORP:
member_list = EveApiManager.get_corp_membertracking(settings.CORP_API_ID, settings.CORP_API_VCODE)
if not member_list:
logger.debug('Unable to fetch members from API. Pulling from EveWho')
member_list = EveWhoManager.get_corporation_members(corpid)
# get default model if none requested
if not corp_id and available.count() == 1:
corpstats = available[0]
SearchResult = namedtuple('SearchResult',
['name', 'id', 'main', 'api_registered', 'character', 'apiinfo'])
context = {
'available': available,
}
searchresults = []
for memberid, member_data in member_list.items():
if searchstring.lower() in member_data["name"].lower():
try:
char = EveCharacter.objects.get(character_name=member_data["name"])
user = char.user
mainid = int(AuthServicesInfo.objects.get_or_create(user=user)[0].main_char_id)
main = EveCharacter.objects.get(character_id=mainid)
if can_see_api(request.user, char):
api_registered = True
apiinfo = EveApiKeyPair.objects.get(api_id=char.api_id)
else:
api_registered = False
apiinfo = None
except EveCharacter.DoesNotExist:
api_registered = False
char = None
main = ""
apiinfo = None
# paginate
members = []
if corpstats:
page = request.GET.get('page', 1)
members = get_page(corpstats.get_member_objects(request.user), page)
searchresults.append(SearchResult(name=member_data["name"], id=memberid, main=main,
api_registered=api_registered,
character=char, apiinfo=apiinfo))
if corpstats:
context.update({
'corpstats': corpstats.get_view_model(request.user),
'members': members,
})
logger.info("Found %s members for user %s matching search string %s" % (
len(searchresults), request.user, searchstring))
return render(request, 'corputils/corpstats.html', context=context)
context = {'corp': corp, 'results': searchresults, 'search_form': CorputilsSearchForm(),
"year": datetime.datetime.now().year, "month": datetime.datetime.now().month}
return render(request, 'registered/corputilssearchview.html',
context=context)
else:
logger.debug("Form invalid - returning for user %s to retry." % request.user)
context = {'corp': corp, 'members': None, 'search_form': CorputilsSearchForm()}
return render(request, 'registered/corputilssearchview.html', context=context)
else:
logger.debug("Returning empty search form for user %s" % request.user)
return redirect("auth_corputils")
@login_required
@user_passes_test(access_corpstats_test)
def corpstats_update(request, corp_id):
corp = get_object_or_404(EveCorporationInfo, corporation_id=corp_id)
corpstats = get_object_or_404(CorpStats, corp=corp)
if corpstats.can_update(request.user):
corpstats.update()
else:
logger.warn('User %s not authorized to view corp stats for corp ID %s' % (request.user, corpid))
return redirect("auth_dashboard")
raise PermissionDenied('You do not have permission to update member data for the selected corporation statistics module.')
return redirect('corputils:view_corp', corp_id=corp.corporation_id)
@login_required
@user_passes_test(access_corpstats_test)
def corpstats_search(request):
results = []
search_string = request.GET.get('search_string', None)
if search_string:
has_similar = CorpStats.objects.filter(_members__icontains=search_string).visible_to(request.user)
for corpstats in has_similar:
similar = [(member_id, corpstats.members[member_id]) for member_id in corpstats.members if search_string.lower() in corpstats.members[member_id].lower()]
for s in similar:
results.append((corpstats, CorpStats.MemberObject(s[0], s[1], show_apis=corpstats.show_apis(request.user))))
page = request.GET.get('page', 1)
results = sorted(results, key=lambda x: x[1].character_name)
results_page = get_page(results, page)
context = {
'available': CorpStats.objects.visible_to(request.user),
'results': results_page,
'search_string': search_string,
}
return render(request, 'corputils/search.html', context=context)
return redirect('corputils:view')

View File

@@ -3,71 +3,48 @@ from eveonline.models import EveCharacter
from eveonline.models import EveApiKeyPair
from eveonline.models import EveAllianceInfo
from eveonline.models import EveCorporationInfo
from eveonline.providers import eve_adapter_factory, EveXmlProvider
from services.managers.eve_api_manager import EveApiManager
import logging
logger = logging.getLogger(__name__)
adapter = eve_adapter_factory()
class EveManager:
def __init__(self):
pass
@staticmethod
def create_character(character_id, character_name, corporation_id,
corporation_name, corporation_ticker, alliance_id,
alliance_name, user, api_id):
logger.debug("Creating model for character %s id %s" % (character_name, character_id))
if not EveCharacter.objects.filter(character_id=character_id).exists():
eve_char = EveCharacter()
eve_char.character_id = character_id
eve_char.character_name = character_name
eve_char.corporation_id = corporation_id
eve_char.corporation_name = corporation_name
eve_char.corporation_ticker = corporation_ticker
eve_char.alliance_id = alliance_id
eve_char.alliance_name = alliance_name
eve_char.user = user
eve_char.api_id = api_id
eve_char.save()
logger.info("Created new character model %s for user %s" % (eve_char, user))
else:
logger.warn("Attempting to create existing character model with id %s" % character_id)
def create_character(id, user, api_id):
return EveManager.create_character_obj(adapter.get_character(id), user, api_id)
@staticmethod
def create_characters_from_list(chars, user, api_id):
logger.debug("Creating characters from batch: %s" % chars.result)
for char in chars.result:
if not EveManager.check_if_character_exist(chars.result[char]['name']):
EveManager.create_character(chars.result[char]['id'],
chars.result[char]['name'],
chars.result[char]['corp']['id'],
chars.result[char]['corp']['name'],
EveApiManager.get_corporation_ticker_from_id(
chars.result[char]['corp']['id']),
chars.result[char]['alliance']['id'],
chars.result[char]['alliance']['name'],
user, api_id)
def create_character_obj(character, user, api_id):
EveCharacter.objects.create(
character_id = character.id,
character_name = character.name,
corporation_id = character.corp.id,
corporation_name = character.corp.name,
alliance_id = character.alliance.id,
alliance_name = character.alliance.name,
user = user,
api_id = api_id,
)
@staticmethod
def update_characters_from_list(chars):
logger.debug("Updating characters from list: %s" % chars.result)
for char in chars.result:
if EveManager.check_if_character_exist(chars.result[char]['name']):
eve_char = EveManager.get_character_by_character_name(chars.result[char]['name'])
logger.debug("Got existing character model %s" % eve_char)
eve_char.corporation_id = chars.result[char]['corp']['id']
eve_char.corporation_name = chars.result[char]['corp']['name']
eve_char.corporation_ticker = EveApiManager.get_corporation_ticker_from_id(
chars.result[char]['corp']['id'])
eve_char.alliance_id = chars.result[char]['alliance']['id']
eve_char.alliance_name = chars.result[char]['alliance']['name']
eve_char.save()
logger.info("Updated character model %s" % eve_char)
else:
logger.warn(
"Attempting to update non-existing character model with name %s" % chars.result[char]['name'])
def update_character(id):
return EveManager.update_character_obj(adapter.get_character(id))
@staticmethod
def update_character_obj(char):
model = EveCharacter.objects.get(character_id=char.id)
model.character_name = char.name
model.corporation_id = char.corp.id
model.corporation_name = char.corp.name
model.alliance_id = char.alliance.id
model.alliance_name = char.alliance.name
model.save()
@staticmethod
def create_api_keypair(api_id, api_key, user_id):
@@ -83,64 +60,80 @@ class EveManager:
logger.warn("Attempting to create existing api keypair with id %s" % api_id)
@staticmethod
def create_alliance_info(alliance_id, alliance_name, alliance_ticker, alliance_executor_corp_id,
alliance_member_count, is_blue):
logger.debug("Creating alliance info for alliance %s id %s" % (alliance_name, alliance_id))
if not EveManager.check_if_alliance_exists_by_id(alliance_id):
alliance_info = EveAllianceInfo()
alliance_info.alliance_id = alliance_id
alliance_info.alliance_name = alliance_name
alliance_info.alliance_ticker = alliance_ticker
alliance_info.executor_corp_id = alliance_executor_corp_id
alliance_info.member_count = alliance_member_count
alliance_info.is_blue = is_blue
alliance_info.save()
logger.info("Created alliance model for %s" % alliance_info)
else:
logger.warn("Attempting to create existing alliance model with id %s" % alliance_id)
def create_alliance(id, is_blue=False):
return EveManager.create_alliance_obj(adapter.get_alliance(id), is_blue=is_blue)
@staticmethod
def update_alliance_info(alliance_id, alliance_executor_corp_id, alliance_member_count, is_blue):
logger.debug("Updating alliance model with id %s" % alliance_id)
if EveManager.check_if_alliance_exists_by_id(alliance_id):
alliance_info = EveAllianceInfo.objects.get(alliance_id=alliance_id)
alliance_info.executor_corp_id = alliance_executor_corp_id
alliance_info.member_count = alliance_member_count
alliance_info.is_blue = is_blue
alliance_info.save()
logger.debug("Updated alliance model %s" % alliance_info)
else:
logger.warn("Attempting to update non-existing alliance model with id %s" % alliance_id)
def create_alliance_obj(alliance, is_blue=False):
EveAllianceInfo.objects.create(
alliance_id = alliance.id,
alliance_name = alliance.name,
alliance_ticker = alliance.ticker,
executor_corp_id = alliance.executor_corp_id,
is_blue = is_blue,
)
@staticmethod
def create_corporation_info(corp_id, corp_name, corp_ticker, corp_member_count, is_blue, alliance):
logger.debug("Creating corp info for corp %s id %s" % (corp_name, corp_id))
if not EveManager.check_if_corporation_exists_by_id(corp_id):
corp_info = EveCorporationInfo()
corp_info.corporation_id = corp_id
corp_info.corporation_name = corp_name
corp_info.corporation_ticker = corp_ticker
corp_info.member_count = corp_member_count
corp_info.is_blue = is_blue
if alliance:
corp_info.alliance = alliance
corp_info.save()
logger.info("Created corp model for %s" % corp_info)
else:
logger.warn("Attempting to create existing corp model with id %s" % corp_id)
def update_alliance(id, is_blue=None):
return EveManager.update_alliance_obj(adapter.get_alliance(id), is_blue=is_blue)
@staticmethod
def update_corporation_info(corp_id, corp_member_count, alliance, is_blue):
logger.debug("Updating corp model with id %s" % corp_id)
if EveManager.check_if_corporation_exists_by_id(corp_id):
corp_info = EveCorporationInfo.objects.get(corporation_id=corp_id)
corp_info.member_count = corp_member_count
corp_info.alliance = alliance
corp_info.is_blue = is_blue
corp_info.save()
logger.debug("Updated corp model %s" % corp_info)
else:
logger.warn("Attempting to update non-existant corp model with id %s" % corp_id)
def update_alliance_obj(alliance, is_blue=None):
model = EveAllianceInfo.objects.get(alliance_id=alliance.id)
model.executor_corp_id = alliance.executor_corp_id
model.is_blue = model.is_blue if is_blue == None else is_blue
model.save()
@staticmethod
def populate_alliance(id):
alliance_model = EveAllianceInfo.objects.get(alliance_id=id)
alliance = adapter.get_alliance(id)
for corp_id in alliance.corp_ids:
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
EveManager.create_corporation(corp_id, is_blue=alliance_model.is_blue)
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(alliance=alliance_model)
EveCorporationInfo.objects.filter(alliance=alliance_model).exclude(corporation_id__in=alliance.corp_ids).update(alliance=None)
@staticmethod
def create_corporation(id, is_blue=False):
return EveManager.create_corporation_obj(adapter.get_corp(id), is_blue=is_blue)
@staticmethod
def create_corporation_obj(corp, is_blue=False):
try:
alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
except EveAllianceInfo.DoesNotExist:
alliance = None
EveCorporationInfo.objects.create(
corporation_id = corp.id,
corporation_name = corp.name,
corporation_ticker = corp.ticker,
member_count = corp.members,
alliance = alliance,
is_blue = is_blue,
)
@staticmethod
def update_corporation(id, is_blue=None):
return EveManager.update_corporation_obj(adapter.get_corp(id), is_blue=is_blue)
@staticmethod
def update_corporation_obj(corp, is_blue=None):
model = EveCorporationInfo.objects.get(corporation_id=corp.id)
model.member_count = corp.members
try:
model.alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
except EveAllianceInfo.DoesNotExist:
model.alliance = None
model.is_blue = model.is_blue if is_blue == None else is_blue
model.save()
@staticmethod
def get_characters_from_api(api):
char_result = EveApiManager.get_characters_from_api(api.api_id, api.api_key).result
provider = EveXmlProvider(adapter=adapter)
return [provider._build_character(result) for id, result in char_result.items()]
@staticmethod
def get_api_key_pairs(user):
@@ -227,6 +220,10 @@ class EveManager:
return None
@staticmethod
def get_characters_by_api_id(api_id):
return EveCharacter.objects.filter(api_id=api_id)
@staticmethod
def get_charater_alliance_id_by_id(char_id):
if EveCharacter.objects.filter(character_id=char_id).exists():

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-16 23:22
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0004_eveapikeypair_sso_verified'),
]
operations = [
migrations.RemoveField(
model_name='eveallianceinfo',
name='member_count',
),
]

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-02 19:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0005_remove_eveallianceinfo_member_count'),
]
operations = [
migrations.AlterField(
model_name='evecharacter',
name='alliance_id',
field=models.CharField(blank=True, default='', max_length=254, null=True),
),
migrations.AlterField(
model_name='evecharacter',
name='alliance_name',
field=models.CharField(blank=True, default='', max_length=254, null=True),
),
]

View File

@@ -11,8 +11,8 @@ class EveCharacter(models.Model):
corporation_id = models.CharField(max_length=254)
corporation_name = models.CharField(max_length=254)
corporation_ticker = models.CharField(max_length=254)
alliance_id = models.CharField(max_length=254)
alliance_name = models.CharField(max_length=254)
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='')
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
api_id = models.CharField(max_length=254)
user = models.ForeignKey(User, blank=True, null=True)
@@ -38,7 +38,6 @@ class EveAllianceInfo(models.Model):
alliance_ticker = models.CharField(max_length=254)
executor_corp_id = models.CharField(max_length=254)
is_blue = models.BooleanField(default=False)
member_count = models.IntegerField()
def __str__(self):
return self.alliance_name

279
eveonline/providers.py Normal file
View File

@@ -0,0 +1,279 @@
from django.utils.encoding import python_2_unicode_compatible
from esi.clients import esi_client_factory
from django.conf import settings
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
import evelink
@python_2_unicode_compatible
class ObjectNotFound(Exception):
def __init__(self, id, type):
self.id = id
self.type = type
def __str__(self):
return '%s with ID %s not found.' % (self.type, self.id)
@python_2_unicode_compatible
class Entity(object):
def __init__(self, id, name):
self.id = id
self.name = name
def __str__(self):
return self.name
def __repr__(self):
return "<{} ({}): {}>".format(self.__class__.__name__, self.id, self.name)
def __bool__(self):
return bool(self.id)
def __eq__(self, other):
return self.id == other.id
class Corporation(Entity):
def __init__(self, provider, id, name, ticker, ceo_id, members, alliance_id):
super(Corporation, self).__init__(id, name)
self.provider = provider
self.ticker = ticker
self.ceo_id = ceo_id
self.members = members
self.alliance_id = alliance_id
@property
def alliance(self):
if self.alliance_id:
return self.provider.get_alliance(self.alliance_id)
return Entity(None, None)
@property
def ceo(self):
return self.provider.get_character(self.ceo_id)
class Alliance(Entity):
def __init__(self, provider, id, name, ticker, corp_ids, executor_corp_id):
super(Alliance, self).__init__(id, name)
self.provider = provider
self.ticker = ticker
self.corp_ids = corp_ids
self.executor_corp_id = executor_corp_id
def corp(self, id):
assert id in self.corp_ids
return self.provider.get_corp(id)
@property
def corps(self):
return sorted([self.corp(id) for id in self.corp_ids], key=lambda x: x.name)
@property
def executor_corp(self):
return self.provider.get_corp(self.executor_corp_id)
class Character(Entity):
def __init__(self, provider, id, name, corp_id, alliance_id):
super(Character, self).__init__(id, name)
self.provider = provider
self.corp_id = corp_id
self.alliance_id = alliance_id
@property
def corp(self):
return self.provider.get_corp(self.corp_id)
@property
def alliance(self):
if self.alliance_id:
return self.provider.get_alliance(self.alliance_id)
return Entity(None, None)
class EveProvider:
def get_alliance(self, alliance_id):
"""
:return: an Alliance object for the given ID
"""
raise NotImplementedError()
def get_corp(self, corp_id):
"""
:return: a Corporation object for the given ID
"""
raise NotImplementedError()
def get_character(self, corp_id):
"""
:return: a Character object for the given ID
"""
raise NotImplementedError()
@python_2_unicode_compatible
class EveSwaggerProvider(EveProvider):
def __init__(self, token=None, adapter=None):
self.client = esi_client_factory(token=token)
self.adapter = adapter or self
def __str__(self):
return 'esi'
def get_alliance(self, id):
try:
data = self.client.Alliance.get_alliances_alliance_id(alliance_id=id).result()
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=id).result()
model = Alliance(
self.adapter,
id,
data['alliance_name'],
data['ticker'],
corps,
data['executor_corp'],
)
return model
except HTTPNotFound:
raise ObjectNotFound(id, 'alliance')
def get_corp(self, id):
try:
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=id).result()
model = Corporation(
self.adapter,
id,
data['corporation_name'],
data['ticker'],
data['ceo_id'],
data['member_count'],
data['alliance_id'] if 'alliance_id' in data else None,
)
return model
except HTTPNotFound:
raise ObjectNotFound(id, 'corporation')
def get_character(self, id):
try:
data = self.client.Character.get_characters_character_id(character_id=id).result()
alliance_id = self.adapter.get_corp(data['corporation_id']).alliance_id
model = Character(
self.adapter,
id,
data['name'],
data['corporation_id'],
alliance_id,
)
return model
except (HTTPNotFound, HTTPUnprocessableEntity):
raise ObjectNotFound(id, 'character')
@python_2_unicode_compatible
class EveXmlProvider(EveProvider):
def __init__(self, api_key=None, adapter=None):
"""
:param api_key: eveonline.EveApiKeyPair
"""
self.api = evelink.api.API(api_key=(api_key.api_id, api_key.api_key)) if api_key else evelink.api.API()
self.adapter = adapter or self
def __str__(self):
return 'xml'
def get_alliance(self, id):
api = evelink.eve.EVE(api=self.api)
alliances = api.alliances().result
try:
results = alliances[int(id)]
except KeyError:
raise ObjectNotFound(id, 'alliance')
model = Alliance(
self.adapter,
id,
results['name'],
results['ticker'],
results['member_corps'],
results['executor_id'],
)
return model
def get_corp(self, id):
api = evelink.corp.Corp(api=self.api)
try:
corpinfo = api.corporation_sheet(corp_id=int(id)).result
except evelink.api.APIError as e:
if int(e.code) == 523:
raise ObjectNotFound(id, 'corporation')
raise e
model = Corporation(
self.adapter,
id,
corpinfo['name'],
corpinfo['ceo']['id'],
corpinfo['members']['current'],
corpinfo['ticker'],
corpinfo['alliance']['id'] if corpinfo['alliance'] else None,
)
return model
def _build_character(self, result):
return Character(
self.adapter,
result['id'],
result['name'],
result['corp']['id'],
result['alliance']['id'],
)
def get_character(self, id):
api = evelink.eve.EVE(api=self.api)
try:
charinfo = api.character_info_from_id(id).result
except evelink.api.APIError as e:
if int(e.code) == 105:
raise ObjectNotFound(id, 'character')
raise e
return self._build_character(charinfo)
class EveAdapter(EveProvider):
"""
Redirects queries to appropriate data source.
"""
def __init__(self, char_provider, corp_provider, alliance_provider):
self.char_provider = char_provider
self.corp_provider = corp_provider
self.alliance_provider = alliance_provider
self.char_provider.adapter = self
self.corp_provider.adapter = self
self.alliance_provider.adapter = self
def __repr__(self):
return "<{} (char:{}, corp:{}, alliance:{})>".format(self.__class__.__name__, str(self.char_provider), str(self.corp_provider), str(self.alliance_provider))
def get_character(self, id):
return self.char_provider.get_character(id)
def get_corp(self, id):
return self.corp_provider.get_corp(id)
def get_alliance(self, id):
return self.alliance_provider.get_alliance(id)
def eve_adapter_factory(character_source=settings.EVEONLINE_CHARACTER_PROVIDER, corp_source=settings.EVEONLINE_CORP_PROVIDER, alliance_source=settings.EVEONLINE_ALLIANCE_PROVIDER, api_key=None, token=None):
sources = [character_source, corp_source, alliance_source]
providers = []
xml = EveXmlProvider(api_key=api_key)
esi = EveSwaggerProvider(token=token)
for source in sources:
if source == 'xml':
providers.append(xml)
elif source == 'esi':
providers.append(esi)
else:
raise ValueError('Unrecognized data source "%s"' % source)
return EveAdapter(providers[0], providers[1], providers[2])

View File

@@ -12,6 +12,7 @@ from services.managers.eve_api_manager import EveApiManager
from eveonline.models import EveCharacter
from eveonline.models import EveCorporationInfo
from eveonline.models import EveAllianceInfo
from eveonline.providers import eve_adapter_factory
from authentication.tasks import set_state
import logging
import evelink
@@ -26,21 +27,16 @@ def refresh_api(api):
try:
EveApiManager.validate_api(api.api_id, api.api_key, api.user)
# Update characters
characters = EveApiManager.get_characters_from_api(api.api_id, api.api_key)
EveManager.update_characters_from_list(characters)
new_character = False
for char in characters.result:
# Ensure we have a model for all characters on key
if not EveManager.check_if_character_exist(characters.result[char]['name']):
logger.debug(
"API key %s has a new character on the account: %s" % (api.api_id, characters.result[char]['name']))
new_character = True
if new_character:
logger.debug("Creating new character %s from api key %s" % (characters.result[char]['name'], api.api_id))
EveManager.create_characters_from_list(characters, api.user, api.api_id)
characters = EveManager.get_characters_from_api(api)
for c in characters:
try:
EveManager.update_character_obj(c)
except EveCharacter.DoesNotExist:
logger.debug("API key %s has a new character on the account: %s" % (api.api_id, c))
EveManager.create_character_obj(c, api.user, api.api_id)
current_chars = EveCharacter.objects.filter(api_id=api.api_id)
for c in current_chars:
if not int(c.character_id) in characters.result:
if not int(c.character_id) in [c.id for c in characters]:
logger.info("Character %s no longer found on API ID %s" % (c, api.api_id))
c.delete()
except evelink.api.APIError as e:
@@ -105,153 +101,53 @@ def run_api_refresh():
refresh_user_apis.delay(u)
def populate_alliance(id, blue=False):
logger.debug("Populating alliance model with id %s blue %s" % (id, blue))
alliance_info = EveApiManager.get_alliance_information(id)
if not alliance_info:
raise ValueError("Supplied alliance id %s is invalid" % id)
if not EveAllianceInfo.objects.filter(alliance_id=id).exists():
EveManager.create_alliance_info(alliance_info['id'], alliance_info['name'], alliance_info['ticker'],
alliance_info['executor_id'], alliance_info['member_count'], blue)
alliance = EveAllianceInfo.objects.get(alliance_id=id)
for member_corp in alliance_info['member_corps']:
if EveCorporationInfo.objects.filter(corporation_id=member_corp).exists():
corp = EveCorporationInfo.objects.get(corporation_id=member_corp)
if corp.alliance != alliance:
corp.alliance = alliance
corp.save()
else:
logger.info("Creating new alliance member corp id %s" % member_corp)
corpinfo = EveApiManager.get_corporation_information(member_corp)
EveManager.create_corporation_info(corpinfo['id'], corpinfo['name'], corpinfo['ticker'],
corpinfo['members']['current'], blue, alliance)
@task
def update_corp(id):
EveManager.update_corporation(id)
@task
def update_alliance(id):
alliance = EveAllianceInfo.objects.get(alliance_id=id)
corps = EveCorporationInfo.objects.filter(alliance=alliance)
logger.debug("Updating alliance %s with %s member corps" % (alliance, len(corps)))
allianceinfo = EveApiManager.get_alliance_information(alliance.alliance_id)
if allianceinfo:
EveManager.update_alliance_info(allianceinfo['id'], allianceinfo['executor_id'],
allianceinfo['member_count'], alliance.is_blue)
for corp in corps:
if corp.corporation_id in allianceinfo['member_corps'] is False:
logger.info("Corp %s no longer in alliance %s" % (corp, alliance))
corp.alliance = None
corp.save()
populate_alliance(alliance.alliance_id, blue=alliance.is_blue)
elif EveApiManager.check_if_alliance_exists(alliance.alliance_id) is False:
logger.info("Alliance %s has closed. Deleting model" % alliance)
alliance.delete()
@task
def update_corp(id):
corp = EveCorporationInfo.objects.get(corporation_id=id)
logger.debug("Updating corp %s" % corp)
corpinfo = EveApiManager.get_corporation_information(corp.corporation_id)
if corpinfo:
alliance = None
if EveAllianceInfo.objects.filter(alliance_id=corpinfo['alliance']['id']).exists():
alliance = EveAllianceInfo.objects.get(alliance_id=corpinfo['alliance']['id'])
EveManager.update_corporation_info(corpinfo['id'], corpinfo['members']['current'], alliance, corp.is_blue)
elif EveApiManager.check_if_corp_exists(corp.corporation_id) is False:
logger.info("Corp %s has closed. Deleting model" % corp)
corp.delete()
# Run Every 2 hours
EveManager.update_alliance(id)
EveManager.populate_alliance(id)
@periodic_task(run_every=crontab(minute=0, hour="*/2"))
def run_corp_update():
if EveApiManager.check_if_api_server_online() is False:
if not EveApiManager.check_if_api_server_online():
logger.warn("Aborted updating corp and alliance models: API server unreachable")
return
standing_level = 'alliance'
# generate member corps
for corp_id in settings.STR_CORP_IDS:
if EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
update_corp(corp_id)
else:
EveManager.create_corporation(corp_id)
# generate member alliances
for alliance_id in settings.STR_ALLIANCE_IDS:
if EveAllianceInfo.objects.filter(alliance_id=alliance_id).exists():
logger.debug("Updating existing owner alliance model with id %s" % alliance_id)
update_alliance(alliance_id)
else:
EveManager.create_alliance(alliance_id)
EveManager.populate_alliance(alliance_id)
# update existing corp models
for corp in EveCorporationInfo.objects.all():
update_corp.delay(corp.corporation_id)
# update existing alliance models
for alliance in EveAllianceInfo.objects.all():
update_alliance.delay(alliance.alliance_id)
try:
# get corp info for owning corp if required
ownercorpinfo = {}
if settings.IS_CORP:
standing_level = 'corp'
logger.debug("Getting information for owning corp with id %s" % settings.CORP_ID)
ownercorpinfo = EveApiManager.get_corporation_information(settings.CORP_ID)
if not ownercorpinfo:
logger.error("Failed to retrieve corp info for owning corp id %s - bad corp id?" % settings.CORP_ID)
return
# check if we need to update an alliance model
alliance_id = ''
if ownercorpinfo and ownercorpinfo['alliance']['id']:
alliance_id = ownercorpinfo['alliance']['id']
elif settings.IS_CORP is False:
alliance_id = settings.ALLIANCE_ID
# get and create alliance info for owning alliance if required
alliance = None
if alliance_id:
logger.debug("Getting information for owning alliance with id %s" % alliance_id)
ownerallianceinfo = EveApiManager.get_alliance_information(alliance_id)
if not ownerallianceinfo:
logger.error("Failed to retrieve corp info for owning alliance id %s - bad alliance id?" % alliance_id)
return
if EveAllianceInfo.objects.filter(alliance_id=ownerallianceinfo['id']).exists():
logger.debug("Updating existing owner alliance model with id %s" % alliance_id)
EveManager.update_alliance_info(ownerallianceinfo['id'], ownerallianceinfo['executor_id'],
ownerallianceinfo['member_count'], False)
else:
populate_alliance(alliance_id)
alliance = EveAllianceInfo.objects.get(alliance_id=alliance_id)
# create corp info for owning corp if required
if ownercorpinfo:
if EveCorporationInfo.objects.filter(corporation_id=ownercorpinfo['id']).exists():
logger.debug("Updating existing owner corp model with id %s" % ownercorpinfo['id'])
EveManager.update_corporation_info(ownercorpinfo['id'], ownercorpinfo['members']['current'], alliance,
False)
else:
logger.info("Creating model for owning corp with id %s" % ownercorpinfo['id'])
EveManager.create_corporation_info(ownercorpinfo['id'], ownercorpinfo['name'], ownercorpinfo['ticker'],
ownercorpinfo['members']['current'], False, alliance)
# validate and create corp models for member corps of owning alliance
if alliance:
current_corps = EveCorporationInfo.objects.filter(alliance=alliance)
for corp in current_corps:
if corp.corporation_id in ownerallianceinfo['member_corps'] is False:
logger.info("Corp %s is no longer in owning alliance %s - updating model." % (corp, alliance))
corp.alliance = None
corp.save()
for member_corp in ownerallianceinfo['member_corps']:
if EveCorporationInfo.objects.filter(corporation_id=member_corp).exists():
corp = EveCorporationInfo.objects.get(corporation_id=member_corp)
if corp.alliance == alliance is not True:
logger.info("Associating corp %s with owning alliance %s" % (corp, alliance))
corp.alliance = alliance
corp.save()
else:
corpinfo = EveApiManager.get_corporation_information(member_corp)
logger.info("Creating model for owning alliance member corp with id %s" % corpinfo['id'])
EveManager.create_corporation_info(corpinfo['id'], corpinfo['name'], corpinfo['ticker'],
corpinfo['members']['current'], False, alliance)
# update existing corp models
for corp in EveCorporationInfo.objects.all():
update_corp.delay(corp.corporation_id)
# update existing alliance models
for alliance in EveAllianceInfo.objects.all():
update_alliance.delay(alliance.alliance_id)
# create standings
standings = EveApiManager.get_corp_standings()
if standings:
standings = standings[standing_level]
standings = standings[settings.STANDING_LEVEL]
for standing in standings:
if int(standings[standing]['standing']) >= settings.BLUE_STANDING:
if float(standings[standing]['standing']) >= settings.BLUE_STANDING:
logger.debug("Standing %s meets threshold" % standing)
if EveApiManager.check_if_id_is_alliance(standing):
logger.debug("Standing %s is an alliance" % standing)
@@ -262,7 +158,7 @@ def run_corp_update():
alliance.is_blue = True
alliance.save()
else:
populate_alliance(standing, blue=True)
EveManager.create_alliance(standing, blue=True)
elif EveApiManager.check_if_id_is_corp(standing):
logger.debug("Standing %s is a corp" % standing)
if EveCorporationInfo.objects.filter(corporation_id=standing).exists():
@@ -273,13 +169,7 @@ def run_corp_update():
corp.save()
else:
logger.info("Creating model for blue corp with id %s" % standing)
corpinfo = EveApiManager.get_corporation_information(standing)
corp_alliance = None
if EveAllianceInfo.objects.filter(alliance_id=corpinfo['alliance']['id']).exists():
logger.debug("New corp model for standing %s has existing alliance model" % standing)
corp_alliance = EveAllianceInfo.objects.get(alliance_id=corpinfo['alliance']['id'])
EveManager.create_corporation_info(corpinfo['id'], corpinfo['name'], corpinfo['ticker'],
corpinfo['members']['current'], True, corp_alliance)
EveManager.create_corporation(standing, blue=True)
# update alliance standings
for alliance in EveAllianceInfo.objects.filter(is_blue=True):
@@ -310,43 +200,26 @@ def run_corp_update():
logger.info("Corp %s is no longer blue" % corp)
corp.is_blue = False
corp.save()
except evelink.api.APIError as e:
logger.error("Model update failed with error code %s" % e.code)
# delete unnecessary alliance models
for alliance in EveAllianceInfo.objects.filter(is_blue=False):
logger.debug("Checking to delete alliance %s" % alliance)
if not settings.IS_CORP:
if not alliance.alliance_id == settings.ALLIANCE_ID:
logger.info("Deleting unnecessary alliance model %s" % alliance)
alliance.delete()
else:
if not alliance.evecorporationinfo_set.filter(corporation_id=settings.CORP_ID).exists():
logger.info("Deleting unnecessary alliance model %s" % alliance)
alliance.delete()
# delete unnecessary alliance models
for alliance in EveAllianceInfo.objects.filter(is_blue=False):
logger.debug("Checking to delete alliance %s" % alliance)
if not alliance.alliance_id in settings.STR_ALLIANCE_IDS:
logger.info("Deleting unnecessary alliance model %s" % alliance)
alliance.delete()
# delete unnecessary corp models
for corp in EveCorporationInfo.objects.filter(is_blue=False):
logger.debug("Checking to delete corp %s" % corp)
if not settings.IS_CORP:
if corp.alliance:
logger.debug("Corp %s has alliance %s" % (corp, corp.alliance))
if not corp.alliance.alliance_id == settings.ALLIANCE_ID:
logger.info("Deleting unnecessary corp model %s" % corp)
corp.delete()
else:
# delete unnecessary corp models
for corp in EveCorporationInfo.objects.filter(is_blue=False):
logger.debug("Checking to delete corp %s" % corp)
if not corp.corporation_id in settings.STR_CORP_IDS:
logger.debug("Corp %s is not member corp" % corp)
if corp.alliance:
logger.debug("Corp %s has alliance %s" % (corp, corp.alliance))
if not corp.alliance.alliance_id in settings.STR_ALLIANCE_IDS:
logger.info("Deleting unnecessary corp model %s" % corp)
corp.delete()
else:
if corp.corporation_id != settings.CORP_ID:
logger.debug("Corp %s is not owning corp" % corp)
if corp.alliance:
logger.debug("Corp %s has alliance %s" % (corp, corp.alliance))
if not corp.alliance.evecorporationinfo_set.filter(corporation_id=settings.CORP_ID).exists():
logger.info("Deleting unnecessary corp model %s" % corp)
corp.delete()
else:
logger.info("Deleting unnecessary corp model %s" % corp)
corp.delete()
else:
logger.debug("Corp %s is owning corp" % corp)
except evelink.api.APIError as e:
logger.error("Model update failed with error code %s" % e.code)
logger.info("Deleting unnecessary corp model %s" % corp)
corp.delete()

View File

@@ -12,7 +12,7 @@ from authentication.models import AuthServicesInfo
from authentication.tasks import set_state
from eveonline.tasks import refresh_api
from eve_sso.decorators import token_required
from esi.decorators import token_required
from django.conf import settings
import logging
@@ -46,16 +46,15 @@ def add_api_key(request):
api_key.save()
owner = request.user
# Grab characters associated with the key pair
characters = EveApiManager.get_characters_from_api(form.cleaned_data['api_id'],
form.cleaned_data['api_key'])
EveManager.create_characters_from_list(characters, owner, form.cleaned_data['api_id'])
characters = EveManager.get_characters_from_api(api_key)
[EveManager.create_character_obj(c, owner, api_key.api_id) for c in characters if not EveCharacter.objects.filter(character_id=c.id).exists()]
logger.info("Successfully processed api add form for user %s" % request.user)
if not settings.API_SSO_VALIDATION:
messages.success(request, 'Added API key %s to your account.' % form.cleaned_data['api_id'])
auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
if not auth.main_char_id:
return redirect('auth_characters')
return redirect("/api_key_management/")
return redirect("auth_dashboard")
else:
logger.debug('Requesting SSO validation of API %s by user %s' % (api_key.api_id, request.user))
return render(request, 'registered/apisso.html', context={'api':api_key})
@@ -70,18 +69,17 @@ def add_api_key(request):
@login_required
@token_required(new=True)
def api_sso_validate(request, tokens, api_id):
def api_sso_validate(request, token, api_id):
logger.debug('api_sso_validate called by user %s for api %s' % (request.user, api_id))
api = get_object_or_404(EveApiKeyPair, api_id=api_id)
if api.user and api.user != request.user:
logger.warning('User %s attempting to take ownership of api %s from %s' % (request.user, api_id, api.user))
messages.warning(request, 'API %s already claimed by user %s' % (api_id, api.user))
return redirect('auth_api_key_management')
return redirect('auth_dashboard')
elif api.sso_verified:
logger.debug('API %s has already been verified.' % api_id)
messages.info(request, 'API %s has already been verified' % api_id)
return redirect('auth_api_key_management')
token = tokens[0]
return redirect('auth_dashboard')
logger.debug('API %s has not been verified. Checking if token for %s matches.' % (api_id, token.character_name))
characters = EveApiManager.get_characters_from_api(api.api_id, api.api_key).result
if token.character_id in characters:
@@ -93,21 +91,34 @@ def api_sso_validate(request, tokens, api_id):
auth, c = AuthServicesInfo.objects.get_or_create(user=request.user)
if not auth.main_char_id:
return redirect('auth_characters')
return redirect('auth_api_key_management')
return redirect('auth_dashboard')
else:
messages.warning(request, '%s not found on API %s. Please SSO as a character on the API.' % (token.character_name, api.api_id))
return render(request, 'registered/apisso.html', context={'api':api})
@login_required
def api_key_management_view(request):
logger.debug("api_key_management_view called by user %s" % request.user)
context = {
'apikeypairs': EveManager.get_api_key_pairs(request.user.id),
'api_sso_validation': settings.API_SSO_VALIDATION or False
}
def dashboard_view(request):
logger.debug("dashboard_view called by user %s" % request.user)
auth_info = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
apikeypairs = EveManager.get_api_key_pairs(request.user.id)
sso_validation = settings.API_SSO_VALIDATION or False
api_chars = []
return render(request, 'registered/apikeymanagment.html', context=context)
if apikeypairs:
for api in apikeypairs:
api_chars.append({
'id': api.api_id,
'sso_verified': api.sso_verified if sso_validation else True,
'characters': EveManager.get_characters_by_api_id(api.api_id),
})
context = {
'main': EveManager.get_character_by_id(auth_info.main_char_id),
'apis': api_chars,
'api_sso_validation': settings.API_SSO_VALIDATION or False,
}
return render(request, 'registered/dashboard.html', context=context)
@login_required
@@ -118,13 +129,11 @@ def api_key_removal(request, api_id):
EveManager.delete_characters_by_api_id(api_id, request.user.id)
messages.success(request, 'Deleted API key %s' % api_id)
logger.info("Succesfully processed api delete request by user %s for api %s" % (request.user, api_id))
if EveCharacter.objects.filter(character_id=authinfo.main_char_id).exists():
return redirect("auth_api_key_management")
else:
if not EveCharacter.objects.filter(character_id=authinfo.main_char_id).exists():
authinfo.main_char_id = None
authinfo.save()
set_state(request.user)
return redirect("auth_characters")
return redirect("auth_dashboard")
@login_required
@@ -142,7 +151,7 @@ def main_character_change(request, char_id):
AuthServicesInfoManager.update_main_char_id(char_id, request.user)
messages.success(request, 'Changed main character ID to %s' % char_id)
set_state(request.user)
return redirect("auth_characters")
return redirect("auth_dashboard")
messages.error(request, 'Failed to change main character - selected character is not owned by your account.')
return redirect("auth_characters")
@@ -162,4 +171,4 @@ def user_refresh_api(request, api_id):
else:
messages.warning(request, 'Unable to locate API key %s' % api_id)
logger.warn("User %s unable to refresh api id %s - api key not found" % (request.user, api_id))
return redirect("auth_api_key_management")
return redirect("auth_dashboard")

View File

@@ -0,0 +1,25 @@
{% extends 'public/base.html' %}
{% block title %}Fleet Participation{% endblock %}
{% block page_title %}Fleet Participation{% endblock %}
<div class="col-lg-12">
<h1 class="page-header text-center">Character not found!</h1>
<div class="col-lg-12 container" id="example">
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">{{ character_name }}</div>
<div class="panel-body">
<div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Character/{{ character_id }}_128.jpg">
</div>
<div class="col-lg-10 col-sm-2">
<div class="alert alert-danger" role="alert">Character not registered!</div>
This character is not part of any registered API-key. You must go to <a href=" {% url 'auth_api_key_management' %}">API key management</a> and add an API with the character on before being able to click fleet attendance links.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% extends "public/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% load bootstrap_pagination %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Fatlink view" %}{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Edit fatlink "{{ fatlink }}"{% endblocktrans %}
<div class="text-right">
<form>
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
{% trans "Delete fat" %}
</button>
</form>
</div>
</h1>
<div class="panel panel-default">
<div class="panel-heading">{% trans "Registered characters" %}</div>
<div class="panel-body">
<div class="text-center">
{% bootstrap_paginate registered_fats range=10 %}
</div>
<table class="table table-responsive table-hover">
<tr>
<th class="text-center">{% trans "User" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "System" %}</th>
<th class="text-center">{% trans "Ship" %}</th>
<th class="text-center">{% trans "Eve Time" %}</th>
<th></th>
</tr>
{% for fat in registered_fats %}
<tr>
<td class="text-center">{{ fat.user }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %}
<td class="text-center">Docked in {{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}
<td class="text-center">{{ fat.shiptype }}</td>
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
<td class="text-center">
<form>
<button type="submit" class="btn btn-warning" name="removechar" value="{{ fat.character.character_id }}">
<span class="glyphicon glyphicon-remove"></span>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
<script src="/static/js/dateformat.js"></script>
{% endblock content %}

View File

@@ -6,16 +6,17 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import permission_required
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.contrib import messages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from eveonline.models import EveCharacter
from eveonline.models import EveCorporationInfo
from eveonline.managers import EveManager
from fleetactivitytracking.forms import FatlinkForm
from fleetactivitytracking.models import Fatlink, Fat
from slugify import slugify
from esi.decorators import token_required
from collections import OrderedDict
from slugify import slugify
import string
import random
@@ -25,15 +26,27 @@ import logging
logger = logging.getLogger(__name__)
FATS_PER_PAGE = int(getattr(settings, 'FATS_PER_PAGE', 20))
def get_page(model_list, page_num):
p = Paginator(model_list, FATS_PER_PAGE)
try:
fats = p.page(page_num)
except PageNotAnInteger:
fatss = p.page(1)
except EmptyPage:
fatss = p.page(p.num_pages)
return fats
class CorpStat(object):
def __init__(self, corp_id, corp=None, blue=False):
def __init__(self, corp_id, start_of_month, start_of_next_month, corp=None):
if corp:
self.corp = corp
else:
self.corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
self.n_fats = 0
self.blue = blue
self.n_fats = Fat.objects.filter(character__corporation_id=self.corp.corporation_id).filter(fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
self.blue = self.corp.is_blue
def avg_fat(self):
return "%.2f" % (float(self.n_fats) / float(self.corp.member_count))
@@ -69,7 +82,7 @@ def fatlink_view(request):
else:
context = {'user': user, 'fats': latest_fats}
return render(request, 'registered/fatlinkview.html', context=context)
return render(request, 'fleetactivitytracking/fatlinkview.html', context=context)
@login_required
@@ -81,37 +94,38 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
start_of_next_month = first_day_of_next_month(year, month)
start_of_previous_month = first_day_of_previous_month(year, month)
fatStats = OrderedDict()
fat_stats = {}
if settings.IS_CORP:
fatStats[settings.CORP_NAME] = CorpStat(settings.CORP_ID)
else:
alliance_corps = EveCorporationInfo.objects.filter(alliance__alliance_id=settings.ALLIANCE_ID)
# get FAT stats for member corps
for corp_id in settings.STR_CORP_IDS:
fat_stats[corp_id] = CorpStat(corp_id, start_of_month, start_of_next_month)
for alliance_id in settings.STR_ALLIANCE_IDS:
alliance_corps = EveCorporationInfo.objects.filter(alliance__alliance_id=alliance_id)
for corp in alliance_corps:
fatStats[corp.corporation_name] = CorpStat(corp.corporation_id, corp=corp)
fat_stats[corp.corporation_id] = CorpStat(corp.corporation_id, start_of_month, start_of_next_month)
fatlinks_in_span = Fatlink.objects.filter(fatdatetime__gte=start_of_month).filter(
fatdatetime__lt=start_of_next_month)
# get FAT stats for corps not in alliance
fats_in_span = Fat.objects.filter(fatlink__fatdatetime__gte=start_of_month).filter(
fatlink__fatdatetime__lt=start_of_next_month).exclude(character__corporation_id__in=fat_stats)
for fatlink in fatlinks_in_span:
fats_in_fatlink = Fat.objects.filter(fatlink=fatlink)
for fat in fats_in_fatlink:
fatStats.setdefault(fat.character.corporation_name,
CorpStat(fat.character.corporation_id, blue=True)
).n_fats += 1
for fat in fats_in_span:
if not fat.character.corporation_id in fat_stats:
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month, start_of_next_month)
fatStatsList = [fatStat for corp_name, fatStat in fatStats.items()]
fatStatsList.sort(key=lambda stat: stat.corp.corporation_name)
fatStatsList.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.corp.member_count), reverse=True)
# collect and sort stats
stat_list = [fat_stats[x] for x in fat_stats]
stat_list.sort(key=lambda stat: stat.corp.corporation_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.corp.member_count), reverse=True)
if datetime.datetime.now() > start_of_next_month:
context = {'fatStats': fatStatsList, 'month': start_of_month.strftime("%B"), 'year': year,
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
'previous_month': start_of_previous_month, 'next_month': start_of_next_month}
else:
context = {'fatStats': fatStatsList, 'month': start_of_month.strftime("%B"), 'year': year,
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
'previous_month': start_of_previous_month}
return render(request, 'registered/fatlinkstatisticsview.html', context=context)
return render(request, 'fleetactivitytracking/fatlinkstatisticsview.html', context=context)
@login_required
@@ -139,7 +153,7 @@ def fatlink_personal_statistics_view(request, year=datetime.date.today().year, m
else:
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1}
return render(request, 'registered/fatlinkpersonalstatisticsview.html', context=context)
return render(request, 'fleetactivitytracking/fatlinkpersonalstatisticsview.html', context=context)
@login_required
@@ -173,55 +187,58 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
context["created_fats"] = created_fats
context["n_created_fats"] = len(created_fats)
return render(request, 'registered/fatlinkpersonalmonthlystatisticsview.html', context=context)
return render(request, 'fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html', context=context)
@login_required
def click_fatlink_view(request, hash, fatname):
# Take IG-header data and register the fatlink if not existing already.
# use obj, created = Fat.objects.get_or_create()
# onload="CCPEVE.requestTrust('http://www.mywebsite.com')"
@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):
try:
fatlink = Fatlink.objects.filter(hash=hash)[0]
if 'HTTP_EVE_TRUSTED' in request.META and request.META['HTTP_EVE_TRUSTED'] == "Yes":
# Retrieve the latest fatlink using the hash.
try:
fatlink = Fatlink.objects.filter(hash=hash)[0]
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
character = EveManager.get_character_by_id(token.character_id)
character = EveManager.get_character_by_id(request.META['HTTP_EVE_CHARID'])
if character:
fat = Fat()
fat.system = request.META['HTTP_EVE_SOLARSYSTEMNAME']
if 'HTTP_EVE_STATIONNAME' in request.META:
fat.station = request.META['HTTP_EVE_STATIONNAME']
else:
fat.station = "No Station"
fat.shiptype = request.META['HTTP_EVE_SHIPTYPENAME']
fat.fatlink = fatlink
fat.character = character
fat.user = character.user
try:
fat.full_clean()
fat.save()
context = {'trusted': True, 'registered': True}
except ValidationError as e:
messages = []
for errorname, message in e.message_dict.items():
messages.append(message[0].decode())
context = {'trusted': True, 'errormessages': messages}
if character:
# get data
c = token.get_esi_client()
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
location['solar_system_name'] = c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['solar_system_name']
if location['structure_id']:
location['station_name'] = c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()['name']
elif location['station_id']:
location['station_name'] = c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['station_name']
else:
context = {'character_id': request.META['HTTP_EVE_CHARID'],
'character_name': request.META['HTTP_EVE_CHARNAME']}
return render(request, 'public/characternotexisting.html', context=context)
location['station_name'] = "No Station"
ship['ship_type_name'] = c.Universe.get_universe_types_type_id(type_id=ship['ship_type_id']).result()['type_name']
fat = Fat()
fat.system = location['solar_system_name']
fat.station = location['station_name']
fat.shiptype = ship['ship_type_name']
fat.fatlink = fatlink
fat.character = character
fat.user = character.user
try:
fat.full_clean()
fat.save()
messages.success(request, 'Fleet participation registered.')
except ValidationError as e:
err_messages = []
for errorname, message in e.message_dict.items():
err_messages.append(message[0].decode())
messages.error(request, ' '.join(err_messages))
else:
context = {'trusted': True, 'expired': True}
except ObjectDoesNotExist:
context = {'trusted': True}
else:
context = {'trusted': False, 'fatname': fatname}
return render(request, 'public/clickfatlinkview.html', context=context)
context = {'character_id': token.character_id,
'character_name': token.character_name}
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
else:
messages.error(request, 'FAT link has expired.')
except (ObjectDoesNotExist, KeyError):
messages.error(request, 'Invalid FAT link.')
return redirect('auth_fatlink_view')
@login_required
@@ -250,7 +267,7 @@ def create_fatlink_view(request):
for errorname, message in e.message_dict.items():
messages.append(message[0].decode())
context = {'form': form, 'errormessages': messages}
return render(request, 'registered/fatlinkformatter.html', context=context)
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context)
else:
form = FatlinkForm()
context = {'form': form, 'badrequest': True}
@@ -263,7 +280,7 @@ def create_fatlink_view(request):
context = {'form': form}
return render(request, 'registered/fatlinkformatter.html', context=context)
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context)
@login_required
@@ -271,24 +288,26 @@ def create_fatlink_view(request):
def modify_fatlink_view(request, hash=""):
logger.debug("modify_fatlink_view called by user %s" % request.user)
if not hash:
return redirect('/fat/')
return redirect('auth_fatlink_view')
fatlink = Fatlink.objects.filter(hash=hash)[0]
if request.GET.get('removechar'):
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))
Fat.objects.filter(fatlink=fatlink).filter(character=character).delete()
if request.GET.get('deletefat'):
if request.GET.get('deletefat', None):
logger.debug("Removing fleetactivitytracking %s" % fatlink.name)
fatlink.delete()
return redirect('/fat/')
return redirect('auth_fatlink_view')
registered_fats = Fat.objects.filter(fatlink=fatlink).order_by('character')
registered_fats = Fat.objects.filter(fatlink=fatlink).order_by('character__character_name')
context = {'fatlink': fatlink, 'registered_fats': registered_fats}
fat_page = get_page(registered_fats, request.GET.get('page', 1))
return render(request, 'registered/fatlinkmodify.html', context=context)
context = {'fatlink': fatlink, 'registered_fats': fat_page}
return render(request, 'fleetactivitytracking/fatlinkmodify.html', context=context)

View File

@@ -1,13 +1,15 @@
from __future__ import unicode_literals
from django.contrib import admin
from groupmanagement.models import GroupDescription
from groupmanagement.models import GroupRequest
from groupmanagement.models import HiddenGroup
from groupmanagement.models import OpenGroup
from groupmanagement.models import AuthGroup
admin.site.register(GroupDescription)
class AuthGroupAdmin(admin.ModelAdmin):
"""
Admin model for AuthGroup
"""
filter_horizontal = ('group_leaders',)
admin.site.register(GroupRequest)
admin.site.register(HiddenGroup)
admin.site.register(OpenGroup)
admin.site.register(AuthGroup, AuthGroupAdmin)

View File

@@ -0,0 +1,5 @@
from groupmanagement.managers import GroupManager
def can_manage_groups(request):
return {'can_manage_groups': GroupManager.can_manage_groups(request.user)}

View File

@@ -0,0 +1,56 @@
from django.contrib.auth.models import Group
from django.conf import settings
from authentication.managers import UserState
class GroupManager:
def __init__(self):
pass
@staticmethod
def get_joinable_groups():
return Group.objects.exclude(authgroup__internal=True)
@staticmethod
def get_group_leaders_groups(user):
return Group.objects.filter(authgroup__group_leaders__in=[user])
@staticmethod
def joinable_group(group):
"""
Check if a group is a user joinable group, i.e.
not an internal group for Corp, Alliance, Members etc
:param group: django.contrib.auth.models.Group object
:return: bool True if its joinable, False otherwise
"""
return not group.authgroup.internal
@staticmethod
def has_management_permission(user):
return user.has_perm('auth.group_management')
@classmethod
def can_manage_groups(cls, user):
"""
For use with user_passes_test decorator.
Check if the user can manage groups. Either has the
auth.group_management permission or is a leader of at least one group
and is also a Member.
:param user: django.contrib.auth.models.User for the request
:return: bool True if user can manage groups, False otherwise
"""
if user.is_authenticated:
return cls.has_management_permission(user) or (user.leads_groups.all() and UserState.member_state(user))
return False
@classmethod
def can_manage_group(cls, user, group):
"""
Check user has permission to manage the given group
:param user: User object to test permission of
:param group: Group object the user is attempting to manage
:return: True if the user can manage the group
"""
if user.is_authenticated:
return cls.has_management_permission(user) or (
user.leads_groups.filter(group=group).exists() and UserState.member_state(user))
return False

View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-04 10:25
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
from django.core.exceptions import ObjectDoesNotExist
import django.db.models.deletion
def internal_group(group):
return (
"Corp_" in group.name or
"Alliance_" in group.name or
settings.DEFAULT_AUTH_GROUP in group.name or
settings.DEFAULT_BLUE_GROUP in group.name
)
def combine_group_models(apps, schema_editor):
Group = apps.get_model("auth", "Group")
AuthGroup = apps.get_model("groupmanagement", "AuthGroup")
for group in Group.objects.all():
authgroup = AuthGroup(group=group)
if not hasattr(group, 'hiddengroup'):
authgroup.hidden = False
if hasattr(group, 'opengroup'):
authgroup.open = True
if hasattr(group, 'groupdescription'):
authgroup.description = group.groupdescription.description
authgroup.internal = internal_group(group)
authgroup.save()
def reverse_group_models(apps, schema_editor):
Group = apps.get_model("auth", "Group")
GroupDescription = apps.get_model("groupmanagement", "GroupDescription")
OpenGroup = apps.get_model("groupmanagement", "OpenGroup")
HiddenGroup = apps.get_model("groupmanagement", "HiddenGroup")
for group in Group.objects.all():
if not hasattr(group, 'authgroup') or group.authgroup is None:
continue
if group.authgroup.open:
OpenGroup.objects.get_or_create(group=group)
else:
try:
OpenGroup.objects.get(group=group).delete()
except ObjectDoesNotExist:
pass
if group.authgroup.hidden:
HiddenGroup.objects.get_or_create(group=group)
else:
try:
HiddenGroup.objects.get(group=group).delete()
except ObjectDoesNotExist:
pass
if len(group.authgroup.description):
GroupDescription.objects.update_or_create(group=group,
defaults={'description': group.authgroup.description})
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('groupmanagement', '0003_default_groups'),
]
operations = [
migrations.CreateModel(
name='AuthGroup',
fields=[
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True,
serialize=False, to='auth.Group')),
('internal', models.BooleanField(default=True, help_text='Internal group, users cannot see, join or request to join this group.<br>Used for groups such as Members, Corp_*, Alliance_* etc.<br><b>Overrides Hidden and Open options when selected.</b>')),
('hidden', models.BooleanField(default=True, help_text='Group is hidden from users but can still join with the correct link.')),
('open', models.BooleanField(default=False, help_text='Group is open and users will be automatically added upon request. <br>If the group is not open users will need their request manually approved.')),
('description', models.CharField(max_length=512, blank=True, help_text='Description of the group shown to users.', )),
('group_leaders', models.ManyToManyField(related_name='leads_groups', to=settings.AUTH_USER_MODEL, blank=True, help_text='Group leaders can process group requests for this group specifically. Use the auth.group_management permission to allow a user to manage all groups.',)),
],
),
migrations.RunPython(combine_group_models, reverse_group_models),
migrations.RemoveField(
model_name='groupdescription',
name='group',
),
migrations.RemoveField(
model_name='hiddengroup',
name='group',
),
migrations.RemoveField(
model_name='opengroup',
name='group',
),
migrations.DeleteModel(
name='GroupDescription',
),
migrations.DeleteModel(
name='HiddenGroup',
),
migrations.DeleteModel(
name='OpenGroup',
),
]

View File

@@ -3,19 +3,12 @@ from django.utils.encoding import python_2_unicode_compatible
from django.db import models
from django.contrib.auth.models import User
from django.contrib.auth.models import Group
from django.db.models.signals import post_save
from django.dispatch import receiver
from eveonline.models import EveCharacter
@python_2_unicode_compatible
class GroupDescription(models.Model):
description = models.CharField(max_length=512)
group = models.OneToOneField(Group)
def __str__(self):
return self.group.name + " - Description"
@python_2_unicode_compatible
class GroupRequest(models.Model):
status = models.CharField(max_length=254)
@@ -29,16 +22,57 @@ class GroupRequest(models.Model):
@python_2_unicode_compatible
class HiddenGroup(models.Model):
group = models.OneToOneField(Group)
class AuthGroup(models.Model):
"""
Extends Django Group model with a one-to-one field
Attributes are accessible via group as if they were in the model
e.g. group.authgroup.internal
Logic:
Internal - not requestable by users, at all. Covers Corp_, Alliance_, Members etc groups.
Groups are internal by default
Not Internal and:
Hidden - users cannot view, can request if they have the direct link.
Not Hidden - Users can view and request the group
Open - Users are automatically accepted into the group
Not Open - Users requests must be approved before they are added to the group
"""
group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True)
internal = models.BooleanField(default=True,
help_text="Internal group, users cannot see, join or request to join this group.<br>"
"Used for groups such as Members, Corp_*, Alliance_* etc.<br>"
"<b>Overrides Hidden and Open options when selected.</b>")
hidden = models.BooleanField(default=True,
help_text="Group is hidden from users but can still join with the correct link.")
open = models.BooleanField(default=False,
help_text="Group is open and users will be automatically added upon request. <br>"
"If the group is not open users will need their request manually approved.")
# Group leaders have management access to this group
group_leaders = models.ManyToManyField(User, related_name='leads_groups', blank=True,
help_text="Group leaders can process group requests for this group "
"specifically. Use the auth.group_management permission to allow "
"a user to manage all groups.")
description = models.CharField(max_length=512, blank=True, help_text="Description of the group shown to users.")
def __str__(self):
return self.group.name + " - Hidden"
return self.group.name
@python_2_unicode_compatible
class OpenGroup(models.Model):
group = models.OneToOneField(Group)
@receiver(post_save, sender=Group)
def create_auth_group(sender, instance, created, **kwargs):
"""
Creates the AuthGroup model when a group is created
"""
if created:
AuthGroup.objects.create(group=instance)
def __str__(self):
return self.group.name + " - Open"
@receiver(post_save, sender=Group)
def save_auth_group(sender, instance, **kwargs):
"""
Ensures AuthGroup model is saved automatically
"""
instance.authgroup.save()

View File

@@ -1,18 +1,18 @@
from __future__ import unicode_literals
from django.shortcuts import render, redirect
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group
from django.contrib import messages
from notifications import notify
from groupmanagement.models import GroupDescription
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.http import Http404
from groupmanagement.managers import GroupManager
from groupmanagement.models import GroupRequest
from groupmanagement.models import HiddenGroup
from groupmanagement.models import OpenGroup
from authentication.models import AuthServicesInfo
from eveonline.managers import EveManager
from django.utils.translation import ugettext_lazy as _
import logging
@@ -20,17 +20,25 @@ logger = logging.getLogger(__name__)
@login_required
@permission_required('auth.group_management')
@user_passes_test(GroupManager.can_manage_groups)
def group_management(request):
logger.debug("group_management called by user %s" % request.user)
acceptrequests = []
leaverequests = []
for grouprequest in GroupRequest.objects.all():
if GroupManager.has_management_permission(request.user):
# Full access
group_requests = GroupRequest.objects.all()
else:
# Group specific leader
group_requests = GroupRequest.objects.filter(group__authgroup__group_leaders__in=[request.user])
for grouprequest in group_requests:
if grouprequest.leave_request:
leaverequests.append(grouprequest)
else:
acceptrequests.append(grouprequest)
logger.debug("Providing user %s with %s acceptrequests and %s leaverequests." % (
request.user, len(acceptrequests), len(leaverequests)))
@@ -40,12 +48,98 @@ def group_management(request):
@login_required
@permission_required('auth.group_management')
@user_passes_test(GroupManager.can_manage_groups)
def group_membership(request):
logger.debug("group_membership called by user %s" % request.user)
# Get all open and closed groups
if GroupManager.has_management_permission(request.user):
# Full access
groups = GroupManager.get_joinable_groups()
else:
# Group leader specific
groups = GroupManager.get_group_leaders_groups(request.user)
groups = groups.exclude(authgroup__internal=True).annotate(num_members=Count('user')).order_by('name')
render_items = {'groups': groups}
return render(request, 'registered/groupmembership.html', context=render_items)
@login_required
@user_passes_test(GroupManager.can_manage_groups)
def group_membership_list(request, group_id):
logger.debug("group_membership_list called by user %s for group id %s" % (request.user, group_id))
try:
group = Group.objects.get(id=group_id)
# Check its a joinable group i.e. not corp or internal
# And the user has permission to manage it
if not GroupManager.joinable_group(group) or not GroupManager.can_manage_group(request.user, group):
logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
(request.user, group_id))
raise PermissionDenied
except ObjectDoesNotExist:
raise Http404("Group does not exist")
members = list()
for member in group.user_set.all().order_by('username'):
authinfo = AuthServicesInfo.objects.get_or_create(user=member)[0]
members.append({
'user': member,
'main_char': EveManager.get_character_by_id(authinfo.main_char_id)
})
render_items = {'group': group, 'members': members}
return render(request, 'registered/groupmembers.html', context=render_items)
@login_required
@user_passes_test(GroupManager.can_manage_groups)
def group_membership_remove(request, group_id, user_id):
logger.debug("group_membership_remove called by user %s for group id %s on user id %s" %
(request.user, group_id, user_id))
try:
group = Group.objects.get(id=group_id)
# Check its a joinable group i.e. not corp or internal
# And the user has permission to manage it
if not GroupManager.joinable_group(group) or not GroupManager.can_manage_group(request.user, group):
logger.warning("User %s attempted to remove a user from group %s but permission was denied" % (request.user,
group_id))
raise PermissionDenied
try:
user = group.user_set.get(id=user_id)
# Remove group from user
user.groups.remove(group)
logger.info("User %s removed user %s from group %s" % (request.user, user, group))
messages.success(request, "Removed user %s from group %s" % (user, group))
except ObjectDoesNotExist:
messages.warning(request, "User does not exist in that group")
except ObjectDoesNotExist:
messages.warning(request, "Group does not exist")
return redirect('auth_group_membership_list', group_id)
@login_required
@user_passes_test(GroupManager.can_manage_groups)
def group_accept_request(request, group_request_id):
logger.debug("group_accept_request called by user %s for grouprequest id %s" % (request.user, group_request_id))
try:
group_request = GroupRequest.objects.get(id=group_request_id)
group, created = Group.objects.get_or_create(name=group_request.group.name)
if not GroupManager.joinable_group(group_request.group) or \
not GroupManager.can_manage_group(request.user, group_request.group):
raise PermissionDenied
group_request.user.groups.add(group)
group_request.user.save()
group_request.delete()
@@ -55,6 +149,11 @@ def group_accept_request(request, group_request_id):
message="Your application to %s has been accepted." % group_request.group)
messages.success(request,
'Accepted application from %s to %s.' % (group_request.main_char, group_request.group))
except PermissionDenied as p:
logger.warning("User %s attempted to accept group join request %s but permission was denied" %
(request.user, group_request_id))
raise p
except:
messages.error(request, 'An unhandled error occurred while processing the application from %s to %s.' % (
group_request.main_char, group_request.group))
@@ -66,12 +165,15 @@ def group_accept_request(request, group_request_id):
@login_required
@permission_required('auth.group_management')
@user_passes_test(GroupManager.can_manage_groups)
def group_reject_request(request, group_request_id):
logger.debug("group_reject_request called by user %s for group request id %s" % (request.user, group_request_id))
try:
group_request = GroupRequest.objects.get(id=group_request_id)
if not GroupManager.can_manage_group(request.user, group_request.group):
raise PermissionDenied
if group_request:
logger.info("User %s rejected group request from user %s to group %s" % (
request.user, group_request.user, group_request.group.name))
@@ -80,6 +182,11 @@ def group_reject_request(request, group_request_id):
message="Your application to %s has been rejected." % group_request.group)
messages.success(request,
'Rejected application from %s to %s.' % (group_request.main_char, group_request.group))
except PermissionDenied as p:
logger.warning("User %s attempted to reject group join request %s but permission was denied" %
(request.user, group_request_id))
raise p
except:
messages.error(request, 'An unhandled error occured while processing the application from %s to %s.' % (
group_request.main_char, group_request.group))
@@ -91,12 +198,16 @@ def group_reject_request(request, group_request_id):
@login_required
@permission_required('auth.group_management')
@user_passes_test(GroupManager.can_manage_groups)
def group_leave_accept_request(request, group_request_id):
logger.debug(
"group_leave_accept_request called by user %s for group request id %s" % (request.user, group_request_id))
try:
group_request = GroupRequest.objects.get(id=group_request_id)
if not GroupManager.can_manage_group(request.user, group_request.group):
raise PermissionDenied
group, created = Group.objects.get_or_create(name=group_request.group.name)
group_request.user.groups.remove(group)
group_request.user.save()
@@ -107,6 +218,10 @@ def group_leave_accept_request(request, group_request_id):
message="Your request to leave %s has been accepted." % group_request.group)
messages.success(request,
'Accepted application from %s to leave %s.' % (group_request.main_char, group_request.group))
except PermissionDenied as p:
logger.warning("User %s attempted to accept group leave request %s but permission was denied" %
(request.user, group_request_id))
raise p
except:
messages.error(request, 'An unhandled error occured while processing the application from %s to leave %s.' % (
group_request.main_char, group_request.group))
@@ -118,13 +233,16 @@ def group_leave_accept_request(request, group_request_id):
@login_required
@permission_required('auth.group_management')
@user_passes_test(GroupManager.can_manage_groups)
def group_leave_reject_request(request, group_request_id):
logger.debug(
"group_leave_reject_request called by user %s for group request id %s" % (request.user, group_request_id))
try:
group_request = GroupRequest.objects.get(id=group_request_id)
if not GroupManager.can_manage_group(request.user, group_request.group):
raise PermissionDenied
if group_request:
group_request.delete()
logger.info("User %s rejected group leave request from user %s for group %s" % (
@@ -133,6 +251,10 @@ def group_leave_reject_request(request, group_request_id):
message="Your request to leave %s has been rejected." % group_request.group)
messages.success(request, 'Rejected application from %s to leave %s.' % (
group_request.main_char, group_request.group))
except PermissionDenied as p:
logger.warning("User %s attempted to reject group leave request %s but permission was denied" %
(request.user, group_request_id))
raise p
except:
messages.error(request, 'An unhandled error occured while processing the application from %s to leave %s.' % (
group_request.main_char, group_request.group))
@@ -146,37 +268,16 @@ def group_leave_reject_request(request, group_request_id):
@login_required
def groups_view(request):
logger.debug("groups_view called by user %s" % request.user)
paired_list = []
groups = []
for group in Group.objects.all():
# Check if group is a corp
if "Corp_" in group.name:
pass
elif "Alliance_" in group.name:
pass
elif settings.DEFAULT_AUTH_GROUP in group.name:
pass
elif settings.DEFAULT_BLUE_GROUP in group.name:
pass
elif HiddenGroup.objects.filter(group=group).exists():
pass
else:
# Get the descriptionn
group_desc = GroupDescription.objects.filter(group=group)
for group in GroupManager.get_joinable_groups():
# Exclude hidden
if not group.authgroup.hidden:
group_request = GroupRequest.objects.filter(user=request.user).filter(group=group)
if group_desc:
if group_request:
paired_list.append((group, group_desc[0], group_request[0]))
else:
paired_list.append((group, group_desc[0], ""))
else:
if group_request:
paired_list.append((group, "", group_request[0]))
else:
paired_list.append((group, "", ""))
groups.append({'group': group, 'request': group_request[0] if group_request else None})
render_items = {'pairs': paired_list}
render_items = {'groups': groups}
return render(request, 'registered/groups.html', context=render_items)
@@ -184,7 +285,12 @@ def groups_view(request):
def group_request_add(request, group_id):
logger.debug("group_request_add called by user %s for group id %s" % (request.user, group_id))
group = Group.objects.get(id=group_id)
if OpenGroup.objects.filter(group=group).exists():
if not GroupManager.joinable_group(group):
logger.warning("User %s attempted to join group id %s but it is not a joinable group" %
(request.user, group_id))
messages.warning(request, "You cannot join that group")
return redirect('auth_groups')
if group.authgroup.open:
logger.info("%s joining %s as is an open group" % (request.user, group))
request.user.groups.add(group)
return redirect("auth_groups")
@@ -205,7 +311,17 @@ def group_request_add(request, group_id):
def group_request_leave(request, group_id):
logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id))
group = Group.objects.get(id=group_id)
if OpenGroup.objects.filter(group=group).exists():
if not GroupManager.joinable_group(group):
logger.warning("User %s attempted to leave group id %s but it is not a joinable group" %
(request.user, group_id))
messages.warning(request, "You cannot leave that group")
return redirect('auth_groups')
if group not in request.user.groups.all():
logger.debug("User %s attempted to leave group id %s but they are not a member" %
(request.user, group_id))
messages.warning(request, "You are not a member of that group")
return redirect('auth_groups')
if group.authgroup.open:
logger.info("%s leaving %s as is an open group" % (request.user, group))
request.user.groups.remove(group)
return redirect("auth_groups")

View File

@@ -15,6 +15,7 @@ redis
django>=1.10,<2.0
django-bootstrap-form
django-navhelper
django-bootstrap-pagination
# awating release for fix to celery/django-celery#447
# django-celery
@@ -23,4 +24,4 @@ git+https://github.com/celery/django-celery
git+git://github.com/nikdoof/python-ts3.git
git+https://github.com/pyghassen/openfire-restapi
git+https://github.com/adarnof/adarnauth-eve-sso
git+https://github.com/adarnof/adarnauth-esi

View File

@@ -23,9 +23,5 @@ def auth_settings(request):
'MEMBER_API_MASK': settings.MEMBER_API_MASK,
'MEMBER_API_ACCOUNT': settings.MEMBER_API_ACCOUNT,
'JABBER_URL': settings.JABBER_URL,
'ALLIANCE_NAME': settings.ALLIANCE_NAME,
'ALLIANCE_ID': settings.ALLIANCE_ID,
'CORP_NAME': settings.CORP_NAME,
'CORP_ID': settings.CORP_ID,
'IS_CORP': settings.IS_CORP,
}
'SITE_NAME': settings.SITE_NAME,
}

View File

@@ -319,7 +319,6 @@ class EveApiManager:
def validate_api(api_id, api_key, user):
try:
info = EveApiManager.get_api_info(api_id, api_key).result
chars = EveApiManager.get_characters_from_api(api_id, api_key).result
except evelink.api.APIError as e:
if int(e.code) == 222:
raise EveApiManager.ApiInvalidError(api_id)
@@ -329,6 +328,7 @@ class EveApiManager:
auth, c = AuthServicesInfo.objects.get_or_create(user=user)
states = [auth.state]
from authentication.tasks import determine_membership_by_character # circular import issue
chars = info['characters']
for char in chars:
evechar = EveCharacter()
evechar.character_name = chars[char]['name']

View File

@@ -1,25 +0,0 @@
from __future__ import unicode_literals
import requests
import json
class EveWhoManager:
def __init__(self):
pass
@staticmethod
def get_corporation_members(corpid):
url = "http://evewho.com/api.php?type=corplist&id=%s" % corpid
jsondata = requests.get(url).content
data = json.loads(jsondata.decode())
members = {}
page_count = 0
while len(data["characters"]):
for row in data["characters"]:
members[int(row["character_id"])] = {"name": row["name"], "id": int(row["character_id"])}
page_count += 1
jsondata = requests.get(url + "&page=%i" % page_count).content
data = json.loads(jsondata.decode())
return members

View File

@@ -489,7 +489,6 @@ def update_all_discord_nicknames():
@task(bind=True)
@only_one(key="Discourse", timeout=60*5)
def update_discourse_groups(self, pk):
user = User.objects.get(pk=pk)
logger.debug("Updating discourse groups for user %s" % user)

View File

@@ -12,7 +12,7 @@
<meta name="description" content="">
<meta name="author" content="">
<title>{% block title %}Empty title{% endblock title %}</title>
<title>{% block title %}Alliance Auth{% endblock title %}</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
@@ -46,11 +46,7 @@
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="container-fluid">
<a class="navbar-brand">
{% if IS_CORP %}
{{ CORP_NAME }}
{% else %}
{{ ALLIANCE_NAME }}
{% endif %}
{{ SITE_NAME }}
</a>
<!-- /.navbar-header -->
@@ -110,18 +106,6 @@
</a>
</li>
<li>
<a class="{% navactive request 'auth_api_key_management auth_add_api_key' %}" href="{% url 'auth_api_key_management' %}">
<i class="fa fa-key fa-fw grayiconecolor"></i>{% trans " Api Keys" %}
</a>
</li>
<li>
<a class="{% navactive request 'auth_characters' %}" href="{% url 'auth_characters' %}">
<i class="fa fa-users fa-fw grayiconecolor"></i>{% trans " Characters" %}
</a>
</li>
{% if STATE == MEMBER_STATE or user.is_superuser %}
<li>
<a class="{% navactive request 'auth_groups' %}" href="{% url 'auth_groups' %}">
@@ -157,17 +141,17 @@
</li>
{% endif %}
{% if perms.auth.corp_apis or perms.auth.alliance_apis %}
{% if perms.corputils.view_corp_corpstats or perms.corputils.view_alliance_corpstats or perms.corputils.view_blue_corpstats %}
<li>
<a class="{% navactive request 'auth_corputils auth_corputils_search auth_corputils_corp_view auth_corputils_month' %}" href="{% url 'auth_corputils' %}">
<a class="{% navactive request 'corputils:view corputils:search' %}" href="{% url 'corputils:view' %}">
<i class="fa fa-share-alt fa-fw grayiconecolor"></i>{% trans " Corporation Stats" %}
</a>
</li>
{% endif %}
{% if perms.auth.group_management %}
{% if can_manage_groups %}
<li>
<a class="{% navactive request 'auth_group_management' %}" href="{% url 'auth_group_management' %}">
<a class="{% navactive request 'auth_group_management auth_group_membership auth_group_membership_list' %}" href="{% url 'auth_group_management' %}">
<i class="fa fa-lock fa-sitemap fa-fw grayiconecolor"></i>{% trans " Group Management" %}
</a>
</li>
@@ -199,7 +183,7 @@
{% endif %}
<li>
<a class="{% navactive request 'auth_fatlink_view auth_fatlink_view_statistics auth_fatlink_view_statistics_month auth_fatlink_view_personal_statistics auth_fatlink_view_personal_statistics_year auth_fatlink_personal_statistics_month auth_fatlink_view_user_statistics_month auth_create_fatlink_view auth_modify_fatlink_view auth_click_fatlink_view' %}" href="{% url 'auth_fatlink_view' %}">
<a class="{% navactive request 'auth_fatlink_view auth_fatlink_view_statistics auth_fatlink_view_statistics_month auth_fatlink_view_personal_statistics auth_fatlink_view_personal_statistics_year auth_fatlink_view_personal_statistics_month auth_fatlink_view_user_statistics_month auth_create_fatlink_view auth_modify_fatlink_view auth_click_fatlink_view' %}" href="{% url 'auth_fatlink_view' %}">
<i class="fa fa-users fa-lightbulb-o fa-fw grayiconecolor"></i> Fleet Activity Tracking
</a>
</li>

View File

@@ -1,90 +0,0 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Fleet participation</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'css/sb-admin-2.css' %}" rel="stylesheet">
{% block extra_css %}{% endblock extra_css %}
<style>
.grayiconecolor {
color: #505050;
}
</style>
</head>
<body onload=CCPEVE.requestTrust('{{ DOMAIN }}')>
<div id="wrapper">
<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="navbar-header ">
<a class="navbar-brand " href="/dashboard/">
<div class="fa fa-cog fa-spin"></div>
{% if IS_CORP %}
{{ CORP_NAME }}
{% else %}
{{ ALLIANCE_NAME }}
{% endif %}
</a>
</div>
<!-- /.navbar-header -->
<ul class="nav navbar-top-links navbar-right">
{% if user.is_authenticated %}
<li><a href="{% url 'auth_logout_user' %}">Logout</a></li>
{% else %}
<li><a href="{% url 'auth_login_user' %}">Login</a></li>
{% endif %}
</ul>
<!-- /.navbar-static-side -->
</nav>
</div>
<div class="col-lg-12">
<h1 class="page-header text-center">Character not found!</h1>
<div class="col-lg-12 container" id="example">
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">{{ character_name }}</div>
<div class="panel-body">
<div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Character/{{ character_id }}_128.jpg">
</div>
<div class="col-lg-10 col-sm-2">
<div class="alert alert-danger" role="alert">Character not registered!</div>
This character is not part of any registered API-key. You must go to <a href=" {% url 'auth_api_key_management' %}">API key management</a> and add an API with the character on before being able to click fleet attendance links.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/jquery.datetimepicker.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>

View File

@@ -1,96 +0,0 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Fleet participation</title>
<!-- Bootstrap Core CSS -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom Fonts -->
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'css/sb-admin-2.css' %}" rel="stylesheet">
{% block extra_css %}{% endblock extra_css %}
<style>
.grayiconecolor {
color: #505050;
}
</style>
</head>
<body onload=CCPEVE.requestTrust('{{ DOMAIN }}')>
<div id="wrapper">
<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="navbar-header ">
<a class="navbar-brand " href="/dashboard/">
<div class="fa fa-cog fa-spin"></div>
{% if IS_CORP %}
{{ CORP_NAME }}
{% else %}
{{ ALLIANCE_NAME }}
{% endif %}
</a>
</div>
<!-- /.navbar-header -->
<ul class="nav navbar-top-links navbar-right">
{% if user.is_authenticated %}
<li><a href="{% url 'auth_logout_user' %}">Logout</a></li>
{% else %}
<li><a href="{% url 'auth_login_user' %}">Login</a></li>
{% endif %}
</ul>
<!-- /.navbar-static-side -->
</nav>
</div>
<div class="col-lg-12">
{% if registered %}<h1 class="page-header text-center">Fleet registered!</h1> {% elif expired%}<h1 class="page-header text-center">This link has expired.</h1> {% elif errormessages%}<h1 class="page-header text-center">Something horrible happened. Shoot your FC!</h1>{% else %}<h1 class="page-header text-center">Invalid link.</h1>{% endif %}
<div class="col-lg-12 container" id="example">
{% for message in errormessages %}
<div class="alert alert-danger" role="alert">{{ message }}</div>
{% endfor %}
{% if trusted %}
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Fleet stats</div>
<div class="panel-body">
<div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/{% if IS_CORP %}Corporation/{{ CORPORATION_ID }}{% else %}Alliance/{{ ALLIANCE_ID }}{% endif %}_128.png">
</div>
<div class="col-lg-7 col-sm-2">
</div>
</div>
</div>
</div>
</div>
{% else %}
<div class="alert alert-danger" role="alert">This page requires trust to operate.</div>
{% endif %}
</div>
</div>
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/jquery.datetimepicker.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>

View File

@@ -43,16 +43,8 @@
<body>
<div id="logo">
<p style="text-align:center">
<!--Uncomment the below line to use a custom logo-->
<!--<img src="{% static 'img/index_images/logo.png' %}" border="0">-->
<!--Comment the below block to use a custom logo-->
{% if IS_CORP %}
<img src="https://image.eveonline.com/Corporation/{{ CORP_ID }}_256.png" border="0">
{% else %}
<img src="https://image.eveonline.com/Alliance/{{ ALLIANCE_ID }}_128.png" border="0">
{% endif %}
<!--Stop commenting here-->
</p>
<img src="{% static 'img/index_images/logo.png' %}" border="0">
</p>
</div>
<div id="content">
<p style="text-align:center">

View File

@@ -75,16 +75,14 @@
<a href="{% url 'auth_register_user' %}" class="btn btn-success btn-block btn-lg">{% trans "Register" %}</a>
</div>
</div>
{% if EVE_SSO_CALLBACK_URL %}
<br>
<div class="col-md-12">
<p style="text-align:center">
<a href="{% url 'auth_sso_login' %}">
<img src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
</a>
</p>
</div>
{% endif %}
<br>
<div class="col-md-12">
<p style="text-align:center">
<a href="{% url 'auth_sso_login' %}">
<img src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
</a>
</p>
</div>
</div>
</div>

View File

@@ -1,64 +0,0 @@
{% extends "public/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "API Key Management" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %}
{% block content %}
<div class="col-lg-6 col-lg-offset-3">
<div class="row">
<h1 class="page-header text-center">{% trans "API Key Management" %}
<div class="text-right">
<a href="{% url 'auth_add_api_key' %}" class="btn btn-success">{% trans "Add Key" %}</a>
</div>
</h1>
{% if apikeypairs %}
<table class="table">
<tr>
<th class="text-center">{% trans "API ID" %}</th>
{% if api_sso_validation %}
<th class="text-center">{% trans "SSO Verified" %}</th>
{% endif %}
<th class="text-center">{% trans "Action" %}</th>
</tr>
{% for pair in apikeypairs %}
<tr>
<td class="text-center">{{ pair.api_id }}</td>
{% if api_sso_validation %}
<th class="text-center" style="font-size: 2em;">
{% if pair.sso_verified %}
<span class="glyphicon glyphicon-ok text-success" title="API key verified"></span>
{% else %}
<span class="glyphicon glyphicon-remove text-danger" title="API key not verified">
</span>
{% endif %}
</th>
{% endif %}
<td class="text-center">
<a href="{% url 'auth_user_refresh_api' pair.api_id %}" class="btn btn-success">
<span class="glyphicon glyphicon-refresh"></span>
</a>
<a href="{% url 'auth_api_key_removal' pair.api_id %}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% if api_sso_validation and not pair.sso_verified %}
<a href="{% url 'auth_api_sso' pair.api_id %}" class="btn btn-info"
title="EVE SSO verify this key">
<i class="fa fa-shield" aria-hidden="true"></i>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="alert alert-danger" role="alert">{% trans "No api keys found" %}</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -1,229 +0,0 @@
{% extends "public/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Corporation Member Tracking" %}{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Corporation Member Data" %}</h1>
<div class="col-lg-12 container" id="example">
{% if corp %}
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">{% trans "Corporation" %}</div>
<div class="panel-body">
<div class="col-lg-5 col-sm-2">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Corporation/{{ corp.corporation_id }}_128.png">
</div>
<div class="col-lg-7 col-sm-2">
<h4 class="">{{ corp.corporation_name }}</h4>
<p>{% trans "Ticker:" %} {{ corp.corporation_ticker }}</p>
<p>{% trans "Member count:" %} {{ corp.member_count }}</p>
<p>{% trans "Player count:" %} {{characters_with_api|length}}</p>
<p>{% trans "Unregistered characters:" %} {{characters_without_api|length|add:n_unacounted}}</p>
</div>
<div class="col-lg-12 col-sm-5">
<b>{% trans "API Index:" %}</b>
<div class="progress">
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{{ n_registered }}" aria-valuemin="0" aria-valuemax="{{ corp.member_count }}" style="width: {% widthratio characters_with_api|length corp.member_count 100 %}%;">
{{n_registered}}/{{ corp.member_count }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav navbar-wide">
{% if membercorplist and perms.auth.alliance_apis %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Alliance corporations" %} <span class="caret"></span></a>
<ul class="dropdown-menu scrollable">
{% for membercorpid, membercorpname in membercorplist %}
<li>
<a href="{% url 'auth_corputils_corp_view' membercorpid %}">{{ membercorpname }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
<li style="float: right">
<p class="navbar-form">
Statistics for:
<a href="{% url 'auth_corputils_month' corp.corporation_id previous_month|date:"Y" previous_month|date:"m" %}">
<i class="fa fa-arrow-circle-left fa-fw grayiconecolor"></i>
</a>
{{ this_month|date:"M" }}, {{ this_month|date:"Y" }}
{% if next_month %}
<a href="{% url 'auth_corputils_month' corp.corporation_id next_month|date:"Y" next_month|date:"m" %}" >
<i class="fa fa-arrow-circle-right fa-fw grayiconecolor"></i>
</a>
<a href="{% url 'auth_corputils' %}" >
<i class="fa fa-angle-double-right fa-fw grayiconecolor"></i>
</a>
{% endif %}
</p>
</li>
<li style="float: right">
<p class="navbar-btn">
<a href="https://zkillboard.com/corporation/{{ corp.corporation_id }}/" class="btn btn-default" target="_blank">{{ corp.corporation_name }} {% trans "Killboard" %}</a>
</p>
</li>
<li style="float: right">
<form class="navbar-form navbar-left" role="search" action={% url 'auth_corputils_search' %}{{ corp.corporation_id }}/ method="POST">
<div class="form-group">
{% csrf_token %}
{{ search_form.as_table }}
</div>
<button class="btn btn-default" type="submit">{% trans "Search" %}</button>
</form>
</li>
</ul>
</div>
</nav>
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#gotapi">{% blocktrans %}Registered Main Characters{% endblocktrans %} <b>({{characters_with_api|length}})</b></a></li>
<li><a data-toggle="tab" href="#noapi">{% blocktrans %}Characters without API{% endblocktrans %} <b>({{characters_without_api|length|add:n_unacounted}})</b></a></li>
</ul>
<div class="tab-content">
<div id="gotapi" class="tab-pane fade in active">
{% if characters_with_api %}
<div class="panel-body">
<div class="table-responsive">
<table class="table table-condensed table-hover table-striped">
<tr>
<th class="col-md-1"></th>
<th class="col-md-2">{% trans "Main character" %}</th>
<th class="col-md-2">{% trans "Main corporation" %}</th>
<th class="col-md-2">{% trans "Character list" %}</th>
<th class="col-md-1">{% trans "Fats" %}</th>
{% if perms.auth.fleetactivitytracking_statistics %}
<th class="col-md-3">{% trans "Killboard" %}</th>
<th class="col-md-2">{% trans "Fleet statistics" %}</th>
{% else %}
<th class="col-md-3">{% trans "Killboard" %}</th>
{% endif %}
<th class="col-md-2">{% trans "API JackKnife" %}</th>
</tr>
{% for maincharname, player in characters_with_api %}
<tr >
<td>
<img src="http://image.eveonline.com/Character/{{ player.main.character_id }}_32.jpg" class="img-circle">
</td>
<td>
<p>{{ maincharname }}</p>
</td>
<td>
{% if not corp.corporation_name == player.maincorp%}
<span class="label label-danger">
{{ player.maincorp }}
</span>
{% else %}
<span class="label label-success">
{{ player.maincorp }}
</span>
{% endif %}
</td>
<td>
{% for char in player.altlist %}
<p>{{ char.character_name }}</p>
{% endfor %}
</td>
<td>
{{ player.n_fats }}
</td>
<td>
{% for char in player.altlist %}
<p><a href="https://zkillboard.com/character/{{ char.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></p>
{% endfor %}
</td>
{% if perms.auth.fleetactivitytracking %}
<td>
<a href="{% url 'auth_fatlink_view_user_statistics_month' player.main.character_id this_month|date:"Y" this_month|date:"m" %}">
<button type="button" class="btn btn-primary">Statistics
</button>
</a>
</td>
{% endif %}
<td>
{% for apiinfo in player.apilist %}
<p>
<a href="{{ JACK_KNIFE_URL }}?usid={{ apiinfo.api_id }}&apik={{ apiinfo.api_key }}" target="_blank" class="btn btn-primary">
{% trans "API JackKnife" %}
</a>
</p>
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% else %}
<div class="alert alert-danger" role="alert">
<h3>{% blocktrans %}Seems there are no characters in {{ corp.corporation_name }} tied to a registered API!{% endblocktrans %}</h3>
</div>
{% endif %}
</div>
<div id="noapi" class="tab-pane fade">
{% if characters_without_api %}
<div class="panel-body">
<div class="table-responsive">
{% if 0 < n_unacounted %}
<div class="alert alert-danger" role="alert">
<h3>{% blocktrans %}There are atleast {{ n_unacounted }} characters not accounted for in EveWho.{% endblocktrans %}</h3>
</div>
{% endif %}
<table class="table table-condensed table-hover table-striped">
<tr>
<th class="col-md-1"></th>
<th class="col-md-2">{% trans "Character" %}</th>
<th class="col-md-5">{% trans "Killboard" %}</th>
</tr>
{% for character_name, character_id in characters_without_api %}
<tr>
<td>
<img src="http://image.eveonline.com/Character/{{ character_id }}_32.jpg" class="img-circle">
</td>
<td>
<p>{{ character_name }}</p>
</td>
<td>
<a href="https://zkillboard.com/character/{{ character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% else %}
<div class="alert alert-success" role="alert">
<h3>{% blocktrans %}Good job! Every character in {{ corp.corporation_name }} seem to be tied to an API!{% endblocktrans %}</h3>
</div>
{% endif %}
</div>
</div>
{% else %}
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<div class="row">
<div class="alert alert-danger text-center" role="alert">{% trans "No corporation model found. Contact your admin." %}</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -1,93 +0,0 @@
{% extends "public/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Corporation Member Tracking" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Member Search Results" %}</h1>
<h2 class="text-center"><a href="{% url 'auth_corputils_corp_view' corp.corporation_id %}">{{ corp.corporation_name }}</a></h2>
<div class="container-fluid">
<div class="panel panel-default">
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav navbar-wide">
<li style="float: right">
<form class="navbar-form navbar-right" role="search" action="{% url 'auth_corputils_search_corp' corp.corporation_id %}" method="POST">
<div class="form-group">
{% csrf_token %}
{{ search_form|bootstrap }}
</div>
<button class="btn btn-default" type="submit">{% trans "Search" %}</button>
</form>
</li>
</ul>
</div>
</nav>
<div class="panel-body">
<table class="table table-condensed table-hover table-striped">
<tr>
<th class="col-md-1"></th>
<th class="col-md-2">{% trans "Character" %}</th>
<th class="col-md-2">{% trans "Main character" %}</th>
{% if perms.auth.fleetactivitytracking%}
<th class="col-md-5">{% trans "Killboard" %}</th>
<th class="col-md-2">{% trans "Fleet statistics" %}</th>
{% else %}
<th class="col-md-5">{% trans "Killboard" %}</th>
{% endif %}
<th class="col-md-2">{% trans "API JackKnife" %}</th>
</tr>
{% for result in results %}
<tr >
<td>
<img src="http://image.eveonline.com/Character/{{ result.id }}_32.jpg" class="img-circle">
</td>
<td>{{ result.name }}</td>
<td>
{% if result.api_registered%}
{{ result.main.character_name }}
{% else %}
<span class="label label-danger">{% trans "No API registered!" %}</span>
{% endif %}
</td>
<td>
<p><a href="https://zkillboard.com/character/{{ result.char.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></p>
</td>
{% if perms.auth.fleetactivitytracking %}
{% if result.main %}
<td>
<a href="{% url 'auth_fatlink_view_user_statistics_month' result.main.character_id year month%}">
<button type="button" class="btn btn-primary">Statistics
</button>
</a>
</td>
{% else %}
<td></td>
{% endif %}
{% endif %}
<td>
{% if result.api_registered %}
<a href="{{ JACK_KNIFE_URL }}?usid={{ result.apiinfo.api_id }}&apik={{ result.apiinfo.api_key }}"
target="_blank">
<button type="button" class="btn btn-primary">{% trans "API JackKnife" %}
</button>
</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -1,69 +1,121 @@
{% extends "public/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Dashboard" %}{% endblock page_title %}
{% block title %}{% trans "Dashboard" %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Dashboard" %}</h1>
{% if STATE == MEMBER_STATE or user.is_superuser%}
<div class="col-lg-12 container" id="example">
<div class="col-lg-12 container">
<div class="row">
<div class="col-lg-6">
{% for character in characters %}
{% ifequal character.character_id authinfo.main_char_id %}
<div class="panel panel-default">
<div class="panel-heading">{% trans "Main character" %}</div>
<div class="panel-body">
<div class="col-lg-5 col-sm-2">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Character/{{ character.character_id }}_128.jpg">
</div>
<div class="col-lg-7 col-sm-2">
<h4 class="">{{ character.character_name }}</h4>
<p>{{ character.corporation_name }}</p>
<p>{{ character.alliance_name }}</p>
</div>
<div class="col-lg-6 text-center">
<div class="panel panel-primary">
<div class="panel-heading">{% trans "Main Character" %}</div>
<div class="panel-body">
{% if main %}
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr><td class="text-center"><img class="ra-avatar" src="https://image.eveonline.com/Character/{{ main.character_id }}_128.jpg"></td></tr>
<tr><td class="text-center">{{ main.character_name }}</td></tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr><td class="text-center"><img class="ra-avatar" src="http://image.eveonline.com/Corporation/{{ main.corporation_id }}_128.png"></td></tr>
<tr><td class="text-center">{{ main.corporation_name }}</td></tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
{% if main.alliance_id != '0' %}
<table class="table">
<tr><td class="text-center"><img class="ra-avatar" src="https://image.eveonline.com/Alliance/{{ main.alliance_id }}_128.png"></td></tr>
<tr><td class="text-center">{{ main.alliance_name }}</td><tr>
</table>
{% endif %}
</div>
{% else %}
<div class="alert alert-danger" role="alert">Missing main character model.</div>
{% endif %}
<div class="clearfix"></div>
<div class="col-sm-6">
<a href="{% url 'auth_add_api_key' %}" class="btn btn-block btn-success" label="Add API Key">Add API Key</a>
</div>
{% endifequal %}
{% endfor %}
<div class="col-sm-6">
<a href="{% url 'auth_characters' %}" class="btn btn-block btn-info" label="Change Main Character">Change Main</a>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="panel panel-default">
<div class="col-lg-6 text-center">
<div class="panel panel-success">
<div class="panel-heading">{% trans "Groups" %}</div>
<div class="panel-body">
<div style="height: 128px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<div style="height: 236px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<table class="table table-striped">
{% for group in user.groups.all %}
<tr>
<td>
<p class="">{{ group.name }}</p>
</td>
<td>{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% else %}
{% if IS_CORP %}
<div class="alert alert-danger" role="alert">{% trans "Not a part of the corporation." %}</div>
{% else %}
<div class="alert alert-danger" role="alert">{% trans "Not a part of the alliance." %}</div>
{% endif %}
{% endif %}
</div>
{% endblock content %}
<div class="clearfix"></div>
{% if apis %}
{% for api in apis %}
<div class="panel panel-{{ api.sso_verified|yesno:"success,warning,default" }}">
<div class="panel-heading" style="display:flex;">
<div style="width: 25%;" title="{% if not api.sso_verified and api_sso_validation %}{% trans "API Key requires EVE SSO verification" %}{% else %}{% trans "API Key is valid" %}{% endif %}">
{% trans "API ID" %} {{ api.id }} <span class="glyphicon glyphicon-{{ api.sso_verified|yesno:"ok-sign,warning-sign,question-sign" }}"></span>
</div>
<div class="text-right" style="width: 75%;">
<a href="{% url 'auth_user_refresh_api' api.id %}" class="btn btn-primary" title="Update"><span class="glyphicon glyphicon-refresh"></span></a>
<a href="{% url 'auth_api_key_removal' api.id %}" class="btn btn-danger" title="Delete"><span class="glyphicon glyphicon-remove"></span></a>
{% if api_sso_validation and not api.sso_verified %}
<a href="{% url 'auth_api_sso' api.id %}" class="btn btn-info"
title="EVE SSO verify this key">
<span class="fa fa-shield"></span>
</a>
{% endif %}
</div>
</div>
<table class="table">
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans "Name" %}</th>
<th class="text-center">{% trans "Corp" %}</th>
<th class="text-center">{% trans "Alliance" %}</th>
</tr>
{% for char in api.characters %}
<tr>
<td class="text-center">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Character/{{ char.character_id }}_32.jpg">
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td>
</tr>
{% endfor %}
{% if api.corp %}
<tr>
<td class="text-center">
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Corporation/{{ api.corporation_id }}_32.png">
</td>
<td class="text-center"></td>
<td class="text-center">{{ api.corporation_name }}</td>
<td class="text-center">{{ api.alliance_name }}</td>
</tr>
{% endif %}
</table>
</div>
{% endfor %}
{% else %}
<div class="alert alert-danger" role="alert">{% trans "No API keys found" %}</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,55 +0,0 @@
{% extends "public/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Fatlink view" %}{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Edit fatlink "{{ fatlink.name }}"{% endblocktrans %}
<div class="text-right">
<form>
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
{% trans "Delete fat" %}
</button>
</form>
</div>
</h1>
<h4><b>{% trans "Registered characters" %}</b></h4>
<table class="table table-responsive table-bordered">
<tr>
<th class="text-center">{% trans "User" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "System" %}</th>
<th class="text-center">{% trans "Ship" %}</th>
<th class="text-center">{% trans "Eve Time" %}</th>
<th></th>
</tr>
{% for fat in registered_fats %}
<tr>
<td class="text-center">{{ fat.user }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %}
<td class="text-center">Docked in {{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}
<td class="text-center">{{ fat.shiptype }}</td>
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
<td class="text-center">
<form>
<button type="submit" class="btn btn-warning" name="removechar" value="{{ fat.character.character_id }}"><span
class="glyphicon glyphicon-remove"></span></button>
</form>
</td>
</tr>
{% endfor %}
</table>
</div>
<script src="/static/js/dateformat.js"></script>
{% endblock content %}

View File

@@ -9,7 +9,7 @@
{% block content %}
<div class="col-lg-12">
<h3 class="page-header text-center">{% trans "Group Management" %}</h3>
{% include 'registered/groupmanagementmenu.html' %}
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#add">{% trans "Group Add Requests" %}</a></li>
<li><a data-toggle="tab" href="#leave">{% trans "Group Leave Requests" %}</a></li>

View File

@@ -0,0 +1,27 @@
{% load staticfiles %}
{% load i18n %}
{% load navactive %}
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">{% trans "Toggle navigation" %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Group Management</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="{% navactive request 'auth_group_management' %}">
<a href="{% url 'auth_group_management' %}">{% trans "Group Requests" %}</a>
</li>
<li class="{% navactive request 'auth_group_membership auth_group_membership_list' %}">
<a href="{% url 'auth_group_membership' %}">{% trans "Group Membership" %}</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@@ -0,0 +1,44 @@
{% extends "public/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Group Members" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %}
{% block content %}
<div class="col-lg-12">
{% include 'registered/groupmanagementmenu.html' %}
<h3>{{ group.name }} {% trans 'Members' %}</h3>
<div id="list" class="">
{% if group.user_set %}
<table class="table">
<tr>
<th class="text-center">{% trans "User" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Corp" %}</th>
<th class="text-center">{% trans "Alliance" %}</th>
<th class="text-center">{% trans "Action" %}</th>
</tr>
{% for member in members %}
<tr>
<td class="text-center">{{ member.user.username }}</td>
<td class="text-center">{{ member.main_char.character_name }}</td>
<td class="text-center">{{ member.main_char.corporation_name }}</td>
<td class="text-center">{{ member.main_char.alliance_name }}</td>
<td class="text-center">
<a href="{% url 'auth_group_membership_remove' group.id member.user.id %}" class="btn btn-danger"
title="{% trans "Remove from group" %}">
<i class="glyphicon glyphicon-remove"></i>
</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="alert alert-warning text-center">No group members to list.</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -0,0 +1,54 @@
{% extends "public/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Groups Membership" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %}
{% block content %}
<div class="col-lg-12">
{% include 'registered/groupmanagementmenu.html' %}
<div>
{% if groups %}
<h3>Groups</h3>
<table class="table">
<tr>
<th class="text-center">{% trans "Name" %}</th>
<th class="text-center">{% trans "Description" %}</th>
<th class="text-center">{% trans "Status" %}</th>
<th class="text-center">{% trans "Member Count" %}</th>
<th class="text-center">{% trans "Action" %}</th>
</tr>
{% for group in groups %}
<tr>
<td class="text-center">{{ group.name }}</td>
<td class="text-center">{{ group.authgroup.description }}</td>
<td class="text-center">
{% if group.authgroup.hidden %}
<span class="label label-info">{% trans "Hidden" %}</span>
{% elif group.authgroup.open %}
<span class="label label-success">{% trans "Open" %}</span>
{% else %}
<span class="label label-default">{% trans "Requestable" %}</span>
{% endif %}
</td>
<td class="text-center">
{{ group.num_members }}
</td>
<td class="text-center">
<a href="{% url 'auth_group_membership_list' group.id %}" class="btn btn-primary"
title="{% trans "View Members" %}">
<i class="glyphicon glyphicon-eye-open"></i>
</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="alert alert-warning text-center">No groups to list.</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -11,36 +11,36 @@
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Available Groups" %}</h1>
{% if STATE == MEMBER_STATE or user.is_superuser %}
{% if pairs %}
{% if groups %}
<table class="table">
<tr>
<th class="text-center">{% trans "GroupName" %}</th>
<th class="text-center">{% trans "GroupDesc" %}</th>
<th class="text-center">{% trans "Name" %}</th>
<th class="text-center">{% trans "Description" %}</th>
<th class="text-center">{% trans "Action" %}</th>
</tr>
{% for pair in pairs %}
{% for g in groups %}
<tr>
<td class="text-center">{{ pair.0.name }}</td>
<td class="text-center">{{ pair.1.description }}</td>
<td class="text-center">{{ g.group.name }}</td>
<td class="text-center">{{ g.group.authgroup.description }}</td>
<td class="text-center">
{% if pair.0 in user.groups.all %}
{% if pair.2 == "" %}
<a href="{% url 'auth_group_request_leave' pair.0.id %}" class="btn btn-danger">
{% if g.group in user.groups.all %}
{% if not g.request %}
<a href="{% url 'auth_group_request_leave' g.group.id %}" class="btn btn-danger">
{% trans "Leave" %}
</a>
{% else %}
<button type="button" class="btn btn-primary" disabled>
{{ pair.2.status }}
{{ g.request.status }}
</button>
{% endif %}
{% elif pair.2 == "" %}
<a href="{% url 'auth_group_request_add' pair.0.id %}" class="btn btn-success">
{% elif not g.request %}
<a href="{% url 'auth_group_request_add' g.group.id %}" class="btn btn-success">
{% trans "Request" %}
</a>
{% else %}
<button type="button" class="btn btn-primary" disabled>
{{ pair.2.status }}
{{ g.request.status }}
</button>
{% endif %}
</td>

View File

@@ -90,6 +90,37 @@
POS [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[M]" %}
<div class="label label-danger">
Citadel [M]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[L]" %}
<div class="label label-danger">
Citadel [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[XL]" %}
<div class="label label-danger">
Citadel [XL]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[M]" %}
<div class="label label-warning">
Engineering Complex [M]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[L]" %}
<div class="label label-warning">
Engineering Complex [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[XL]" %}
<div class="label label-warning">
Engineering Complex [XL]
</div>
{% endifequal %}
{% ifequal timer.structure "Station" %}
<div class="label label-danger">
Station
@@ -191,11 +222,41 @@
POS [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[M]" %}
<div class="label label-danger">
Citadel [M]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[L]" %}
<div class="label label-danger">
Citadel [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[XL]" %}
<div class="label label-danger">
Citadel [XL]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[M]" %}
<div class="label label-warning">
Engineering Complex [M]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[L]" %}
<div class="label label-warning">
Engineering Complex [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[XL]" %}
<div class="label label-warning">
Engineering Complex [XL]
</div>
{% endifequal %}
{% ifequal timer.structure "Station" %}
<div class="label label-danger">
Station
</div>
{% endifequal %}
{% endifequal %}
{% ifequal timer.structure "TCU" %}
<div class="label label-danger">
TCU
@@ -292,6 +353,36 @@
<div class="label label-info">
POS [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[M]" %}
<div class="label label-danger">
Citadel [M]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[L]" %}
<div class="label label-danger">
Citadel [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Citadel[XL]" %}
<div class="label label-danger">
Citadel [XL]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[M]" %}
<div class="label label-warning">
Engineering Complex [M]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[L]" %}
<div class="label label-warning">
Engineering Complex [L]
</div>
{% endifequal %}
{% ifequal timer.structure "Engineering Complex[XL]" %}
<div class="label label-warning">
Engineering Complex [XL]
</div>
{% endifequal %}
{% ifequal timer.structure "Station" %}
<div class="label label-danger">

View File

@@ -6,8 +6,12 @@ from django.utils.translation import ugettext_lazy as _
class TimerForm(forms.Form):
structure_choices = [('POCO', 'POCO'), ('I-HUB', 'I-HUB'), ('POS[S]', 'POS[S]'),
('POS[M]', 'POS[M]'), ('POS[L]', 'POS[L]'), ('Station', 'Station'), ('TCU', 'TCU'),
(_('Other'), _('Other'))]
('POS[M]', 'POS[M]'), ('POS[L]', 'POS[L]'), ('Citadel[M]', 'Citadel[M]'),
('Citadel[L]', 'Citadel[L]'), ('Citadel[XL]', 'Citadel[XL]'),
('Engineering Complex[M]', 'Engineering Complex[M]'),
('Engineering Complex[L]', 'Engineering Complex[L]'),
('Engineering Complex[XL]', 'Engineering Complex[XL]'),
('Station', 'Station'), ('TCU', 'TCU'), (_('Other'), _('Other'))]
objective_choices = [('Friendly', _('Friendly')), ('Hostile', _('Hostile')), ('Neutral', _('Neutral'))]
details = forms.CharField(max_length=254, required=True, label=_('Details'))