Compare commits

..

21 Commits

Author SHA1 Message Date
Adarnof
0d64441538 Version bump to v1.15.6 2017-11-17 16:22:03 -05:00
Adarnof
58a333c67a Case-insensitive group name to ID translation
Seems Discourse won't let you create `Group` if `group` already exists (`422 Name has already been taken`).

Thanks @huberfe
2017-11-17 13:07:41 -05:00
Adarnof
6837f94e59 Disable SeAT accounts instead of deleting. (#915)
See eveseat/web@1abb402
2017-11-03 19:20:31 -04:00
phaynu
16987fcaf0 Extending Choices for Questions in hrapplications to Allow Multiselect (#911)
An additional field at the question level defines whether the choices for the question are multi-select or not. The template will render the choices with radio buttons or checkboxes depending on multi-select. Multiple selected choices are saved with a line break between them.
2017-10-25 00:35:19 -04:00
Derptron
ebd3be3f46 Documentation update (#850)
* Update to the Dependency in regard to using SeAT
* Update to the installation of SSL-Certificates with Discourse
* CleanUp of some missing information in the discourse section
2017-10-05 13:15:34 +10:00
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
45 changed files with 338 additions and 179 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.6'
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

@@ -25,6 +25,17 @@ Required for displaying web content
apache2 libapache2-mod-php5 libapache2-mod-wsgi apache2 libapache2-mod-php5 libapache2-mod-wsgi
### PHP ### PHP
```eval_rst
.. note::
If you are not planing to install either phpBB, smf, evernus alliance market, etc do not install these modules.
```
```eval_rst
.. important::
If you are not planing to use SeAT; php7.0 is the minimum required.
```
Required for phpBB, smf, evernus alliance market, etc Required for phpBB, smf, evernus alliance market, etc
php5 php5-gd php5-mysqlnd php5-curl php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-ming php5-ps php5-pspell php5-recode php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl php5 php5-gd php5-mysqlnd php5-curl php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-ming php5-ps php5-pspell php5-recode php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl

View File

@@ -65,6 +65,11 @@ Now build:
sudo ./launcher bootstrap app sudo ./launcher bootstrap app
sudo ./launcher start app sudo ./launcher start app
#### Errors:
in case you run into not enough RAM for the docker bootstraping you might want to consider using `./discourse-setup` command. It will start bootstraping and is going to create the `/containers/app.yml` which you can edit.
Note: every time you change something in the `app.yml` you must bootstrap again which will take between *2-8 minutes* and is accomplished by `./launcher rebuild app`.
***
## Apache config ## Apache config
Discourse must run on its own subdomain - it can't handle routing behind an alias like '/forums'. To do so, make a new apache config: Discourse must run on its own subdomain - it can't handle routing behind an alias like '/forums'. To do so, make a new apache config:
@@ -81,9 +86,59 @@ And enter the following, changing the port if you used a different number:
Now enable proxies and restart apache: Now enable proxies and restart apache:
sudo a2ensite discourse
sudo a2enmod proxy_http sudo a2enmod proxy_http
sudo service apache2 reload sudo service apache2 reload
### Setting up SSL
It is 2017 and there is no reason why you should not setup a SSL certificate and enforce https. You may want to consider certbot with Let's encrypt: https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04
sudo certbot --apache -d example.com
now adapt the apache configuration:
sudo nano /etc/apache2/sites-enabled/discourse.conf
and adapt it followlingly:
<VirtualHost *:80>
ServerName discourse.example.com
RewriteEngine on
RewriteCond %{SERVER_NAME} =discourse.example.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Then adapt change the ssl-config file:
sudo nano /etc/apache2/sites-enabled/discourse-le-ssl.conf
and adapt it followlingly:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName discourse.example.com
ProxyPass / http://127.0.0.1:7890/
ProxyPassReverse / http://127.0.0.1:7890/
ProxyPreserveHost On
RequestHeader set X-FORWARDED-PROTOCOL https
RequestHeader set X-FORWARDED-SSL on
SSLCertificateFile /etc/letsencrypt/live/discourse.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/discourse.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
make sure that `a2enmod headers` is enabled and run:
sudo service apache2 restart
Now you are all set-up and can even enforce https in discourse settings.
## Configure API ## Configure API
### Generate admin account ### Generate admin account
@@ -108,6 +163,7 @@ Scroll down to the Discourse section and set the following:
- `DISCOURSE_API_USERNAME`: the username of the admin account you generated the API key with - `DISCOURSE_API_USERNAME`: the username of the admin account you generated the API key with
- `DISCOURSE_API_KEY`: the key you just generated - `DISCOURSE_API_KEY`: the key you just generated
***
### Configure SSO ### Configure SSO
Navigate to `discourse.example.com` and log in. Back to the admin site, scroll down to find SSO settings and set the following: Navigate to `discourse.example.com` and log in. Back to the admin site, scroll down to find SSO settings and set the following:
@@ -120,6 +176,7 @@ Save, now change settings.py and add the following:
### Enable for your members ### Enable for your members
Set either or both of `ENABLE_AUTH_DISCOURSE` and `ENABLE_BLUE_DISCOURSE` in settings.py for your members to gain access. Save and exit with control+o, enter, control+x. Assign discourse permissions for each auth-group that should have access to discourse.
You might want to setup Read/Write/Delete rights per Auth group in discourse as you can limit which categories shall be accessablie per auth-group.
## Done ## Done

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

@@ -16,7 +16,7 @@ class ChoiceInline(admin.TabularInline):
class QuestionAdmin(admin.ModelAdmin): class QuestionAdmin(admin.ModelAdmin):
fieldsets = [ fieldsets = [
(None, {'fields': ['title', 'help_text']}), (None, {'fields': ['title', 'help_text', 'multi_select']}),
] ]
inlines = [ChoiceInline] inlines = [ChoiceInline]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2017-10-20 13:51
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrapplications', '0002_choices_for_questions'),
]
operations = [
migrations.AddField(
model_name='applicationquestion',
name='multi_select',
field=models.BooleanField(default=False),
),
]

View File

@@ -13,6 +13,7 @@ from authentication.models import AuthServicesInfo
class ApplicationQuestion(models.Model): class ApplicationQuestion(models.Model):
title = models.CharField(max_length=254, verbose_name='Question') title = models.CharField(max_length=254, verbose_name='Question')
help_text = models.CharField(max_length=254, blank=True, null=True) help_text = models.CharField(max_length=254, blank=True, null=True)
multi_select = models.BooleanField(default=False)
def __str__(self): def __str__(self):
return "Question: " + self.title return "Question: " + self.title

View File

@@ -71,8 +71,8 @@ def hr_application_create_view(request, form_id=None):
application.save() application.save()
for question in app_form.questions.all(): for question in app_form.questions.all():
response = ApplicationResponse(question=question, application=application) response = ApplicationResponse(question=question, application=application)
response.answer = request.POST.get(str(question.pk), response.answer = "\n".join(request.POST.getlist(str(question.pk),
"Failed to retrieve answer provided by applicant.") ""))
response.save() response.save()
logger.info("%s created %s" % (request.user, application)) logger.info("%s created %s" % (request.user, application))
return redirect('auth_hrapplications_view') return redirect('auth_hrapplications_view')

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

@@ -214,7 +214,7 @@ class DiscourseManager:
def get_or_create_group(): def get_or_create_group():
groups = DiscourseManager._get_groups() groups = DiscourseManager._get_groups()
for g in groups: for g in groups:
if g['name'] == name: if g['name'].lower() == name.lower():
return g['id'] return g['id']
return DiscourseManager._create_group(name)['id'] return DiscourseManager._create_group(name)['id']

View File

@@ -78,7 +78,7 @@ class SeatManager:
@classmethod @classmethod
def enable_user(cls, username): def enable_user(cls, username):
""" Enable user """ """ Enable user """
ret = cls.exec_request('user/{}'.format(username), 'put', active=1) ret = cls.exec_request('user/{}'.format(username), 'put', account_status=1)
logger.debug(ret) logger.debug(ret)
if cls._response_ok(ret): if cls._response_ok(ret):
logger.info("Enabled SeAT user with username %s" % username) logger.info("Enabled SeAT user with username %s" % username)
@@ -86,6 +86,18 @@ class SeatManager:
logger.info("Failed to enabled SeAT user with username %s" % username) logger.info("Failed to enabled SeAT user with username %s" % username)
return None return None
@classmethod
def disable_user(cls, username):
""" Disable user """
cls.update_roles(username, [])
ret = cls.exec_request('user/{}'.format(username), 'put', account_status=0)
logger.debug(ret)
if cls._response_ok(ret):
logger.info("Disabled SeAT user with username %s" % username)
return username
logger.info("Failed to disable SeAT user with username %s" % username)
return None
@classmethod @classmethod
def _check_email_changed(cls, username, email): def _check_email_changed(cls, username, email):
"""Compares email to one set on SeAT""" """Compares email to one set on SeAT"""
@@ -264,5 +276,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

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

View File

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

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>

View File

@@ -20,7 +20,7 @@
<div cass="text-center">{{ question.help_text }}</div> <div cass="text-center">{{ question.help_text }}</div>
{% endif %} {% endif %}
{% for choice in question.choices.all %} {% for choice in question.choices.all %}
<input type="radio" name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}" /> <input type={% if question.multi_select == False %}"radio"{% else %}"checkbox"{% endif %} name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% empty %} {% empty %}
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea> <textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea>