Compare commits

...

29 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
Adarnof
4bdead5ef2 Version bump to v1.15.4 2017-09-11 20:52:07 -04:00
Adarnof
8987cf2199 Use Django's cache framework for service group names (#857)
Use django-redis-cache backend for locking get_or_set
No longer require group-related tasks to be locked to one simultaneous execution.
Remove legacy service group cache models.

Truncate Discord nicknames to 32 characters
Correct Discourse group name extension using only valid leading characters.
Prevent name slicing from ending with illegal character

Closes #801
Closes #847
Closes #835
Closes #852
2017-09-11 20:42:13 -04:00
Basraah
27c9b09116 Add encoding declaration to prevent #819 2017-09-11 12:59:13 +10:00
Adarnof
0ac0f71fef Correct SeAT API logic (#860)
* Do not attempt to change user email on SeAT if unchanged.
This prevents HTTP422 from being raised on password resets.

*Delete users on deactivation.
The existing disable user logic does nothing and results in a HTTP500.
It's safe to delete users entirely - the API keys are retained.

Fixes #844
2017-09-08 13:42:35 -04:00
Basraah
3f454743a9 Openfire group names fix (#859)
* Force lowercase group names

* Fix comparison of group names

* Sanitise group name for broadcast message
2017-09-05 13:12:27 -04:00
Basraah
c2f12eed26 Fixes #753 2017-09-05 14:46:38 +10:00
Basraah
1b1b692ac0 Add missing block tag 2017-09-03 09:09:48 +10:00
mmolitor87
dc8ed2d510 Supervisor celery simplification (#849)
Combines celery confs under one process group for easy restarting. Updates docs to reflect new commands.
2017-09-01 15:43:54 -04:00
Basraah
049c1c66aa Make Fleet-up datetimes timezone aware (#856)
Additionally fix logger depreciation warnings
2017-09-01 15:41:45 -04:00
Adarnof
8028660a8f Return tuple on SeAT activation failure
Addresses #844
2017-09-01 00:40:22 -04:00
ghoti
e6532025f8 fixes #848 - could be bug, could be permanent unclear change from CCP (#854) 2017-09-01 00:29:31 -04:00
cameronurnes
3361d36bbf Allow preset choices for questions in HR (#836) 2017-09-01 00:25:15 -04:00
Basraah
2ab45b1019 Fix cache type error with retry time 2017-07-26 08:59:22 +10:00
57 changed files with 585 additions and 446 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.
from .celeryapp import app as celery_app # noqa
__version__ = '1.15.3'
__version__ = '1.15.5'
NAME = 'Alliance Auth v%s' % __version__

View File

@@ -1,3 +1,4 @@
# -*- coding: UTF-8 -*-
"""
Django settings for alliance_auth project.
@@ -210,10 +211,10 @@ MESSAGE_TAGS = {
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "localhost:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"DB": 1,
}
}
}

View File

@@ -11,6 +11,10 @@ from corputils.managers import CorpStatsManager
from operator import attrgetter
import json
import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
logger = logging.getLogger(__name__)
@@ -41,7 +45,7 @@ class CorpStats(models.Model):
def update(self):
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()[
'corporation_id'] == int(self.corp.corporation_id)
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
# 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)]
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_id_chunks]
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 esi.decorators import token_required
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))
@@ -41,9 +44,8 @@ def corpstats_add(request, token):
if EveCharacter.objects.filter(character_id=token.character_id).exists():
corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
else:
corp_id = \
token.get_esi_client(Character='v4').Character.get_characters_character_id(character_id=token.character_id).result()[
'corporation_id']
corp_id = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH).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)
try:

View File

@@ -22,7 +22,7 @@ CentOS:
Auth provides example config files for the celery workers, the periodic task scheduler (celery beat), and the mumble authenticator. All of these are available in `thirdparty/Supervisor`.
For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth-celerybeat.conf` and `auth-celeryd.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard:
For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard:
sudo cp thirdparty/Supervisor/* /etc/supervisor/conf.d
@@ -44,6 +44,24 @@ Processes will be `STARTING`, `RUNNING`, or `ERROR`. If an error has occurred, c
- celery workers: `log/worker.log`
- celery beat: `log/beat.log`
- authenticator: `log/authenticator.log`
## Restarting Processes
To restart the celery group:
sudo supervisorctl restart auth:*
To restart just celerybeat:
sudo supervisorctl restart auth:celerybeat
To restart just celeryd:
sudo supervisorctl restart auth:celeryd
To restart just mumble authenticator:
sudo supervisorctl restart auth-mumble
## Customizing Config Files

View File

@@ -6,6 +6,9 @@ import json
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
import evelink
import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
logger = logging.getLogger(__name__)
@@ -228,7 +231,7 @@ class EveProvider(object):
@python_2_unicode_compatible
class EveSwaggerProvider(EveProvider):
def __init__(self, token=None, adapter=None):
self.client = esi_client_factory(token=token, Alliance='v1', Character='v4', Corporation='v2', Universe='v2')
self.client = esi_client_factory(token=token, spec_file=SWAGGER_SPEC_PATH)
self.adapter = adapter or self
def __str__(self):
@@ -244,7 +247,7 @@ class EveSwaggerProvider(EveProvider):
data['alliance_name'],
data['ticker'],
corps,
data['executor_corporation_id'],
data['executor_corp'],
)
return model
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)
current_chars = EveCharacter.objects.filter(api_id=api.api_id)
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))
c.delete()
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")
still_valid = False
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:
if not still_valid:
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'),)
def __str__(self):
output = "Fat-link for %s" % self.character.character_name
return output.encode('utf-8')
return "Fat-link for %s" % self.character.character_name

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,8 @@
{% load i18n %}
{% block title %}Fleet Participation{% endblock %}
{% block page_title %}{% trans "Fleet Participation" %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Character not found!" %}</h1>
<div class="col-lg-12 container" id="example">

View File

@@ -37,7 +37,7 @@
<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">{% blocktrans %}Docked in {{ fat.system }}{% endblocktrans %}</td>
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}

View File

@@ -36,7 +36,7 @@
<td class="text-center">{{ fat.fatlink.name }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% 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 %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}

View File

@@ -16,16 +16,15 @@ from eveonline.managers import EveManager
from authentication.models import AuthServicesInfo
from fleetactivitytracking.forms import FatlinkForm
from fleetactivitytracking.models import Fatlink, Fat
from esi.decorators import token_required
from slugify import slugify
import string
import random
import datetime
import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
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()
self.blue = self.corp.is_blue
@property
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):
@@ -70,9 +73,13 @@ class MemberStat(object):
self.n_chars = nchars
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()
@property
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):
@@ -133,7 +140,7 @@ def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
# collect and sort 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.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,
'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
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)
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,
'previous_month': start_of_previous_month}
@@ -256,7 +263,7 @@ def click_fatlink_view(request, token, hash, fatname):
if character:
# 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()
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
location['solar_system_name'] = \
@@ -266,7 +273,6 @@ def click_fatlink_view(request, token, hash, fatname):
location['station_name'] = \
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
elif location['structure_id']:
c = token.get_esi_client(Universe='v1')
location['station_name'] = \
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
'name']

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from django.conf import settings
from django.core.cache import cache
from django.utils import timezone
from datetime import datetime
import logging
@@ -17,6 +18,8 @@ class FleetUpManager:
GROUP_ID = settings.FLEETUP_GROUP_ID
BASE_URL = "http://api.fleet-up.com/Api.svc/{}/{}/{}".format(APP_KEY, USER_ID, API_ID)
TZ = timezone.utc
def __init__(self):
pass
@@ -60,7 +63,7 @@ class FleetUpManager:
cache.set(cache_key, json, cls._cache_until_seconds(json['CachedUntilUTC']))
return json
except requests.exceptions.ConnectionError:
logger.warn("Can't connect to Fleet-Up API, is it offline?!")
logger.warning("Can't connect to Fleet-Up API, is it offline?!")
except requests.HTTPError:
logger.exception("Error accessing Fleetup API")
return None
@@ -87,8 +90,10 @@ class FleetUpManager:
if foperations is None:
return None
return {row["StartString"]: {"subject": row["Subject"],
"start": datetime.strptime(row["StartString"], "%Y-%m-%d %H:%M:%S"),
"end": datetime.strptime(row["EndString"], "%Y-%m-%d %H:%M:%S"),
"start": timezone.make_aware(
datetime.strptime(row["StartString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
"end": timezone.make_aware(
datetime.strptime(row["EndString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
"operation_id": row["OperationId"],
"location": row["Location"],
"location_info": row["LocationInfo"],
@@ -109,9 +114,9 @@ class FleetUpManager:
"owner": row["Owner"],
"type": row["Type"],
"timer_type": row["TimerType"],
"expires": (datetime.strptime(row["ExpiresString"], "%Y-%m-%d %H:%M:%S")),
"expires": timezone.make_aware(
datetime.strptime(row["ExpiresString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
"notes": row["Notes"]} for row in ftimers["Data"]}
return {}
@classmethod
def get_fleetup_doctrines(cls):
@@ -143,9 +148,10 @@ class FleetUpManager:
"estimated": row["EstPrice"],
"faction": row["Faction"],
"categories": row["Categories"],
"last_update": (
datetime.strptime(row["LastUpdatedString"], "%Y-%m-%d %H:%M:%S"))} for row in
ffittings["Data"]}
"last_update":
timezone.make_aware(
datetime.strptime(row["LastUpdatedString"], "%Y-%m-%d %H:%M:%S"), cls.TZ)}
for row in ffittings["Data"]}
@classmethod
def get_fleetup_fitting(cls, fittingnumber):
@@ -156,7 +162,7 @@ class FleetUpManager:
return None
return {"fitting_data": ffitting["Data"]}
except KeyError:
logger.warn("Failed to retrieve fleetup fitting number %s" % fittingnumber)
logger.warning("Failed to retrieve fleetup fitting number %s" % fittingnumber)
return {"fitting_data": {}}
@classmethod
@@ -180,5 +186,5 @@ class FleetUpManager:
return None
return {"fitting_eft": ffittingeft["Data"]["FittingData"]}
except KeyError:
logger.warn("Fleetup fitting eft not found for fitting number %s" % fittingnumber)
logger.warning("Fleetup fitting eft not found for fitting number %s" % fittingnumber)
return {"fitting_eft": {}}

View File

@@ -12,6 +12,7 @@ import json
import datetime
from django.test import TestCase
from django.utils.timezone import make_aware, utc
from fleetup.managers import FleetUpManager
@@ -148,8 +149,8 @@ class FleetupManagerTestCase(TestCase):
expected_result = {
'2017-05-06 11:11:11': {
'subject': 'test_operation',
'start': datetime.datetime(2017, 5, 6, 11, 11, 11),
'end': datetime.datetime(2017, 5, 6, 12, 12, 12),
'start': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc),
'end': make_aware(datetime.datetime(2017, 5, 6, 12, 12, 12), utc),
'operation_id': 1234,
'location': 'Jita',
'location_info': '4-4',
@@ -208,7 +209,7 @@ class FleetupManagerTestCase(TestCase):
FleetUpManager.GROUP_ID)
expected_result = {
'2017-05-06 11:11:11': {
'expires': datetime.datetime(2017, 5, 6, 11, 11, 11),
'expires': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc),
'solarsystem': 'Jita',
'planet': '4',
'moon': '4',
@@ -361,7 +362,7 @@ class FleetupManagerTestCase(TestCase):
'estimated': 500000000,
'faction': 'Amarr',
'categories': ["Armor", "Laser"],
'last_update': datetime.datetime(2017, 5, 6, 11, 11, 11)
'last_update': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc)
}
}
self.assertDictEqual(expected_result, result)

View File

@@ -6,9 +6,22 @@ from hrapplications.models import ApplicationQuestion
from hrapplications.models import ApplicationForm
from hrapplications.models import ApplicationResponse
from hrapplications.models import ApplicationComment
from hrapplications.models import ApplicationChoice
class ChoiceInline(admin.TabularInline):
model = ApplicationChoice
extra = 0
verbose_name_plural = 'Choices (optional)'
verbose_name= 'Choice'
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['title', 'help_text']}),
]
inlines = [ChoiceInline]
admin.site.register(Application)
admin.site.register(ApplicationComment)
admin.site.register(ApplicationQuestion)
admin.site.register(ApplicationQuestion, QuestionAdmin)
admin.site.register(ApplicationForm)
admin.site.register(ApplicationResponse)

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-23 19:46
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hrapplications', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ApplicationChoice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('choice_text', models.CharField(max_length=200, verbose_name='Choice')),
],
),
migrations.AlterField(
model_name='applicationquestion',
name='title',
field=models.CharField(max_length=254, verbose_name='Question'),
),
migrations.AddField(
model_name='applicationchoice',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='hrapplications.ApplicationQuestion'),
),
]

View File

@@ -11,13 +11,21 @@ from authentication.models import AuthServicesInfo
@python_2_unicode_compatible
class ApplicationQuestion(models.Model):
title = models.CharField(max_length=254)
title = models.CharField(max_length=254, verbose_name='Question')
help_text = models.CharField(max_length=254, blank=True, null=True)
def __str__(self):
return "Question: " + self.title
@python_2_unicode_compatible
class ApplicationChoice(models.Model):
question = models.ForeignKey(ApplicationQuestion,on_delete=models.CASCADE,related_name="choices")
choice_text = models.CharField(max_length=200, verbose_name='Choice')
def __str__(self):
return self.choice_text
@python_2_unicode_compatible
class ApplicationForm(models.Model):
questions = models.ManyToManyField(ApplicationQuestion)

View File

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

View File

@@ -5,7 +5,7 @@ dnspython
passlib
requests>=2.9.1
bcrypt
slugify
python-slugify>=1.2
requests-oauthlib
sleekxmpp
redis
@@ -16,11 +16,11 @@ django>=1.10,<2.0
django-bootstrap-form
django-navhelper
django-bootstrap-pagination
django-redis>=4.4
django-redis-cache>=1.7.1
django-recaptcha
django-celery-beat
# awating pyghassen/openfire-restapi #1 to fix installation issues
git+https://github.com/adarnof/openfire-restapi
git+https://github.com/adarnof/adarnauth-esi
adarnauth-esi>=1.4.1,<2.0

View File

@@ -1,5 +0,0 @@
from __future__ import unicode_literals
from django.contrib import admin
from services.models import GroupCache
admin.site.register(GroupCache)

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-09-02 06:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('services', '0002_auto_20161016_0135'),
]
operations = [
migrations.DeleteModel(
name='GroupCache',
),
]

View File

@@ -1,18 +0,0 @@
from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible
from django.db import models
@python_2_unicode_compatible
class GroupCache(models.Model):
SERVICE_CHOICES = (
("discourse", "discourse"),
("discord", "discord"),
)
created = models.DateTimeField(auto_now_add=True)
groups = models.TextField(default={})
service = models.CharField(max_length=254, choices=SERVICE_CHOICES, unique=True)
def __str__(self):
return self.service

View File

@@ -2,15 +2,15 @@ from __future__ import unicode_literals
import requests
import json
import re
import math
from django.conf import settings
from services.models import GroupCache
from requests_oauthlib import OAuth2Session
from functools import wraps
import logging
import datetime
import time
from django.utils import timezone
from django.core.cache import cache
from hashlib import md5
logger = logging.getLogger(__name__)
@@ -20,9 +20,13 @@ EVE_IMAGE_SERVER = "https://image.eveonline.com"
AUTH_URL = "https://discordapp.com/api/oauth2/authorize"
TOKEN_URL = "https://discordapp.com/api/oauth2/token"
# needs administrator, since Discord can't get their permissions system to work
# was kick members, manage roles, manage nicknames
#BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
"""
Previously all we asked for was permission to kick members, manage roles, and manage nicknames.
Users have reported weird unauthorized errors we don't understand. So now we ask for full server admin.
It's almost fixed the problem.
"""
# kick members, manage roles, manage nicknames
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
BOT_PERMISSIONS = 0x00000008
# get user ID, accept invite
@@ -31,7 +35,7 @@ SCOPES = [
'guilds.join',
]
GROUP_CACHE_MAX_AGE = datetime.timedelta(minutes=30)
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # 2 hours default
class DiscordApiException(Exception):
@@ -47,12 +51,20 @@ class DiscordApiTooBusy(DiscordApiException):
class DiscordApiBackoff(DiscordApiException):
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__()
self.retry_after = retry_after
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):
@@ -110,16 +122,16 @@ def api_backoff(func):
break
except requests.HTTPError as e:
if e.response.status_code == 429:
if 'Retry-After' in e.response.headers:
retry_after = e.response.headers['Retry-After']
else:
try:
retry_after = int(e.response.headers['Retry-After'])
except (TypeError, KeyError):
# Pick some random time
retry_after = 5
retry_after = 5000
logger.info("Received backoff from API of %s seconds, handling" % retry_after)
# Store value in redis
backoff_until = (datetime.datetime.utcnow() +
datetime.timedelta(seconds=int(retry_after)))
datetime.timedelta(milliseconds=retry_after))
global_backoff = bool(e.response.headers.get('X-RateLimit-Global', False))
if global_backoff:
logger.info("Global backoff!!")
@@ -135,7 +147,7 @@ def api_backoff(func):
# Sleep if we're blocking
if blocking:
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:
# Otherwise raise exception and let caller handle the backoff
raise DiscordApiBackoff(retry_after=bo.retry_after, global_ratelimit=bo.global_ratelimit)
@@ -150,10 +162,14 @@ class DiscordOAuthManager:
def __init__(self):
pass
@staticmethod
def _sanitize_name(name):
return re.sub('[^\w.-]', '', name)[:32]
@staticmethod
def _sanitize_groupname(name):
name = name.strip(' _')
return re.sub('[^\w.-]', '', name)
return DiscordOAuthManager._sanitize_name(name)
@staticmethod
def generate_bot_add_url():
@@ -198,8 +214,9 @@ class DiscordOAuthManager:
@staticmethod
def update_nickname(user_id, nickname):
try:
nickname = DiscordOAuthManager._sanitize_name(nickname)
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
data = {'nick': nickname, }
data = {'nick': nickname}
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
r = requests.patch(path, headers=custom_headers, json=data)
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
@@ -230,7 +247,7 @@ class DiscordOAuthManager:
return False
@staticmethod
def __get_groups():
def _get_groups():
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
r = requests.get(path, headers=custom_headers)
@@ -239,41 +256,20 @@ class DiscordOAuthManager:
return r.json()
@staticmethod
def __update_group_cache():
GroupCache.objects.filter(service="discord").delete()
cache = GroupCache.objects.create(service="discord")
cache.groups = json.dumps(DiscordOAuthManager.__get_groups())
cache.save()
return cache
def _generate_cache_role_key(name):
return 'DISCORD_ROLE_NAME__%s' % md5(str(name).encode('utf-8')).hexdigest()
@staticmethod
def __get_group_cache():
if not GroupCache.objects.filter(service="discord").exists():
DiscordOAuthManager.__update_group_cache()
cache = GroupCache.objects.get(service="discord")
age = timezone.now() - cache.created
if age > GROUP_CACHE_MAX_AGE:
logger.debug("Group cache has expired. Triggering update.")
cache = DiscordOAuthManager.__update_group_cache()
return json.loads(cache.groups)
def _group_name_to_id(name):
name = DiscordOAuthManager._sanitize_groupname(name)
@staticmethod
def __group_name_to_id(name):
cache = DiscordOAuthManager.__get_group_cache()
for g in cache:
if g['name'] == name:
return g['id']
logger.debug("Group %s not found on Discord. Creating" % name)
DiscordOAuthManager.__create_group(name)
return DiscordOAuthManager.__group_name_to_id(name)
@staticmethod
def __group_id_to_name(id):
cache = DiscordOAuthManager.__get_group_cache()
for g in cache:
if g['id'] == id:
return g['name']
raise KeyError("Group ID %s not found on Discord" % id)
def get_or_make_role():
groups = DiscordOAuthManager._get_groups()
for g in groups:
if g['name'] == name:
return g['id']
return DiscordOAuthManager._create_group(name)['id']
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
@staticmethod
def __generate_role():
@@ -300,16 +296,15 @@ class DiscordOAuthManager:
return r.json()
@staticmethod
def __create_group(name):
def _create_group(name):
role = DiscordOAuthManager.__generate_role()
DiscordOAuthManager.__edit_role(role['id'], name)
DiscordOAuthManager.__update_group_cache()
return DiscordOAuthManager.__edit_role(role['id'], name)
@staticmethod
@api_backoff
def update_groups(user_id, groups):
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
group_ids = [DiscordOAuthManager.__group_name_to_id(DiscordOAuthManager._sanitize_groupname(g)) for g in groups]
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_groupname(g)) for g in groups]
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
data = {'roles': group_ids}
r = requests.patch(path, headers=custom_headers, json=data)

View File

@@ -10,7 +10,6 @@ from django.core.exceptions import ObjectDoesNotExist
from eveonline.managers import EveManager
from notifications import notify
from services.modules.discord.manager import DiscordOAuthManager, DiscordApiBackoff
from services.tasks import only_one
from .models import DiscordUser
logger = logging.getLogger(__name__)
@@ -76,8 +75,8 @@ class DiscordTasks:
DiscordOAuthManager.update_groups(user.discord.uid, groups)
except DiscordApiBackoff as bo:
logger.info("Discord group sync API back off for %s, "
"retrying in %s seconds" % (user, bo.retry_after))
raise task_self.retry(countdown=bo.retry_after)
"retrying in %s seconds" % (user, bo.retry_after_seconds))
raise task_self.retry(countdown=bo.retry_after_seconds)
except Exception as e:
if task_self:
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)

View File

@@ -351,7 +351,7 @@ class DiscordManagerTestCase(TestCase):
# Assert
self.assertTrue(result)
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._DiscordOAuthManager__get_group_cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
@requests_mock.Mocker()
def test_update_groups(self, group_cache, m):
from . import manager
@@ -385,7 +385,7 @@ class DiscordManagerTestCase(TestCase):
self.assertNotIn(444, history['roles'], 'The group id 444 must NOT be added to the request')
@mock.patch(MODULE_PATH + '.manager.cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._DiscordOAuthManager__get_group_cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
@requests_mock.Mocker()
def test_update_groups_backoff(self, group_cache, djcache, m):
from . import manager
@@ -402,7 +402,7 @@ class DiscordManagerTestCase(TestCase):
m.patch(request_url,
request_headers=headers,
headers={'Retry-After': '200'},
headers={'Retry-After': '200000'},
status_code=429)
# Act & Assert
@@ -410,7 +410,7 @@ class DiscordManagerTestCase(TestCase):
try:
DiscordOAuthManager.update_groups(user_id, groups, blocking=False)
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')
raise bo
@@ -420,7 +420,7 @@ class DiscordManagerTestCase(TestCase):
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
@mock.patch(MODULE_PATH + '.manager.cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._DiscordOAuthManager__get_group_cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
@requests_mock.Mocker()
def test_update_groups_global_backoff(self, group_cache, djcache, m):
from . import manager
@@ -437,7 +437,7 @@ class DiscordManagerTestCase(TestCase):
m.patch(request_url,
request_headers=headers,
headers={'Retry-After': '200', 'X-RateLimit-Global': 'true'},
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
status_code=429)
# Act & Assert
@@ -445,7 +445,7 @@ class DiscordManagerTestCase(TestCase):
try:
DiscordOAuthManager.update_groups(user_id, groups, blocking=False)
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')
raise bo

View File

@@ -1,17 +1,15 @@
from __future__ import unicode_literals
import logging
import requests
import random
import string
import datetime
import json
import re
from django.conf import settings
from django.utils import timezone
from services.models import GroupCache
from django.core.cache import cache
from hashlib import md5
logger = logging.getLogger(__name__)
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # default 2 hours
class DiscourseError(Exception):
def __init__(self, endpoint, errors):
@@ -21,12 +19,13 @@ class DiscourseError(Exception):
def __str__(self):
return "API execution failed.\nErrors: %s\nEndpoint: %s" % (self.errors, self.endpoint)
# not exhaustive, only the ones we need
ENDPOINTS = {
'groups': {
'list': {
'path': "/admin/groups.json",
'method': requests.get,
'method': 'get',
'args': {
'required': [],
'optional': [],
@@ -34,7 +33,7 @@ ENDPOINTS = {
},
'create': {
'path': "/admin/groups",
'method': requests.post,
'method': 'post',
'args': {
'required': ['name'],
'optional': ['visible'],
@@ -42,7 +41,7 @@ ENDPOINTS = {
},
'add_user': {
'path': "/admin/groups/%s/members.json",
'method': requests.put,
'method': 'put',
'args': {
'required': ['usernames'],
'optional': [],
@@ -50,7 +49,7 @@ ENDPOINTS = {
},
'remove_user': {
'path': "/admin/groups/%s/members.json",
'method': requests.delete,
'method': 'delete',
'args': {
'required': ['username'],
'optional': [],
@@ -58,7 +57,7 @@ ENDPOINTS = {
},
'delete': {
'path': "/admin/groups/%s.json",
'method': requests.delete,
'method': 'delete',
'args': {
'required': [],
'optional': [],
@@ -68,7 +67,7 @@ ENDPOINTS = {
'users': {
'create': {
'path': "/users",
'method': requests.post,
'method': 'post',
'args': {
'required': ['name', 'email', 'password', 'username'],
'optional': ['active'],
@@ -76,7 +75,7 @@ ENDPOINTS = {
},
'update': {
'path': "/users/%s.json",
'method': requests.put,
'method': 'put',
'args': {
'required': ['params'],
'optional': [],
@@ -84,7 +83,7 @@ ENDPOINTS = {
},
'get': {
'path': "/users/%s.json",
'method': requests.get,
'method': 'get',
'args': {
'required': [],
'optional': [],
@@ -92,7 +91,7 @@ ENDPOINTS = {
},
'activate': {
'path': "/admin/users/%s/activate",
'method': requests.put,
'method': 'put',
'args': {
'required': [],
'optional': [],
@@ -100,7 +99,7 @@ ENDPOINTS = {
},
'set_email': {
'path': "/users/%s/preferences/email",
'method': requests.put,
'method': 'put',
'args': {
'required': ['email'],
'optional': [],
@@ -108,7 +107,7 @@ ENDPOINTS = {
},
'suspend': {
'path': "/admin/users/%s/suspend",
'method': requests.put,
'method': 'put',
'args': {
'required': ['duration', 'reason'],
'optional': [],
@@ -116,7 +115,7 @@ ENDPOINTS = {
},
'unsuspend': {
'path': "/admin/users/%s/unsuspend",
'method': requests.put,
'method': 'put',
'args': {
'required': [],
'optional': [],
@@ -124,7 +123,7 @@ ENDPOINTS = {
},
'logout': {
'path': "/admin/users/%s/log_out",
'method': requests.post,
'method': 'post',
'args': {
'required': [],
'optional': [],
@@ -132,7 +131,7 @@ ENDPOINTS = {
},
'external': {
'path': "/users/by-external/%s.json",
'method': requests.get,
'method': 'get',
'args': {
'required': [],
'optional': [],
@@ -146,8 +145,7 @@ class DiscourseManager:
def __init__(self):
pass
GROUP_CACHE_MAX_AGE = datetime.timedelta(minutes=30)
REVOKED_EMAIL = 'revoked@' + settings.DOMAIN
REVOKED_EMAIL = 'revoked@localhost'
SUSPEND_DAYS = 99999
SUSPEND_REASON = "Disabled by auth."
@@ -171,7 +169,8 @@ class DiscourseManager:
for arg in kwargs:
if arg not in endpoint['args']['required'] and arg not in endpoint['args']['optional'] and not silent:
logger.warn("Received unrecognized kwarg %s for endpoint %s" % (arg, endpoint))
r = endpoint['method'](settings.DISCOURSE_URL + endpoint['parsed_url'], params=params, json=data)
r = getattr(requests, endpoint['method'])(settings.DISCOURSE_URL + endpoint['parsed_url'], params=params,
json=data)
try:
if 'errors' in r.json() and not silent:
logger.error("Discourse execution failed.\nEndpoint: %s\nErrors: %s" % (endpoint, r.json()['errors']))
@@ -190,67 +189,59 @@ class DiscourseManager:
return out
@staticmethod
def __generate_random_pass():
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
@staticmethod
def __get_groups():
def _get_groups():
endpoint = ENDPOINTS['groups']['list']
data = DiscourseManager.__exc(endpoint)
return [g for g in data if not g['automatic']]
@staticmethod
def __update_group_cache():
GroupCache.objects.filter(service="discourse").delete()
cache = GroupCache.objects.create(service="discourse")
cache.groups = json.dumps(DiscourseManager.__get_groups())
cache.save()
return cache
@staticmethod
def __get_group_cache():
if not GroupCache.objects.filter(service="discourse").exists():
DiscourseManager.__update_group_cache()
cache = GroupCache.objects.get(service="discourse")
age = timezone.now() - cache.created
if age > DiscourseManager.GROUP_CACHE_MAX_AGE:
logger.debug("Group cache has expired. Triggering update.")
cache = DiscourseManager.__update_group_cache()
return json.loads(cache.groups)
@staticmethod
def __create_group(name):
def _create_group(name):
endpoint = ENDPOINTS['groups']['create']
DiscourseManager.__exc(endpoint, name=name[:20], visible=True)
DiscourseManager.__update_group_cache()
return DiscourseManager.__exc(endpoint, name=name[:20], visible=True)['basic_group']
@staticmethod
def _generate_cache_group_name_key(name):
return 'DISCOURSE_GROUP_NAME__%s' % md5(name.encode('utf-8')).hexdigest()
@staticmethod
def _generate_cache_group_id_key(g_id):
return 'DISCOURSE_GROUP_ID__%s' % g_id
@staticmethod
def __group_name_to_id(name):
cache = DiscourseManager.__get_group_cache()
for g in cache:
if g['name'] == name[0:20]:
return g['id']
logger.debug("Group %s not found on Discourse. Creating" % name)
DiscourseManager.__create_group(name)
return DiscourseManager.__group_name_to_id(name)
name = DiscourseManager._sanitize_groupname(name)
def get_or_create_group():
groups = DiscourseManager._get_groups()
for g in groups:
if g['name'] == name:
return g['id']
return DiscourseManager._create_group(name)['id']
return cache.get_or_set(DiscourseManager._generate_cache_group_name_key(name), get_or_create_group,
GROUP_CACHE_MAX_AGE)
@staticmethod
def __group_id_to_name(id):
cache = DiscourseManager.__get_group_cache()
for g in cache:
if g['id'] == id:
return g['name']
raise KeyError("Group ID %s not found on Discourse" % id)
def __group_id_to_name(g_id):
def get_group_name():
groups = DiscourseManager._get_groups()
for g in groups:
if g['id'] == g_id:
return g['name']
raise KeyError("Group ID %s not found on Discourse" % g_id)
return cache.get_or_set(DiscourseManager._generate_cache_group_id_key(g_id), get_group_name,
GROUP_CACHE_MAX_AGE)
@staticmethod
def __add_user_to_group(id, username):
def __add_user_to_group(g_id, username):
endpoint = ENDPOINTS['groups']['add_user']
DiscourseManager.__exc(endpoint, id, usernames=[username])
DiscourseManager.__exc(endpoint, g_id, usernames=[username])
@staticmethod
def __remove_user_from_group(id, username):
def __remove_user_from_group(g_id, username):
endpoint = ENDPOINTS['groups']['remove_user']
DiscourseManager.__exc(endpoint, id, username=username)
DiscourseManager.__exc(endpoint, g_id, username=username)
@staticmethod
def __generate_group_dict(names):
@@ -269,10 +260,6 @@ class DiscourseManager:
data = DiscourseManager.__get_user(name, silent=silent)
return data['user']['id']
@staticmethod
def __user_id_to_name(id):
raise NotImplementedError
@staticmethod
def __get_user(username, silent=False):
endpoint = ENDPOINTS['users']['get']
@@ -281,14 +268,14 @@ class DiscourseManager:
@staticmethod
def __activate_user(username):
endpoint = ENDPOINTS['users']['activate']
id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, id)
u_id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, u_id)
@staticmethod
def __update_user(username, **kwargs):
endpoint = ENDPOINTS['users']['update']
id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, id, params=kwargs)
u_id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, u_id, params=kwargs)
@staticmethod
def __create_user(username, email, password):
@@ -300,21 +287,21 @@ class DiscourseManager:
try:
DiscourseManager.__user_name_to_id(username, silent=True)
return True
except:
except DiscourseError:
return False
@staticmethod
def __suspend_user(username):
id = DiscourseManager.__user_name_to_id(username)
u_id = DiscourseManager.__user_name_to_id(username)
endpoint = ENDPOINTS['users']['suspend']
return DiscourseManager.__exc(endpoint, id, duration=DiscourseManager.SUSPEND_DAYS,
return DiscourseManager.__exc(endpoint, u_id, duration=DiscourseManager.SUSPEND_DAYS,
reason=DiscourseManager.SUSPEND_REASON)
@staticmethod
def __unsuspend(username):
id = DiscourseManager.__user_name_to_id(username)
u_id = DiscourseManager.__user_name_to_id(username)
endpoint = ENDPOINTS['users']['unsuspend']
return DiscourseManager.__exc(endpoint, id)
return DiscourseManager.__exc(endpoint, u_id)
@staticmethod
def __set_email(username, email):
@@ -322,47 +309,53 @@ class DiscourseManager:
return DiscourseManager.__exc(endpoint, username, email=email)
@staticmethod
def __logout(id):
def __logout(u_id):
endpoint = ENDPOINTS['users']['logout']
return DiscourseManager.__exc(endpoint, id)
return DiscourseManager.__exc(endpoint, u_id)
@staticmethod
def __get_user_by_external(id):
def __get_user_by_external(u_id):
endpoint = ENDPOINTS['users']['external']
return DiscourseManager.__exc(endpoint, id)
return DiscourseManager.__exc(endpoint, u_id)
@staticmethod
def __user_id_by_external_id(id):
data = DiscourseManager.__get_user_by_external(id)
def __user_id_by_external_id(u_id):
data = DiscourseManager.__get_user_by_external(u_id)
return data['user']['id']
@staticmethod
def _sanitize_name(name):
name = name.replace(' ', '_')
name = name.replace("'", '')
name = name.lstrip(' _')
name = name[:20]
name = name.rstrip(' _')
return name
@staticmethod
def _sanitize_username(username):
sanitized = username.replace(" ", "_")
sanitized = sanitized.strip(' _')
sanitized = sanitized.replace("'", "")
return sanitized
return DiscourseManager._sanitize_name(username)
@staticmethod
def _sanitize_groupname(name):
name = name.strip(' _')
name = re.sub('[^\w]', '', name)
name = DiscourseManager._sanitize_name(name)
if len(name) < 3:
name = name + "".join('_' for i in range(3-len(name)))
return name[:20]
name = "Group " + name
return name
@staticmethod
def update_groups(user):
groups = []
for g in user.groups.all():
groups.append(DiscourseManager._sanitize_groupname(str(g)[:20]))
groups.append(DiscourseManager._sanitize_groupname(str(g)))
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
group_dict = DiscourseManager.__generate_group_dict(groups)
inv_group_dict = {v: k for k, v in group_dict.items()}
username = DiscourseManager.__get_user_by_external(user.pk)['user']['username']
user_groups = DiscourseManager.__get_user_groups(username)
add_groups = [group_dict[x] for x in group_dict if not group_dict[x] in user_groups]
rem_groups = [x for x in user_groups if not x in inv_group_dict]
rem_groups = [x for x in user_groups if x not in inv_group_dict]
if add_groups or rem_groups:
logger.info(
"Updating discourse user %s groups: adding %s, removing %s" % (username, add_groups, rem_groups))

View File

@@ -5,8 +5,6 @@ from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from notifications import notify
from services.tasks import only_one
from .manager import DiscourseManager
from .models import DiscourseUser

View File

@@ -61,7 +61,7 @@ class OpenfireManager:
@staticmethod
def _sanitize_groupname(name):
name = name.strip(' _')
name = name.strip(' _').lower()
return re.sub('[^\w.-]', '', name)
@staticmethod
@@ -120,9 +120,10 @@ class OpenfireManager:
logger.error("Unable to update openfire user %s password - user not found on server." % username)
return ""
@staticmethod
def update_user_groups(username, groups):
@classmethod
def update_user_groups(cls, username, groups):
logger.debug("Updating openfire user %s groups %s" % (username, groups))
s_groups = list(map(cls._sanitize_groupname, groups)) # Sanitized group names
api = ofUsers(settings.OPENFIRE_ADDRESS, settings.OPENFIRE_SECRET_KEY)
response = api.get_user_groups(username)
remote_groups = []
@@ -130,16 +131,15 @@ class OpenfireManager:
remote_groups = response['groupname']
if isinstance(remote_groups, six.string_types):
remote_groups = [remote_groups]
remote_groups = list(map(cls._sanitize_groupname, remote_groups))
logger.debug("Openfire user %s has groups %s" % (username, remote_groups))
add_groups = []
del_groups = []
for g in groups:
g = OpenfireManager._sanitize_groupname(g)
for g in s_groups:
if g not in remote_groups:
add_groups.append(g)
for g in remote_groups:
g = OpenfireManager._sanitize_groupname(g)
if g not in groups:
if g not in s_groups:
del_groups.append(g)
logger.info(
"Updating openfire groups for user %s - adding %s, removing %s" % (username, add_groups, del_groups))
@@ -155,10 +155,11 @@ class OpenfireManager:
api.delete_user_groups(username, groups)
logger.info("Deleted groups %s from openfire user %s" % (groups, username))
@staticmethod
def send_broadcast_message(group_name, broadcast_message):
logger.debug("Sending jabber ping to group %s with message %s" % (group_name, broadcast_message))
to_address = group_name + '@' + settings.BROADCAST_SERVICE_NAME + '.' + settings.JABBER_URL
@classmethod
def send_broadcast_message(cls, group_name, broadcast_message):
s_group_name = cls._sanitize_groupname(group_name)
logger.debug("Sending jabber ping to group %s with message %s" % (s_group_name, broadcast_message))
to_address = s_group_name + '@' + settings.BROADCAST_SERVICE_NAME + '.' + settings.JABBER_URL
xmpp = PingBot(settings.BROADCAST_USER, settings.BROADCAST_USER_PASSWORD, to_address, broadcast_message)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0199') # XMPP Ping

View File

@@ -219,3 +219,28 @@ class OpenfireManagerTestCase(TestCase):
result_username = self.manager._OpenfireManager__sanitize_username(test_username)
self.assertEqual(result_username, 'My_Test\\20User\\22\\27\\26\\2f\\3a\\3c\\3e\\40name\\5c20name')
def test__sanitize_groupname(self):
test_groupname = " My_Test Groupname"
result_groupname = self.manager._sanitize_groupname(test_groupname)
self.assertEqual(result_groupname, "my_testgroupname")
@mock.patch(MODULE_PATH + '.manager.ofUsers')
def test_update_user_groups(self, api):
groups = ["AddGroup", "othergroup", "Guest Group"]
server_groups = ["othergroup", "Guest Group", "REMOVE group"]
username = "testuser"
api_instance = api.return_value
api_instance.get_user_groups.return_value = {'groupname': server_groups}
self.manager.update_user_groups(username, groups)
self.assertTrue(api_instance.add_user_groups.called)
args, kwargs = api_instance.add_user_groups.call_args
self.assertEqual(args[1], ["addgroup"])
self.assertTrue(api_instance.delete_user_groups.called)
args, kwargs = api_instance.delete_user_groups.call_args
self.assertEqual(args[1], ["removegroup"])

View File

@@ -63,7 +63,7 @@ class SeatManager:
logger.info("Added SeAT user with username %s" % sanitized)
return sanitized, password
logger.info("Failed to add SeAT user with username %s" % sanitized)
return None
return None, None
@classmethod
def delete_user(cls, username):
@@ -75,25 +75,6 @@ class SeatManager:
return username
return None
@classmethod
def disable_user(cls, username):
""" Disable user """
ret = cls.exec_request('user/{}'.format(username), 'put', active=0)
logger.debug(ret)
ret = cls.exec_request('user/{}'.format(username), 'put', email="")
logger.debug(ret)
if cls._response_ok(ret):
try:
cls.update_roles(username, [])
logger.info("Disabled SeAT user with username %s" % username)
return username
except KeyError:
# if something goes wrong, delete user from seat instead of disabling
if cls.delete_user(username):
return username
logger.info("Failed to disabled SeAT user with username %s" % username)
return None
@classmethod
def enable_user(cls, username):
""" Enable user """
@@ -105,14 +86,22 @@ class SeatManager:
logger.info("Failed to enabled SeAT user with username %s" % username)
return None
@classmethod
def _check_email_changed(cls, username, email):
"""Compares email to one set on SeAT"""
ret = cls.exec_request('user/{}'.format(username), 'get', raise_for_status=True)
return ret['email'] != email
@classmethod
def update_user(cls, username, email, password):
""" Edit user info """
logger.debug("Updating SeAT username %s with email %s and password" % (username, email))
ret = cls.exec_request('user/{}'.format(username), 'put', email=email)
logger.debug(ret)
if not cls._response_ok(ret):
logger.warn("Failed to update email for username {}".format(username))
if cls._check_email_changed(username, email):
# if we try to set the email to whatever it is already on SeAT, we get a HTTP422 error
logger.debug("Updating SeAT username %s with email %s and password" % (username, email))
ret = cls.exec_request('user/{}'.format(username), 'put', email=email)
logger.debug(ret)
if not cls._response_ok(ret):
logger.warn("Failed to update email for username {}".format(username))
ret = cls.exec_request('user/{}'.format(username), 'put', password=password)
logger.debug(ret)
if not cls._response_ok(ret):
@@ -275,5 +264,5 @@ class SeatManager:
@staticmethod
def username_hash(username):
m = hashlib.sha1()
m.update(username)
m.update(username.encode('utf-8'))
return m.hexdigest()

View File

@@ -28,7 +28,7 @@ class SeatTasks:
@classmethod
def delete_user(cls, user, notify_user=False):
if cls.has_account(user) and SeatManager.disable_user(user.seat.username):
if cls.has_account(user) and SeatManager.delete_user(user.seat.username):
user.seat.delete()
logger.info("Successfully deactivated SeAT for user %s" % user)
if notify_user:

View File

@@ -100,10 +100,10 @@ class SeatHooksTestCase(TestCase):
# Test none user is deleted
none_user = User.objects.get(username=self.none_user)
manager.disable_user.return_value = 'abc123'
manager.delete_user.return_value = 'abc123'
SeatUser.objects.create(user=none_user, username='abc123')
service.validate_user(none_user)
self.assertTrue(manager.disable_user.called)
self.assertTrue(manager.delete_user.called)
with self.assertRaises(ObjectDoesNotExist):
none_seat = User.objects.get(username=self.none_user).seat
@@ -115,7 +115,7 @@ class SeatHooksTestCase(TestCase):
result = service.delete_user(member)
self.assertTrue(result)
self.assertTrue(manager.disable_user.called)
self.assertTrue(manager.delete_user.called)
with self.assertRaises(ObjectDoesNotExist):
seat_user = User.objects.get(username=self.member).seat
@@ -177,7 +177,7 @@ class SeatViewsTestCase(TestCase):
response = self.client.get(urls.reverse('auth_deactivate_seat'))
self.assertTrue(manager.disable_user.called)
self.assertTrue(manager.delete_user.called)
self.assertRedirects(response, expected_url=urls.reverse('auth_services'), target_status_code=200)
with self.assertRaises(ObjectDoesNotExist):
seat_user = User.objects.get(pk=self.member.pk).seat

View File

@@ -246,6 +246,10 @@ class Teamspeak3Manager:
logger.debug("Deleting user %s with id %s from TS3 server." % (user, uid))
if user:
clients = self.server.send_command('clientlist')
if isinstance(clients, dict):
# Rewrap list
clients = [clients]
for client in clients:
try:
if client['keys']['client_database_id'] == user:

View File

@@ -224,7 +224,7 @@ def srp_request_view(request, fleet_srp):
try:
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:
logger.debug("User %s Submitted Invalid Killmail Link %s or server could not be reached" % (
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)
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.kb_total_loss = ship_value
srp_request.post_time = post_time
@@ -247,8 +247,8 @@ def srp_request_view(request, fleet_srp):
else:
continue
messages.error(request,
_("%(charname)s does not belong to your Auth account. Please add the API key for this character and try again")
% {"charname": victim_name})
_("Character ID %(charid)s does not belong to your Auth account. Please add the API key for this character and try again")
% {"charid": victim_id})
return redirect("auth_srp_management_view")
else:
logger.debug("Returning blank SrpFleetUserRequestForm")

View File

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

View File

@@ -5,29 +5,33 @@
{% block title %}Apply To {{ corp.corporation_name }}{% endblock title %}
{% block page_title %}{% trans "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Apply To" %} {{ corp.corporation_name }}</h1>
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<div class="row">
<form class="form-signin">
{% csrf_token %}
{% for question in questions %}
<div class="form-group">
<label class="control-label" for="id_{{ question.pk }}">{{ question.title }}</label>
<div class=" ">
{% if question.help_text %}
<div cass="text-center">{{ question.help_text }}</div>
{% endif %}
<textarea class="form-control" cols="40" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="10"></textarea>
</div>
</div>
{% endfor %}
<button class="btn btn-lg btn-primary btn-block" type="submit" formmethod="post">{% trans "Submit" %}</button>
</form>
</div>
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Apply To" %} {{ corp.corporation_name }}</h1>
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<div class="row">
<form class="form-signin">
{% csrf_token %}
{% for question in questions %}
<div class="form-group">
<label class="control-label" for="id_{{ question.pk }}">{{ question.title }}</label>
<div class=" ">
{% if question.help_text %}
<div cass="text-center">{{ question.help_text }}</div>
{% endif %}
{% for choice in question.choices.all %}
<input type="radio" name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% empty %}
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea>
{% endfor %}
</div>
</div>
{% endfor %}
<button class="btn btn-lg btn-primary btn-block" type="submit" formmethod="post">{% trans "Submit" %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,10 +0,0 @@
[program:auth-celerybeat]
command=celery -A alliance_auth beat
directory=/home/allianceserver/allianceauth
user=allianceserver
stdout_logfile=/home/allianceserver/allianceauth/log/beat.log
stderr_logfile=/home/allianceserver/allianceauth/log/beat.log
autostart=true
autorestart=true
startsecs=10
priority=999

View File

@@ -1,13 +0,0 @@
[program:auth-celeryd]
command=celery -A alliance_auth worker
directory=/home/allianceserver/allianceauth
user=allianceserver
numprocs=1
stdout_logfile=/home/allianceserver/allianceauth/log/worker.log
stderr_logfile=/home/allianceserver/allianceauth/log/worker.log
autostart=true
autorestart=true
startsecs=10
stopwaitsecs = 600
killasgroup=true
priority=1000

28
thirdparty/Supervisor/auth.conf vendored Normal file
View File

@@ -0,0 +1,28 @@
[program:celerybeat]
command=celery -A alliance_auth beat
directory=/home/allianceserver/allianceauth
user=allianceserver
stdout_logfile=/home/allianceserver/allianceauth/log/beat.log
stderr_logfile=/home/allianceserver/allianceauth/log/beat.log
autostart=true
autorestart=true
startsecs=10
priority=998
[program:celeryd]
command=celery -A alliance_auth worker
directory=/home/allianceserver/allianceauth
user=allianceserver
numprocs=1
stdout_logfile=/home/allianceserver/allianceauth/log/worker.log
stderr_logfile=/home/allianceserver/allianceauth/log/worker.log
autostart=true
autorestart=true
startsecs=10
stopwaitsecs = 600
killasgroup=true
priority=998
[group:auth]
programs=celerybeat,celeryd
priority=999