Compare commits

...

16 Commits

Author SHA1 Message Date
Adarnof
a02e5f400a Version bump to v1.15.5 2017-10-03 22:37:56 -04:00
Adarnof
65c168939d Handle FAT ZeroDivisionErrors
Closes #881
2017-10-03 21:50:32 -04:00
Adarnof
313cac6ac7 Handle new zKillboard API format
Closes #872
2017-10-01 12:53:03 -04:00
Adarnof
0145ea82c8 Correct py3 __str__ support.
Change slugify package for py3
2017-09-30 18:38:05 -04:00
Adarnof
0cdc5ffbd5 Use pypi versioned adarnauth-esi 2017-09-27 18:52:48 -04:00
Basraah
0bdd044378 Improve support for milliseconds backoff 2017-09-26 09:02:37 +10:00
Adarnof
ad266ea2ee Increase tested retry after
Apparently tests take longer than 200ms to evaluate here.
2017-09-25 18:36:53 -04:00
Adarnof
7ea8c9e50d Retry after in milliseconds
Closes #874
2017-09-25 18:21:23 -04:00
mmolitor87
9a015fd582 Change index images to font (#841)
* Change index images to font

* Added SEAT_URL reference and added it to the index template
2017-09-23 08:29:08 +10:00
Adarnof
7ca1c87c87 Minimize swagger spec files. 2017-09-20 01:31:04 -04:00
Adarnof
eee6a9132d Use local swagger spec files (#866)
Allows auth to keep working if CCP changes "latest" definition.
Requires adarnauth-esi>=1.4
2017-09-17 01:08:02 -04:00
Basraah
9d90af4a3d Fixes #865 & adds unit tests 2017-09-13 20:16:07 +10:00
Adarnof
72305de2d8 Correct username hashing on py3 2017-09-13 00:29:59 -04:00
Adarnof
8f58f76001 Stop using v3(dev) Alliance resource
It keeps changing.
2017-09-12 20:16:15 -04:00
Adarnof
a969b6117b Fix missing operation in v3 alliance resource 2017-09-12 12:34:43 -04:00
Basraah
97762119b3 Remove .idea folder 2017-09-12 11:33:42 +10:00
35 changed files with 225 additions and 167 deletions

22
.idea/allianceauth.iml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="alliance_auth/settings.py.example" />
<option name="manageScript" value="manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="commandsToSkip" value="" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 2.7.11 virtualenv at ~/1.6" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
</component>
</module>

12
.idea/dataSources.ids generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="dataSourceStorage">
<data-source source="LOCAL" name="Django default" uuid="3eb61453-647a-4832-8320-f3561f039abc">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
</data-source>
<data-source source="LOCAL" name="Django phpbb3" uuid="2de247c2-1951-4e74-8276-6a1c89c396fa">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
</data-source>
<data-source source="LOCAL" name="Django mumble" uuid="9963e5ca-7f2f-4dd3-9175-bc7102dfd48c">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
</data-source>
</component>

20
.idea/dataSources.xml generated
View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Django default" uuid="3eb61453-647a-4832-8320-f3561f039abc">
<driver-ref>mysql</driver-ref>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_auth</jdbc-url>
</data-source>
<data-source source="LOCAL" name="Django phpbb3" uuid="2de247c2-1951-4e74-8276-6a1c89c396fa">
<driver-ref>mysql</driver-ref>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_forum</jdbc-url>
</data-source>
<data-source source="LOCAL" name="Django mumble" uuid="9963e5ca-7f2f-4dd3-9175-bc7102dfd48c">
<driver-ref>mysql</driver-ref>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_mumble</jdbc-url>
</data-source>
</component>
</project>

5
.idea/encodings.xml generated
View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

View File

@@ -1,11 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7.11 virtualenv at ~/1.6" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="1" />
</component>
</project>

9
.idea/modules.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/allianceauth.iml" filepath="$PROJECT_DIR$/.idea/allianceauth.iml" />
</modules>
</component>
</project>

View File

@@ -1,5 +0,0 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -4,5 +4,5 @@ from __future__ import absolute_import, unicode_literals
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
from .celeryapp import app as celery_app # noqa from .celeryapp import app as celery_app # noqa
__version__ = '1.15.4' __version__ = '1.15.5'
NAME = 'Alliance Auth v%s' % __version__ NAME = 'Alliance Auth v%s' % __version__

View File

@@ -11,6 +11,10 @@ from corputils.managers import CorpStatsManager
from operator import attrgetter from operator import attrgetter
import json import json
import logging import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -41,7 +45,7 @@ class CorpStats(models.Model):
def update(self): def update(self):
try: try:
c = self.token.get_esi_client(Character='v4', Corporation='v2') c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[ assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
'corporation_id'] == int(self.corp.corporation_id) 'corporation_id'] == int(self.corp.corporation_id)
members = c.Corporation.get_corporations_corporation_id_members( members = c.Corporation.get_corporations_corporation_id_members(
@@ -52,7 +56,6 @@ class CorpStats(models.Model):
# the swagger spec doesn't have a maxItems count # the swagger spec doesn't have a maxItems count
# manual testing says we can do over 350, but let's not risk it # manual testing says we can do over 350, but let's not risk it
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)] member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
c = self.token.get_esi_client(Character='v1') # ccplease bump versions of whole resources
member_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in member_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in
member_id_chunks] member_id_chunks]
member_list = {} member_list = {}

1
corputils/swagger.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,9 @@ from eveonline.models import EveCharacter, EveCorporationInfo
from corputils.models import CorpStats from corputils.models import CorpStats
from esi.decorators import token_required from esi.decorators import token_required
from bravado.exception import HTTPError from bravado.exception import HTTPError
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
MEMBERS_PER_PAGE = int(getattr(settings, 'CORPSTATS_MEMBERS_PER_PAGE', 20)) MEMBERS_PER_PAGE = int(getattr(settings, 'CORPSTATS_MEMBERS_PER_PAGE', 20))
@@ -41,9 +44,8 @@ def corpstats_add(request, token):
if EveCharacter.objects.filter(character_id=token.character_id).exists(): if EveCharacter.objects.filter(character_id=token.character_id).exists():
corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
else: else:
corp_id = \ corp_id = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH).Character.get_characters_character_id(
token.get_esi_client(Character='v4').Character.get_characters_character_id(character_id=token.character_id).result()[ character_id=token.character_id).result()['corporation_id']
'corporation_id']
corp = EveCorporationInfo.objects.get(corporation_id=corp_id) corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
cs = CorpStats.objects.create(token=token, corp=corp) cs = CorpStats.objects.create(token=token, corp=corp)
try: try:

View File

@@ -6,6 +6,9 @@ import json
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
import evelink import evelink
import logging import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -228,7 +231,7 @@ class EveProvider(object):
@python_2_unicode_compatible @python_2_unicode_compatible
class EveSwaggerProvider(EveProvider): class EveSwaggerProvider(EveProvider):
def __init__(self, token=None, adapter=None): def __init__(self, token=None, adapter=None):
self.client = esi_client_factory(token=token, Alliance='v3', Character='v4', Corporation='v2', Universe='v2') self.client = esi_client_factory(token=token, spec_file=SWAGGER_SPEC_PATH)
self.adapter = adapter or self self.adapter = adapter or self
def __str__(self): def __str__(self):
@@ -244,7 +247,7 @@ class EveSwaggerProvider(EveProvider):
data['alliance_name'], data['alliance_name'],
data['ticker'], data['ticker'],
corps, corps,
data['executor_corporation_id'], data['executor_corp'],
) )
return model return model
except HTTPNotFound: except HTTPNotFound:

1
eveonline/swagger.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,7 @@ def refresh_api(api):
EveManager.create_character_obj(c, api.user, api.api_id) EveManager.create_character_obj(c, api.user, api.api_id)
current_chars = EveCharacter.objects.filter(api_id=api.api_id) current_chars = EveCharacter.objects.filter(api_id=api.api_id)
for c in current_chars: for c in current_chars:
if not int(c.character_id) in [c.id for c in characters]: if not int(c.character_id) in [d.id for d in characters]:
logger.info("Character %s no longer found on API ID %s" % (c, api.api_id)) logger.info("Character %s no longer found on API ID %s" % (c, api.api_id))
c.delete() c.delete()
except evelink.api.APIError as e: except evelink.api.APIError as e:
@@ -60,7 +60,7 @@ def refresh_api(api):
api.api_id, e.required_mask, e.api_mask), level="danger") api.api_id, e.required_mask, e.api_mask), level="danger")
still_valid = False still_valid = False
except EveApiManager.ApiServerUnreachableError as e: except EveApiManager.ApiServerUnreachableError as e:
logger.warn("Error updating API %s\n%s" % (api.api_id, str(e))) logger.warning("Error updating API %s\n%s" % (api.api_id, str(e)))
finally: finally:
if not still_valid: if not still_valid:
EveManager.delete_characters_by_api_id(api.api_id, api.user.id) EveManager.delete_characters_by_api_id(api.api_id, api.user.id)

View File

@@ -1 +0,0 @@
# Create your tests here.

View File

View File

@@ -0,0 +1,125 @@
from __future__ import unicode_literals
try:
# Py3
from unittest import mock
except ImportError:
# Py2
import mock
from django.test import TestCase
from alliance_auth.tests.auth_utils import AuthUtils
from eveonline.providers import Character, Alliance, Corporation
from eveonline.managers import EveManager
from eveonline import tasks
from eveonline.models import EveApiKeyPair, EveCharacter
from services.managers.eve_api_manager import EveApiManager
MODULE_PATH = 'eveonline.tasks'
class EveOnlineTasksTestCase(TestCase):
def setUp(self):
self.user = AuthUtils.create_member('joebloggs')
self.api_key = EveApiKeyPair.objects.create(api_id='0118999',
api_key='hunter2',
user=self.user,
sso_verified=True)
@mock.patch(MODULE_PATH + '.EveApiManager.validate_api')
@mock.patch(MODULE_PATH + '.EveManager.get_characters_from_api')
def test_refresh_api_characters(self, get_characters_from_api, validate_api):
# Arrange
provider = mock.MagicMock()
provider.get_alliance.return_value = Alliance(provider, 22222, 'Test Alliance', 'TEST', [11111], 11111)
provider.get_corp.return_value = Corporation(provider, 11111, 'Test Corp', 'HERP', 12345, [12345, 23456], 22222)
mock_api_data = [
Character(provider, 12345, 'testchar1', 11111, 22222),
Character(provider, 23456, 'Will beAdded', 11111, 22222)
]
get_characters_from_api.return_value = mock_api_data
validate_api.return_value = True
EveManager.create_character_obj(mock_api_data[0], self.user, '0118999')
EveManager.create_character_obj(Character(provider, 34567, 'deletedcharacter', 11111, 22222),
self.user, '0118999')
# Act
tasks.refresh_api(self.api_key)
# Assert
self.assertTrue(EveCharacter.objects.filter(character_id='12345').exists())
self.assertTrue(EveCharacter.objects.filter(character_id='23456').exists())
self.assertFalse(EveCharacter.objects.filter(character_id='34567').exists())
args, kwargs = validate_api.call_args
self.assertEqual(args[0], self.api_key.api_id)
self.assertEqual(args[1], self.api_key.api_key)
self.assertEqual(args[2], self.api_key.user)
@mock.patch(MODULE_PATH + '.EveApiManager.validate_api')
@mock.patch(MODULE_PATH + '.EveManager')
def test_refresh_api_evelink_exception(self, evemanager, validate_api):
import evelink
validate_api.side_effect = evelink.api.APIError()
tasks.refresh_api(self.api_key)
self.assertTrue(validate_api.called)
self.assertFalse(evemanager.get_characters_from_api.called)
self.assertFalse(evemanager.delete_characters_by_api_id.called)
self.assertFalse(evemanager.delete_api_key_pair.called)
@mock.patch(MODULE_PATH + '.EveApiManager.validate_api')
@mock.patch(MODULE_PATH + '.EveManager')
def test_refresh_api_invalid(self, evemanager, validate_api):
validate_api.side_effect = EveApiManager.ApiInvalidError(self.api_key.api_id)
tasks.refresh_api(self.api_key)
self.assertTrue(validate_api.called)
self.assertFalse(evemanager.get_characters_from_api.called)
self.assertTrue(evemanager.delete_characters_by_api_id.called)
self.assertTrue(evemanager.delete_api_key_pair.called)
@mock.patch(MODULE_PATH + '.EveApiManager.validate_api')
@mock.patch(MODULE_PATH + '.EveManager')
def test_refresh_api_accountvalidationerror(self, evemanager, validate_api):
validate_api.side_effect = EveApiManager.ApiAccountValidationError(self.api_key.api_id)
tasks.refresh_api(self.api_key)
self.assertTrue(validate_api.called)
self.assertFalse(evemanager.get_characters_from_api.called)
self.assertTrue(evemanager.delete_characters_by_api_id.called)
self.assertTrue(evemanager.delete_api_key_pair.called)
@mock.patch(MODULE_PATH + '.EveApiManager.validate_api')
@mock.patch(MODULE_PATH + '.EveManager')
def test_refresh_api_maskvalidationerror(self, evemanager, validate_api):
validate_api.side_effect = EveApiManager.ApiMaskValidationError('12345', '1111', self.api_key.api_id)
tasks.refresh_api(self.api_key)
self.assertTrue(validate_api.called)
self.assertFalse(evemanager.get_characters_from_api.called)
self.assertTrue(evemanager.delete_characters_by_api_id.called)
self.assertTrue(evemanager.delete_api_key_pair.called)
@mock.patch(MODULE_PATH + '.EveApiManager.validate_api')
@mock.patch(MODULE_PATH + '.EveManager')
def test_refresh_api_invalid(self, evemanager, validate_api):
validate_api.side_effect = EveApiManager.ApiServerUnreachableError(self.api_key.api_id)
tasks.refresh_api(self.api_key)
self.assertTrue(validate_api.called)
self.assertFalse(evemanager.get_characters_from_api.called)
# Lets hope we never see that again
self.assertFalse(evemanager.delete_characters_by_api_id.called)
self.assertFalse(evemanager.delete_api_key_pair.called)

View File

@@ -36,5 +36,5 @@ class Fat(models.Model):
unique_together = (('character', 'fatlink'),) unique_together = (('character', 'fatlink'),)
def __str__(self): def __str__(self):
output = "Fat-link for %s" % self.character.character_name return "Fat-link for %s" % self.character.character_name
return output.encode('utf-8')

File diff suppressed because one or more lines are too long

View File

@@ -37,7 +37,7 @@
<td class="text-center">{{ fat.user }}</td> <td class="text-center">{{ fat.user }}</td>
<td class="text-center">{{ fat.character.character_name }}</td> <td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %} {% if fat.station != "No Station" %}
<td class="text-center">{% blocktrans %}Docked in {{ fat.system }}{% endblocktrans %}</td> <td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
{% else %} {% else %}
<td class="text-center">{{ fat.system }}</td> <td class="text-center">{{ fat.system }}</td>
{% endif %} {% endif %}

View File

@@ -36,7 +36,7 @@
<td class="text-center">{{ fat.fatlink.name }}</td> <td class="text-center">{{ fat.fatlink.name }}</td>
<td class="text-center">{{ fat.character.character_name }}</td> <td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %} {% if fat.station != "No Station" %}
<td class="text-center">{% blocktrans %}Docked in {{ fat.system }}{% endblocktrans %}</td> <td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
{% else %} {% else %}
<td class="text-center">{{ fat.system }}</td> <td class="text-center">{{ fat.system }}</td>
{% endif %} {% endif %}

View File

@@ -16,16 +16,15 @@ from eveonline.managers import EveManager
from authentication.models import AuthServicesInfo from authentication.models import AuthServicesInfo
from fleetactivitytracking.forms import FatlinkForm from fleetactivitytracking.forms import FatlinkForm
from fleetactivitytracking.models import Fatlink, Fat from fleetactivitytracking.models import Fatlink, Fat
from esi.decorators import token_required from esi.decorators import token_required
from slugify import slugify from slugify import slugify
import string import string
import random import random
import datetime import datetime
import logging import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -53,8 +52,12 @@ class CorpStat(object):
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count() fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
self.blue = self.corp.is_blue self.blue = self.corp.is_blue
@property
def avg_fat(self): def avg_fat(self):
return "%.2f" % (float(self.n_fats) / float(self.corp.member_count)) try:
return "%.2f" % (float(self.n_fats) / float(self.corp.member_count))
except ZeroDivisionError:
return "%.2f" % 0
class MemberStat(object): class MemberStat(object):
@@ -70,9 +73,13 @@ class MemberStat(object):
self.n_chars = nchars self.n_chars = nchars
self.n_fats = Fat.objects.filter(user_id=member['user_id']).filter( self.n_fats = Fat.objects.filter(user_id=member['user_id']).filter(
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count() fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
@property
def avg_fat(self): def avg_fat(self):
return "%.2f" % (float(self.n_fats) / float(self.n_chars)) try:
return "%.2f" % (float(self.n_fats) / float(self.n_chars))
except ZeroDivisionError:
return "%.2f" % 0
def first_day_of_next_month(year, month): def first_day_of_next_month(year, month):
@@ -133,7 +140,7 @@ def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
# collect and sort stats # collect and sort stats
stat_list = [fat_stats[x] for x in fat_stats] stat_list = [fat_stats[x] for x in fat_stats]
stat_list.sort(key=lambda stat: stat.mainchar.character_name) stat_list.sort(key=lambda stat: stat.mainchar.character_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.n_chars), reverse=True) stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
context = {'fatStats': stat_list, '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, 'corpid': corpid} 'previous_month': start_of_previous_month, 'corpid': corpid}
@@ -171,7 +178,7 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
# collect and sort stats # collect and sort stats
stat_list = [fat_stats[x] for x in fat_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.corp.corporation_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.corp.member_count), reverse=True) stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
context = {'fatStats': stat_list, '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} 'previous_month': start_of_previous_month}
@@ -256,7 +263,7 @@ def click_fatlink_view(request, token, hash, fatname):
if character: if character:
# get data # get data
c = token.get_esi_client(Location='v1', Universe='v2') c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result() 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() ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
location['solar_system_name'] = \ location['solar_system_name'] = \
@@ -266,7 +273,6 @@ def click_fatlink_view(request, token, hash, fatname):
location['station_name'] = \ location['station_name'] = \
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name'] c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
elif location['structure_id']: elif location['structure_id']:
c = token.get_esi_client(Universe='v1')
location['station_name'] = \ location['station_name'] = \
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[ c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
'name'] 'name']

View File

@@ -21,5 +21,4 @@ class optimer(models.Model):
eve_character = models.ForeignKey(EveCharacter) eve_character = models.ForeignKey(EveCharacter)
def __str__(self): def __str__(self):
output = self.operation_name return self.operation_name
return output.encode('utf-8')

View File

@@ -5,7 +5,7 @@ dnspython
passlib passlib
requests>=2.9.1 requests>=2.9.1
bcrypt bcrypt
slugify python-slugify>=1.2
requests-oauthlib requests-oauthlib
sleekxmpp sleekxmpp
redis redis
@@ -23,4 +23,4 @@ django-celery-beat
# awating pyghassen/openfire-restapi #1 to fix installation issues # awating pyghassen/openfire-restapi #1 to fix installation issues
git+https://github.com/adarnof/openfire-restapi git+https://github.com/adarnof/openfire-restapi
git+https://github.com/adarnof/adarnauth-esi adarnauth-esi>=1.4.1,<2.0

View File

@@ -15,6 +15,7 @@ def auth_settings(request):
'IPS4_URL': settings.IPS4_URL, 'IPS4_URL': settings.IPS4_URL,
'SMF_URL': settings.SMF_URL, 'SMF_URL': settings.SMF_URL,
'MARKET_URL': settings.MARKET_URL, 'MARKET_URL': settings.MARKET_URL,
'SEAT_URL': settings.SEAT_URL,
'EXTERNAL_MEDIA_URL': settings.EXTERNAL_MEDIA_URL, 'EXTERNAL_MEDIA_URL': settings.EXTERNAL_MEDIA_URL,
'CURRENT_UTC_TIME': timezone.now(), 'CURRENT_UTC_TIME': timezone.now(),
'BLUE_API_MASK': settings.BLUE_API_MASK, 'BLUE_API_MASK': settings.BLUE_API_MASK,

View File

@@ -27,11 +27,11 @@ class srpManager:
r = requests.get(url, headers=headers) r = requests.get(url, headers=headers)
result = r.json()[0] result = r.json()[0]
if result: if result:
ship_type = result['victim']['shipTypeID'] ship_type = result['victim']['ship_type_id']
logger.debug("Ship type for kill ID %s is determined to be %s" % (kill_id, ship_type)) logger.debug("Ship type for kill ID %s is %s" % (kill_id, ship_type))
ship_value = result['zkb']['totalValue'] ship_value = result['zkb']['totalValue']
logger.debug("total loss value for kill id %s is %s" % (kill_id, ship_value)) logger.debug("Total loss value for kill id %s is %s" % (kill_id, ship_value))
victim_name = result['victim']['characterName'] victim_id = result['victim']['character_id']
return ship_type, ship_value, victim_name return ship_type, ship_value, victim_id
else: else:
raise ValueError("Invalid Kill ID") raise ValueError("Invalid Kill ID")

View File

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import requests import requests
import json import json
import re import re
import math
from django.conf import settings from django.conf import settings
from requests_oauthlib import OAuth2Session from requests_oauthlib import OAuth2Session
from functools import wraps from functools import wraps
@@ -50,12 +51,20 @@ class DiscordApiTooBusy(DiscordApiException):
class DiscordApiBackoff(DiscordApiException): class DiscordApiBackoff(DiscordApiException):
def __init__(self, retry_after, global_ratelimit): def __init__(self, retry_after, global_ratelimit):
"""
:param retry_after: int time to retry after in milliseconds
:param global_ratelimit: bool Is the API under a global backoff
"""
super(DiscordApiException, self).__init__() super(DiscordApiException, self).__init__()
self.retry_after = retry_after self.retry_after = retry_after
self.global_ratelimit = global_ratelimit self.global_ratelimit = global_ratelimit
@property
def retry_after_seconds(self):
return math.ceil(self.retry_after / 1000)
cache_time_format = '%Y-%m-%d %H:%M:%S'
cache_time_format = '%Y-%m-%d %H:%M:%S.%f'
def api_backoff(func): def api_backoff(func):
@@ -117,12 +126,12 @@ def api_backoff(func):
retry_after = int(e.response.headers['Retry-After']) retry_after = int(e.response.headers['Retry-After'])
except (TypeError, KeyError): except (TypeError, KeyError):
# Pick some random time # Pick some random time
retry_after = 5 retry_after = 5000
logger.info("Received backoff from API of %s seconds, handling" % retry_after) logger.info("Received backoff from API of %s seconds, handling" % retry_after)
# Store value in redis # Store value in redis
backoff_until = (datetime.datetime.utcnow() + backoff_until = (datetime.datetime.utcnow() +
datetime.timedelta(seconds=retry_after)) datetime.timedelta(milliseconds=retry_after))
global_backoff = bool(e.response.headers.get('X-RateLimit-Global', False)) global_backoff = bool(e.response.headers.get('X-RateLimit-Global', False))
if global_backoff: if global_backoff:
logger.info("Global backoff!!") logger.info("Global backoff!!")
@@ -138,7 +147,7 @@ def api_backoff(func):
# Sleep if we're blocking # Sleep if we're blocking
if blocking: if blocking:
logger.info("Blocking Back off from API calls for %s seconds" % bo.retry_after) logger.info("Blocking Back off from API calls for %s seconds" % bo.retry_after)
time.sleep(10 if bo.retry_after > 10 else bo.retry_after) time.sleep((10 if bo.retry_after > 10 else bo.retry_after) / 1000)
else: else:
# Otherwise raise exception and let caller handle the backoff # Otherwise raise exception and let caller handle the backoff
raise DiscordApiBackoff(retry_after=bo.retry_after, global_ratelimit=bo.global_ratelimit) raise DiscordApiBackoff(retry_after=bo.retry_after, global_ratelimit=bo.global_ratelimit)

View File

@@ -75,8 +75,8 @@ class DiscordTasks:
DiscordOAuthManager.update_groups(user.discord.uid, groups) DiscordOAuthManager.update_groups(user.discord.uid, groups)
except DiscordApiBackoff as bo: except DiscordApiBackoff as bo:
logger.info("Discord group sync API back off for %s, " logger.info("Discord group sync API back off for %s, "
"retrying in %s seconds" % (user, bo.retry_after)) "retrying in %s seconds" % (user, bo.retry_after_seconds))
raise task_self.retry(countdown=bo.retry_after) raise task_self.retry(countdown=bo.retry_after_seconds)
except Exception as e: except Exception as e:
if task_self: if task_self:
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user) logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)

View File

@@ -402,7 +402,7 @@ class DiscordManagerTestCase(TestCase):
m.patch(request_url, m.patch(request_url,
request_headers=headers, request_headers=headers,
headers={'Retry-After': '200'}, headers={'Retry-After': '200000'},
status_code=429) status_code=429)
# Act & Assert # Act & Assert
@@ -410,7 +410,7 @@ class DiscordManagerTestCase(TestCase):
try: try:
DiscordOAuthManager.update_groups(user_id, groups, blocking=False) DiscordOAuthManager.update_groups(user_id, groups, blocking=False)
except manager.DiscordApiBackoff as bo: except manager.DiscordApiBackoff as bo:
self.assertEqual(bo.retry_after, 200, 'Retry-After time must be equal to Retry-After set in header') self.assertEqual(bo.retry_after, 200000, 'Retry-After time must be equal to Retry-After set in header')
self.assertFalse(bo.global_ratelimit, 'global_ratelimit must be False') self.assertFalse(bo.global_ratelimit, 'global_ratelimit must be False')
raise bo raise bo
@@ -437,7 +437,7 @@ class DiscordManagerTestCase(TestCase):
m.patch(request_url, m.patch(request_url,
request_headers=headers, request_headers=headers,
headers={'Retry-After': '200', 'X-RateLimit-Global': 'true'}, headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
status_code=429) status_code=429)
# Act & Assert # Act & Assert
@@ -445,7 +445,7 @@ class DiscordManagerTestCase(TestCase):
try: try:
DiscordOAuthManager.update_groups(user_id, groups, blocking=False) DiscordOAuthManager.update_groups(user_id, groups, blocking=False)
except manager.DiscordApiBackoff as bo: except manager.DiscordApiBackoff as bo:
self.assertEqual(bo.retry_after, 200, 'Retry-After time must be equal to Retry-After set in header') self.assertEqual(bo.retry_after, 200000, 'Retry-After time must be equal to Retry-After set in header')
self.assertTrue(bo.global_ratelimit, 'global_ratelimit must be True') self.assertTrue(bo.global_ratelimit, 'global_ratelimit must be True')
raise bo raise bo

View File

@@ -264,5 +264,5 @@ class SeatManager:
@staticmethod @staticmethod
def username_hash(username): def username_hash(username):
m = hashlib.sha1() m = hashlib.sha1()
m.update(username) m.update(username.encode('utf-8'))
return m.hexdigest() return m.hexdigest()

View File

@@ -224,7 +224,7 @@ def srp_request_view(request, fleet_srp):
try: try:
srp_kill_link = srpManager.get_kill_id(srp_request.killboard_link) srp_kill_link = srpManager.get_kill_id(srp_request.killboard_link)
(ship_type_id, ship_value, victim_name) = srpManager.get_kill_data(srp_kill_link) (ship_type_id, ship_value, victim_id) = srpManager.get_kill_data(srp_kill_link)
except ValueError: except ValueError:
logger.debug("User %s Submitted Invalid Killmail Link %s or server could not be reached" % ( logger.debug("User %s Submitted Invalid Killmail Link %s or server could not be reached" % (
request.user, srp_request.killboard_link)) request.user, srp_request.killboard_link))
@@ -235,7 +235,7 @@ def srp_request_view(request, fleet_srp):
characters = EveManager.get_characters_by_owner_id(request.user.id) characters = EveManager.get_characters_by_owner_id(request.user.id)
for character in characters: for character in characters:
if character.character_name == victim_name: if character.character_id == str(victim_id):
srp_request.srp_ship_name = EveManager.get_itemtype(ship_type_id).name srp_request.srp_ship_name = EveManager.get_itemtype(ship_type_id).name
srp_request.kb_total_loss = ship_value srp_request.kb_total_loss = ship_value
srp_request.post_time = post_time srp_request.post_time = post_time
@@ -247,8 +247,8 @@ def srp_request_view(request, fleet_srp):
else: else:
continue continue
messages.error(request, messages.error(request,
_("%(charname)s does not belong to your Auth account. Please add the API key for this character and try again") _("Character ID %(charid)s does not belong to your Auth account. Please add the API key for this character and try again")
% {"charname": victim_name}) % {"charid": victim_id})
return redirect("auth_srp_management_view") return redirect("auth_srp_management_view")
else: else:
logger.debug("Returning blank SrpFleetUserRequestForm") logger.debug("Returning blank SrpFleetUserRequestForm")

View File

@@ -4,6 +4,7 @@
<head lang="en"> <head lang="en">
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ SITE_NAME }}</title> <title>{{ SITE_NAME }}</title>
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<style> <style>
html { html {
background: url('{% static 'img/index_images/index_blank_bg.jpg' %}') no-repeat scroll; background: url('{% static 'img/index_images/index_blank_bg.jpg' %}') no-repeat scroll;
@@ -22,6 +23,7 @@
margin-top: -100px; margin-top: -100px;
margin-left: -200px; margin-left: -200px;
} }
#logo { #logo {
height: 200px; height: 200px;
width: 900px; width: 900px;
@@ -31,9 +33,18 @@
margin-top: -100px; margin-top: -100px;
margin-left: -450px; margin-left: -450px;
} }
img { img {
border: 0; border: 0;
} }
a:link, a:hover, a:visited, a:active {
font-family: 'Roboto', sans-serif;
color: #ffffff;
font-size: 36px;
padding: 10px 20px 10px 20px;
text-decoration: none;
}
</style> </style>
</head> </head>
<body> <body>
@@ -44,29 +55,31 @@
</div> </div>
<div id="content"> <div id="content">
<p style="text-align:center"> <p style="text-align:center">
<a href="/dashboard/"> <a href="/dashboard/">auth</a>
<img src="{% static 'img/index_images/auth.png' %}" alt="Auth">
</a>
</p> </p>
{% if FORUM_URL %} {% if FORUM_URL %}
<p style="text-align:center"> <p style="text-align:center">
<a href="{{FORUM_URL}}"> <a href="{{FORUM_URL}}">forum</a>
<img src="{% static 'img/index_images/forums.png' %}" alt="Forums"> </p>
</a> {% endif %}
{% if MARKET_URL %}
<p style="text-align:center">
<a href="{{MARKET_URL}}">market</a>
</p>
{% endif %}
{% if SEAT_URL %}
<p style="text-align:center">
<a href="{{SEAT_URL}}">seat</a>
</p> </p>
{% endif %} {% endif %}
{% if KILLBOARD_URL %} {% if KILLBOARD_URL %}
<p style="text-align:center"> <p style="text-align:center">
<a href="{{KILLBOARD_URL}}"> <a href="{{KILLBOARD_URL}}">killboard</a>
<img src="{% static 'img/index_images/killboard.png' %}" alt="Killboard">
</a>
</p> </p>
{% endif %} {% endif %}
{% if EXTERNAL_MEDIA_URL %} {% if EXTERNAL_MEDIA_URL %}
<p style="text-align:center"> <p style="text-align:center">
<a href="{{EXTERNAL_MEDIA_URL}}"> <a href="{{EXTERNAL_MEDIA_URL}}">media</a>
<img src="{% static 'img/index_images/media.png' %}" alt="External Media">
</a>
</p> </p>
{% endif %} {% endif %}
</div> </div>