diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index ce81d9f7..23d753c3 100644 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -63,6 +63,7 @@ INSTALLED_APPS = [ 'notifications', 'esi', 'geelweb.django.navhelper', + 'bootstrap_pagination', ] MIDDLEWARE = [ diff --git a/alliance_auth/urls.py b/alliance_auth/urls.py index 1a1e770c..3416a130 100755 --- a/alliance_auth/urls.py +++ b/alliance_auth/urls.py @@ -10,12 +10,12 @@ 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 corputils.urls import esi.urls # Functional/Untranslated URL's @@ -27,8 +27,11 @@ urlpatterns = [ url(r'^admin/', include(admin.site.urls)), # SSO - url (r'^sso/', include(esi.urls, namespace='esi')), - 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 +147,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[0-9]+)/$', corputils.views.corp_member_view, name='auth_corputils_corp_view'), - url(r'^corputils/(?P[0-9]+)/(?P[0-9]+)/(?P[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[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'), @@ -245,12 +240,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[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[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'), diff --git a/corputils/admin.py b/corputils/admin.py index baffc488..f3bb4062 100644 --- a/corputils/admin.py +++ b/corputils/admin.py @@ -1 +1,5 @@ from __future__ import unicode_literals +from corputils.models import CorpStats +from django.contrib import admin + +admin.site.register(CorpStats) diff --git a/corputils/managers.py b/corputils/managers.py new file mode 100644 index 00000000..701f8889 --- /dev/null +++ b/corputils/managers.py @@ -0,0 +1,43 @@ +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('corpstats.corp_apis'): + queries.append(models.Q(corp__corporation_id=char.corporation_id)) + if user.has_perm('corpstats.alliance_apis'): + queries.append(models.Q(corp__alliance_id=char.alliance_id)) + if user.has_perm('corpstats.blue_apis'): + 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) diff --git a/corputils/migrations/0001_initial.py b/corputils/migrations/0001_initial.py new file mode 100644 index 00000000..b56507d5 --- /dev/null +++ b/corputils/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2016-12-13 06:23 +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', 'delete', 'view'), + '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.')), + }, + ), + ] diff --git a/corputils/migrations/0002_auto_20161213_0637.py b/corputils/migrations/0002_auto_20161213_0637.py new file mode 100644 index 00000000..3fe547b2 --- /dev/null +++ b/corputils/migrations/0002_auto_20161213_0637.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2016-12-13 06:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('corputils', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='corpstats', + options={'default_permissions': ('add', 'delete', 'view'), '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.')), 'verbose_name': 'Corp stats'}, + ), + ] diff --git a/corputils/migrations/__init__.py b/corputils/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/corputils/models.py b/corputils/models.py index baffc488..eb1896d9 100644 --- a/corputils/models.py +++ b/corputils/models.py @@ -1 +1,164 @@ 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', + 'delete', + 'view', + ) + verbose_name = "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(id=auth.main_char_id) + if char.corporation_id == self.corp.corporation_id and user.has_perm('corputils.corp_apis'): + return True + elif char.alliance_id == self.corp.alliance_id and user.has_perm('corputils.alliance_apis'): + return True + elif 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 + if show_apis: + self.api = EveApiKeyPair.objects.get(api_id=char.api_id) + except (EveCharacter.DoesNotExist, AuthServicesInfo.DoesNotExist): + self.main = None + self.api = None + except EveApiKeyPair.DoesNotExist: + self.api = None + + 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) diff --git a/corputils/templates/corputils/base.html b/corputils/templates/corputils/base.html new file mode 100644 index 00000000..476a5974 --- /dev/null +++ b/corputils/templates/corputils/base.html @@ -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 %} +
+

{% trans "Corporation Member Data" %}

+
+ + {% block member_data %}{% endblock %} +
+
+{% endblock %} + diff --git a/corputils/templates/corputils/corpstats.html b/corputils/templates/corputils/corpstats.html new file mode 100644 index 00000000..dd9e110b --- /dev/null +++ b/corputils/templates/corputils/corpstats.html @@ -0,0 +1,92 @@ +{% extends 'corputils/base.html' %} +{% load i18n %} +{% load humanize %} +{% load bootstrap_pagination %} +{% block member_data %} + {% if corpstats %} +
+
+ + + + {% if corpstats.corp.alliance %} + + {% endif %} + + + + {% if corpstats.corp.alliance %} + + {% endif %} + +
{{ corpstats.corp.corporation_name }}{{ corpstats.corp.alliance.alliance_name }}
+
+
+
+
+ {% trans "API Index:" %} +
+
+ {{ corpstats.registered_members }}/{{ corpstats.total_members }} +
+
+
+
+
+
+
+
+
+

Members

+
+
+ Last update: {{ corpstats.last_updated|naturaltime }} + {% if corpstats.can_update %} + + + + {% endif %} +
+
+
+
+ {% bootstrap_paginate members range=10 %} +
+
+ + + + + {% if corpstats.show_apis %} + + {% endif %} + + + + + + {% for member in members %} + + + + {% if corpstats.show_apis %} + {% if member.api %} + + {% else %} + + {% endif %} + {% endif %} + + + + + + {% endfor %} +
CharacterAPIzKillboardMain CharacterMain CorporationMain Alliance
{{ member.character_name }}{{ member.api.api_id }}{% trans "Killboard" %}{{ member.main.character_name }}{{ member.main.corporation_name }}{{ member.main.alliance_name }}
+
+
+
+
+
+ {% endif %} +{% endblock %} diff --git a/corputils/templates/corputils/search.html b/corputils/templates/corputils/search.html new file mode 100644 index 00000000..0848a3e9 --- /dev/null +++ b/corputils/templates/corputils/search.html @@ -0,0 +1,43 @@ +{% extends "corputils/base.html" %} +{% load i18n %} +{% load bootstrap_pagination %} +{% block member_data %} +
+
+
Search Results
+
+
+
+ {% bootstrap_paginate results range=10 %} +
+ + + + + + + + + + + + {% for result in results %} + + + + + {% if result.1.api %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} +
CharacterCorporationAPIzKillboardMain CharacterMain CorporationMain Alliance
{{ result.1.character_name }}{{ result.0.corp.corporation_name }}{{ result.1.api.api_id }}{% trans "Killboard" %}{{ result.1.main.character_name }}{{ result.1.main.corporation_name }}{{ result.1.main.alliance_name }}
+
+
+{% endblock %} diff --git a/corputils/urls.py b/corputils/urls.py new file mode 100644 index 00000000..3cd98e85 --- /dev/null +++ b/corputils/urls.py @@ -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(\d)*)/$', corputils.views.corpstats_view, name='view_corp'), + url(r'^(?P(\d)+)/update/$', corputils.views.corpstats_update, name='update'), + url(r'^search/$', corputils.views.corpstats_search, name='search'), + ] diff --git a/corputils/views.py b/corputils/views.py index 1dc6a874..c7e86def 100644 --- a/corputils/views.py +++ b/corputils/views.py @@ -1,325 +1,112 @@ 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 collections import namedtuple - -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 django.contrib.auth.decorators import login_required, permission_required +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 corputils.forms import CorputilsSearchForm -from evelink.api import APIError +from esi.decorators import token_required -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) +MEMBERS_PER_PAGE = int(getattr(settings, 'CORPSTATS_MEMBERS_PER_PAGE', 20)) +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 @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) - +@permission_required('corputils.view_corpstats') +@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) + CorpStats.objects.create(token=token, corp=corp) + 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.') + return redirect('corputils:view') @login_required -def corputils_search(request, corpid=settings.CORP_ID): - logger.debug("corputils_search called by user %s" % request.user) +@permission_required('corputils.view_corpstats') +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 this one + 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) + context = { + 'available': available, + } - SearchResult = namedtuple('SearchResult', - ['name', 'id', 'main', 'api_registered', 'character', 'apiinfo']) + # paginate + members = [] + if corpstats: + page = request.GET.get('page', 1) + members = get_page(corpstats.get_member_objects(request.user), page) - 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 + if corpstats: + context.update({ + 'corpstats': corpstats.get_view_model(request.user), + 'members': members, + }) - searchresults.append(SearchResult(name=member_data["name"], id=memberid, main=main, - api_registered=api_registered, - character=char, apiinfo=apiinfo)) + return render(request, 'corputils/corpstats.html', context=context) - logger.info("Found %s members for user %s matching search string %s" % ( - len(searchresults), request.user, searchstring)) - - 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 +@permission_required('corputils.view_corpstats') +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 +@permission_required('corputils.view_corpstats') +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') diff --git a/requirements.txt b/requirements.txt index a1d00830..eb129766 100755 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ sleekxmpp django>=1.10,<2.0 django-bootstrap-form django-navhelper +django-bootstrap-pagination # awating release for fix to celery/django-celery#447 # django-celery diff --git a/services/managers/evewho_manager.py b/services/managers/evewho_manager.py deleted file mode 100644 index ee7407e2..00000000 --- a/services/managers/evewho_manager.py +++ /dev/null @@ -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 diff --git a/stock/templates/public/base.html b/stock/templates/public/base.html index 8edaa580..5b6bb177 100755 --- a/stock/templates/public/base.html +++ b/stock/templates/public/base.html @@ -157,9 +157,9 @@ {% endif %} - {% if perms.auth.corp_apis or perms.auth.alliance_apis %} + {% if perms.corputils.view_corpstats %}
  • - + {% trans " Corporation Stats" %}
  • diff --git a/stock/templates/registered/corputils.html b/stock/templates/registered/corputils.html deleted file mode 100644 index 8cfffe7d..00000000 --- a/stock/templates/registered/corputils.html +++ /dev/null @@ -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 %} - -
    -

    {% trans "Corporation Member Data" %}

    -
    - {% if corp %} -
    -
    -
    -
    {% trans "Corporation" %}
    -
    -
    - -
    -
    -

    {{ corp.corporation_name }}

    - -

    {% trans "Ticker:" %} {{ corp.corporation_ticker }}

    - -

    {% trans "Member count:" %} {{ corp.member_count }}

    - -

    {% trans "Player count:" %} {{characters_with_api|length}}

    - -

    {% trans "Unregistered characters:" %} {{characters_without_api|length|add:n_unacounted}}

    -
    -
    - {% trans "API Index:" %} -
    -
    - {{n_registered}}/{{ corp.member_count }} -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {% if characters_with_api %} -
    -
    - - - - - - - - {% if perms.auth.fleetactivitytracking_statistics %} - - - {% else %} - - {% endif %} - - - {% for maincharname, player in characters_with_api %} - - - - - - - - {% if perms.auth.fleetactivitytracking %} - - {% endif %} - - - {% endfor %} -
    {% trans "Main character" %}{% trans "Main corporation" %}{% trans "Character list" %}{% trans "Fats" %}{% trans "Killboard" %}{% trans "Fleet statistics" %}{% trans "Killboard" %}{% trans "API JackKnife" %}
    - - -

    {{ maincharname }}

    -
    - {% if not corp.corporation_name == player.maincorp%} - - {{ player.maincorp }} - - {% else %} - - {{ player.maincorp }} - - {% endif %} - - {% for char in player.altlist %} -

    {{ char.character_name }}

    - {% endfor %} -
    - {{ player.n_fats }} - - {% for char in player.altlist %} -

    {% trans "Killboard" %}

    - {% endfor %} -
    - - - - - {% for apiinfo in player.apilist %} -

    - - {% trans "API JackKnife" %} - -

    - {% endfor %} -
    -
    -
    - {% else %} - - {% endif %} -
    -
    - {% if characters_without_api %} -
    -
    - {% if 0 < n_unacounted %} - - {% endif %} - - - - - - - {% for character_name, character_id in characters_without_api %} - - - - - - {% endfor %} -
    {% trans "Character" %}{% trans "Killboard" %}
    - - -

    {{ character_name }}

    -
    - {% trans "Killboard" %} -
    -
    -
    - {% else %} - - {% endif %} -
    -
    - {% else %} -
    -
    -
    - -
    -
    -
    -
    - {% endif %} -
    - -{% endblock content %} diff --git a/stock/templates/registered/corputilssearchview.html b/stock/templates/registered/corputilssearchview.html deleted file mode 100644 index dc45b6d0..00000000 --- a/stock/templates/registered/corputilssearchview.html +++ /dev/null @@ -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 %} -
    -

    {% trans "Member Search Results" %}

    -

    {{ corp.corporation_name }}

    -
    - -
    - -
    - - - - - - {% if perms.auth.fleetactivitytracking%} - - - {% else %} - - {% endif %} - - - {% for result in results %} - - - - - - - {% if perms.auth.fleetactivitytracking %} - {% if result.main %} - - {% else %} - - {% endif %} - {% endif %} - - - - {% endfor %} -
    {% trans "Character" %}{% trans "Main character" %}{% trans "Killboard" %}{% trans "Fleet statistics" %}{% trans "Killboard" %}{% trans "API JackKnife" %}
    - - {{ result.name }} - {% if result.api_registered%} - {{ result.main.character_name }} - {% else %} - {% trans "No API registered!" %} - {% endif %} - -

    {% trans "Killboard" %}

    -
    - - - - - {% if result.api_registered %} - - - - {% endif %} -
    -
    -
    -
    -
    -{% endblock content %}