Merge branch 'v2.9.x' into 'master'

Bring up Master to 2.9.0

See merge request allianceauth/allianceauth!1344
This commit is contained in:
Ariel Rin 2021-10-18 01:58:24 +00:00
commit eddb5480e9
400 changed files with 9098 additions and 5481 deletions

24
.editorconfig Normal file
View File

@ -0,0 +1,24 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{yaml,yml,less}]
indent_size = 2
[*.md]
indent_size = 2
# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab
[*.bat]
indent_style = tab

View File

@ -1,4 +1,5 @@
stages:
- pre-commit
- gitlab
- test
- deploy
@ -13,6 +14,18 @@ before_script:
- python -V
- pip install wheel tox
pre-commit-check:
stage: pre-commit
image: python:3.6-buster
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
paths:
- ${PRE_COMMIT_HOME}
script:
- pip install pre-commit
- pre-commit run --all-files
sast:
stage: gitlab
before_script: []
@ -20,23 +33,14 @@ sast:
dependency_scanning:
stage: gitlab
before_script:
- apt-get update && apt-get install redis-server libmariadbclient-dev -y
- apt-get update && apt-get install redis-server libmariadb-dev -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
test-3.6-core:
image: python:3.6-buster
script:
- tox -e py36-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.7-core:
image: python:3.7-buster
script:
image: python:3.7-bullseye
script:
- tox -e py37-core
artifacts:
when: always
@ -44,26 +48,26 @@ test-3.7-core:
cobertura: coverage.xml
test-3.8-core:
image: python:3.8-buster
script:
image: python:3.8-bullseye
script:
- tox -e py38-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.6-all:
image: python:3.6-buster
script:
- tox -e py36-all
test-3.9-core:
image: python:3.9-bullseye
script:
- tox -e py39-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.7-all:
image: python:3.7-buster
script:
image: python:3.7-bullseye
script:
- tox -e py37-all
artifacts:
when: always
@ -71,17 +75,26 @@ test-3.7-all:
cobertura: coverage.xml
test-3.8-all:
image: python:3.8-buster
script:
image: python:3.8-bullseye
script:
- tox -e py38-all
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.9-all:
image: python:3.9-bullseye
script:
- tox -e py39-all
artifacts:
when: always
reports:
cobertura: coverage.xml
deploy_production:
stage: deploy
image: python:3.8-buster
image: python:3.9-bullseye
before_script:
- pip install twine wheel
@ -91,4 +104,4 @@ deploy_production:
- twine upload dist/*
rules:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_TAG

View File

@ -1,8 +1,8 @@
# Bug
- I have searched [issues](https://gitlab.com/allianceauth/allianceauth/issues?scope=all&utf8=%E2%9C%93&state=all) (Y/N):
- What Version of Alliance Auth:
- What Operating System:
- What Version of Alliance Auth:
- What Operating System:
- Version of other components relevant to issue eg. Service, Database:
Please include a brief description of your issue here.
@ -11,4 +11,4 @@ Please include steps to reproduce the issue
Please include any tracebacks or logs
Please include the results of the command `pip list`
Please include the results of the command `pip list`

View File

@ -4,4 +4,4 @@
- Is this a Service (external integration), a Module (Alliance Auth extension) or an enhancement to an existing service/module.
- Describe why its useful to you or others.
- Describe why its useful to you or others.

28
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,28 @@
# Apply to all files without committing:
# pre-commit run --all-files
# Update this file:
# pre-commit autoupdate
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: fix-byte-order-marker
- id: trailing-whitespace
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
- id: end-of-file-fixer
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
- id: mixed-line-ending
args: [ '--fix=lf' ]
- id: fix-encoding-pragma
args: [ '--remove' ]
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.3.5
hooks:
- id: editorconfig-checker
exclude: ^(LICENSE|allianceauth\/static\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))

View File

@ -20,4 +20,4 @@ formats: all
python:
version: 3.7
install:
- requirements: docs/requirements.txt
- requirements: docs/requirements.txt

View File

@ -337,4 +337,3 @@ proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -1,7 +1,7 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '2.8.7'
__version__ = '2.9.0'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = '%s v%s' % (__title__, __version__)

View File

@ -0,0 +1 @@
default_app_config = 'allianceauth.analytics.apps.AnalyticsConfig'

View File

@ -0,0 +1,21 @@
from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
@admin.register(AnalyticsIdentifier)
class AnalyticsIdentifierAdmin(admin.ModelAdmin):
search_fields = ['identifier', ]
list_display = ('identifier',)
@admin.register(AnalyticsTokens)
class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ]
list_display = ('name', 'type',)
@admin.register(AnalyticsPath)
class AnalyticsPathAdmin(admin.ModelAdmin):
search_fields = ['ignore_path', ]
list_display = ('ignore_path',)

View File

@ -0,0 +1,9 @@
from django.apps import AppConfig
class AnalyticsConfig(AppConfig):
name = 'allianceauth.analytics'
label = 'analytics'
def ready(self):
import allianceauth.analytics.signals

View File

@ -0,0 +1,21 @@
[
{
"model": "analytics.AnalyticsTokens",
"pk": 1,
"fields": {
"name": "AA Team Public Google Analytics (Universal)",
"type": "GA-V4",
"token": "UA-186249766-2",
"send_page_views": "False",
"send_celery_tasks": "False",
"send_stats": "False"
}
},
{
"model": "analytics.AnalyticsIdentifier",
"pk": 1,
"fields": {
"identifier": "ab33e241fbf042b6aa77c7655a768af7"
}
}
]

View File

@ -0,0 +1,49 @@
from bs4 import BeautifulSoup
from django.utils.deprecation import MiddlewareMixin
from .models import AnalyticsTokens, AnalyticsIdentifier
from .tasks import send_ga_tracking_web_view
import re
class AnalyticsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
"""Django Middleware: Process Page Views and creates Analytics Celery Tasks"""
analyticstokens = AnalyticsTokens.objects.all()
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
try:
title = BeautifulSoup(
response.content, "html.parser").html.head.title.text
except AttributeError:
title = ''
for token in analyticstokens:
# Check if Page View Sending is Disabled
if token.send_page_views is False:
continue
# Check Exclusions
ignore = False
for ignore_path in token.ignore_paths.values():
ignore_path_regex = re.compile(ignore_path["ignore_path"])
if re.search(ignore_path_regex, request.path) is not None:
ignore = True
if ignore is True:
continue
tracking_id = token.token
locale = request.LANGUAGE_CODE
path = request.path
try:
useragent = request.headers["User-Agent"]
except KeyError:
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
send_ga_tracking_web_view.s(tracking_id=tracking_id,
client_id=client_id,
page=path,
title=title,
locale=locale,
useragent=useragent).\
apply_async(priority=9)
return response

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.4 on 2020-12-30 13:11
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AnalyticsIdentifier',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('identifier', models.UUIDField(default=uuid.uuid4, editable=False)),
],
),
migrations.CreateModel(
name='AnalyticsPath',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ignore_path', models.CharField(default='/example/', max_length=254)),
],
),
migrations.CreateModel(
name='AnalyticsTokens',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=254)),
('type', models.CharField(choices=[('GA-U', 'Google Analytics Universal'), ('GA-V4', 'Google Analytics V4')], max_length=254)),
('token', models.CharField(max_length=254)),
('send_page_views', models.BooleanField(default=False)),
('send_celery_tasks', models.BooleanField(default=False)),
('send_stats', models.BooleanField(default=False)),
('ignore_paths', models.ManyToManyField(blank=True, to='analytics.AnalyticsPath')),
],
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 3.1.4 on 2020-12-30 08:53
from django.db import migrations
def add_aa_team_token(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens()
token.type = 'GA-U'
token.token = 'UA-186249766-2'
token.send_page_views = True
token.send_celery_tasks = True
token.send_stats = True
token.name = 'AA Team Public Google Analytics (Universal)'
token.save()
def remove_aa_team_token(apps, schema_editor):
# Have to define some code to remove this identifier
# In case of migration rollback?
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.filter(token="UA-186249766-2").delete()
class Migration(migrations.Migration):
dependencies = [
('analytics', '0001_initial'),
]
operations = [migrations.RunPython(add_aa_team_token, remove_aa_team_token)
]

View File

@ -0,0 +1,30 @@
# Generated by Django 3.1.4 on 2020-12-30 08:53
from uuid import uuid4
from django.db import migrations
def generate_identifier(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Identifier = apps.get_model('analytics', 'AnalyticsIdentifier')
identifier = Identifier()
identifier.id = 1
identifier.save()
def zero_identifier(apps, schema_editor):
# Have to define some code to remove this identifier
# In case of migration rollback?
Identifier = apps.get_model('analytics', 'AnalyticsIdentifier')
Identifier.objects.filter(id=1).delete()
class Migration(migrations.Migration):
dependencies = [
('analytics', '0002_add_AA_Team_Token'),
]
operations = [migrations.RunPython(generate_identifier, zero_identifier)
]

View File

@ -0,0 +1,31 @@
# Generated by Django 3.1.13 on 2021-10-15 05:02
from django.db import migrations
def modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
user_notifications_count = AnalyticsPath.objects.create(ignore_path=r"^\/user_notifications_count\/.*")
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.get(token="UA-186249766-2")
token.ignore_paths.add(admin, user_notifications_count)
def undo_modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
# nothing should need to migrate away here?
return True
class Migration(migrations.Migration):
dependencies = [
('analytics', '0003_Generate_Identifier'),
]
operations = [migrations.RunPython(modify_aa_team_token_add_page_ignore_paths, undo_modify_aa_team_token_add_page_ignore_paths)
]

View File

@ -0,0 +1,38 @@
from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from uuid import uuid4
class AnalyticsIdentifier(models.Model):
identifier = models.UUIDField(default=uuid4,
editable=False)
def save(self, *args, **kwargs):
if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object
raise ValidationError('There is can be only one \
AnalyticsIdentifier instance')
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super(AnalyticsIdentifier, self).save(*args, **kwargs)
class AnalyticsPath(models.Model):
ignore_path = models.CharField(max_length=254, default="/example/", help_text="Regex Expression, If matched no Analytics Page View is sent")
class AnalyticsTokens(models.Model):
class Analytics_Type(models.TextChoices):
GA_U = 'GA-U', _('Google Analytics Universal')
GA_V4 = 'GA-V4', _('Google Analytics V4')
name = models.CharField(max_length=254)
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
token = models.CharField(max_length=254, blank=False)
send_page_views = models.BooleanField(default=False)
send_celery_tasks = models.BooleanField(default=False)
send_stats = models.BooleanField(default=False)
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)

View File

@ -0,0 +1,50 @@
from allianceauth.analytics.tasks import analytics_event
from celery.signals import task_failure, task_success
import logging
logger = logging.getLogger(__name__)
@task_failure.connect
def process_failure_signal(
exception, traceback,
sender, task_id, signal,
args, kwargs, einfo, **kw):
logger.debug("Celery task_failure signal %s" % sender.__class__.__name__)
category = sender.__module__
if 'allianceauth.analytics' not in category:
if category.endswith(".tasks"):
category = category[:-6]
action = sender.__name__
label = f"{exception.__class__.__name__}"
analytics_event(category=category,
action=action,
label=label)
@task_success.connect
def celery_success_signal(sender, result=None, **kw):
logger.debug("Celery task_success signal %s" % sender.__class__.__name__)
category = sender.__module__
if 'allianceauth.analytics' not in category:
if category.endswith(".tasks"):
category = category[:-6]
action = sender.__name__
label = "Success"
value = 0
if isinstance(result, int):
value = result
analytics_event(category=category,
action=action,
label=label,
value=value)

View File

@ -0,0 +1,207 @@
import requests
import logging
from django.conf import settings
from django.apps import apps
from celery import shared_task
from allianceauth import __version__
from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import (
install_stat_addons,
install_stat_tokens,
install_stat_users)
logger = logging.getLogger(__name__)
BASE_URL = "https://www.google-analytics.com/"
DEBUG_URL = f"{BASE_URL}debug/collect"
COLLECTION_URL = f"{BASE_URL}collect"
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
# Force sending of analytics data during in a debug/test environemt
# Usefull for developers working on this feature.
logger.warning(
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
"This debug instance will send analytics data!")
DEBUG_URL = COLLECTION_URL
ANALYTICS_URL = COLLECTION_URL
if settings.DEBUG is True:
ANALYTICS_URL = DEBUG_URL
def analytics_event(category: str,
action: str,
label: str,
value: int = 0,
event_type: str = 'Celery'):
"""
Send a Google Analytics Event for each token stored
Includes check for if its enabled/disabled
Parameters
-------
`category` (str): Celery Namespace
`action` (str): Task Name
`label` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a binary True/False
`event_type` (str): Optional, Celery or Stats only, Default to Celery
"""
analyticstokens = AnalyticsTokens.objects.all()
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
for token in analyticstokens:
if event_type == 'Celery':
allowed = token.send_celery_tasks
elif event_type == 'Stats':
allowed = token.send_stats
else:
allowed = False
if allowed is True:
tracking_id = token.token
send_ga_tracking_celery_event.s(tracking_id=tracking_id,
client_id=client_id,
category=category,
action=action,
label=label,
value=value).\
apply_async(priority=9)
@shared_task()
def analytics_daily_stats():
"""Celery Task: Do not call directly
Gathers a series of daily statistics and sends analytics events containing them"""
users = install_stat_users()
tokens = install_stat_tokens()
addons = install_stat_addons()
logger.debug("Running Daily Analytics Upload")
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='existence',
value=1,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='users',
value=users,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='tokens',
value=tokens,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
label='addons',
value=addons,
event_type='Stats')
for appconfig in apps.get_app_configs():
analytics_event(category='allianceauth.analytics',
action='send_extension_stats',
label=appconfig.label,
value=1,
event_type='Stats')
@shared_task()
def send_ga_tracking_web_view(
tracking_id: str,
client_id: str,
page: str,
title: str,
locale: str,
useragent: str) -> requests.Response:
"""Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware
Parameters
----------
`tracking_id` (str): Unique Server Identifier
`client_id` (str): GA Token
`page` (str): Page Path
`title` (str): Page Title
`locale` (str): Browser Language
`useragent` (str): Browser UserAgent
Returns
-------
requests.Reponse Object
"""
headers = {"User-Agent": useragent}
payload = {
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'pageview',
'dp': page,
'dt': title,
'ul': locale,
'ua': useragent,
'aip': 1,
'an': "allianceauth",
'av': __version__
}
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Page View HTTP{response.status_code}")
return response
@shared_task()
def send_ga_tracking_celery_event(
tracking_id: str,
client_id: str,
category: str,
action: str,
label: str,
value: int) -> requests.Response:
"""Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware
Parameters
----------
`tracking_id` (str): Unique Server Identifier
`client_id` (str): GA Token
`category` (str): Celery Namespace
`action` (str): Task Name
`label` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a binary True/False
Returns
-------
requests.Reponse Object
"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
payload = {
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'event',
'ec': category,
'ea': action,
'el': label,
'ev': value,
'aip': 1,
'an': "allianceauth",
'av': __version__
}
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
return response

View File

View File

@ -0,0 +1,23 @@
from allianceauth.analytics.middleware import AnalyticsMiddleware
from unittest.mock import Mock
from django.test.testcases import TestCase
class TestAnalyticsMiddleware(TestCase):
def setUp(self):
self.middleware = AnalyticsMiddleware()
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/testURL/'
self.request.session = {}
self.request.LANGUAGE_CODE = 'en'
self.response = Mock()
self.response.content = 'hello world'
def test_middleware(self):
response = self.middleware.process_response(self.request, self.response)
self.assertEqual(self.response, response)

View File

@ -0,0 +1,26 @@
from allianceauth.analytics.models import AnalyticsIdentifier
from django.core.exceptions import ValidationError
from django.test.testcases import TestCase
from uuid import UUID, uuid4
# Identifiers
uuid_1 = "ab33e241fbf042b6aa77c7655a768af7"
uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
class TestAnalyticsIdentifier(TestCase):
def test_identifier_random(self):
self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4)
def test_identifier_singular(self):
AnalyticsIdentifier.objects.all().delete()
AnalyticsIdentifier.objects.create(identifier=uuid_1)
# Yeah i have multiple asserts here, they all do the same thing
with self.assertRaises(ValidationError):
AnalyticsIdentifier.objects.create(identifier=uuid_2)
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
self.assertEqual(AnalyticsIdentifier.objects.get(pk=1).identifier, UUID(uuid_1))

View File

@ -0,0 +1,119 @@
from allianceauth.analytics.tasks import (
analytics_event,
send_ga_tracking_celery_event,
send_ga_tracking_web_view)
from django.test.testcases import TestCase
class TestAnalyticsTasks(TestCase):
def test_analytics_event(self):
analytics_event(
category='allianceauth.analytics',
action='send_tests',
label='test',
value=1,
event_type='Stats')
def test_send_ga_tracking_web_view_sent(self):
# This test sends if the event SENDS to google
# Not if it was successful
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent)
self.assertEqual(response.status_code, 200)
def test_send_ga_tracking_web_view_success(self):
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
json_response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent).json()
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
def test_send_ga_tracking_web_view_invalid_token(self):
tracking_id = 'UA-IntentionallyBadTrackingID-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
json_response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent).json()
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
self.assertEqual(json_response["hitParsingResult"][0]["parserMessage"][1]["description"], "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.")
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
def test_send_ga_tracking_celery_event_sent(self):
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value)
self.assertEqual(response.status_code, 200)
def test_send_ga_tracking_celery_event_success(self):
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
json_response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value).json()
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
def test_send_ga_tracking_celery_event_invalid_token(self):
tracking_id = 'UA-IntentionallyBadTrackingID-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
json_response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value).json()
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
self.assertEqual(json_response["hitParsingResult"][0]["parserMessage"][1]["description"], "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.")
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]

View File

@ -0,0 +1,55 @@
from django.apps import apps
from allianceauth.authentication.models import User
from esi.models import Token
from allianceauth.analytics.utils import install_stat_users, install_stat_tokens, install_stat_addons
from django.test.testcases import TestCase
def create_testdata():
User.objects.all().delete()
User.objects.create_user(
'user_1'
'abc@example.com',
'password'
)
User.objects.create_user(
'user_2'
'abc@example.com',
'password'
)
#Token.objects.all().delete()
#Token.objects.create(
# character_id=101,
# character_name='character1',
# access_token='my_access_token'
#)
#Token.objects.create(
# character_id=102,
# character_name='character2',
# access_token='my_access_token'
#)
class TestAnalyticsUtils(TestCase):
def test_install_stat_users(self):
create_testdata()
expected = 2
users = install_stat_users()
self.assertEqual(users, expected)
#def test_install_stat_tokens(self):
# create_testdata()
# expected = 2
#
# tokens = install_stat_tokens()
# self.assertEqual(tokens, expected)
def test_install_stat_addons(self):
# this test does what its testing...
# but helpful for existing as a sanity check
expected = len(list(apps.get_app_configs()))
addons = install_stat_addons()
self.assertEqual(addons, expected)

View File

@ -0,0 +1,36 @@
from django.apps import apps
from allianceauth.authentication.models import User
from esi.models import Token
def install_stat_users() -> int:
"""Count and Return the number of User accounts
Returns
-------
int
The Number of User objects"""
users = User.objects.count()
return users
def install_stat_tokens() -> int:
"""Count and Return the number of ESI Tokens Stored
Returns
-------
int
The Number of Token Objects"""
tokens = Token.objects.count()
return tokens
def install_stat_addons() -> int:
"""Count and Return the number of Django Applications Installed
Returns
-------
int
The Number of Installed Apps"""
addons = len(list(apps.get_app_configs()))
return addons

View File

@ -84,7 +84,7 @@ class UserProfileInline(admin.StackedInline):
if request.user.is_superuser:
query |= Q(userprofile__isnull=True)
else:
query |= Q(character_ownership__user=obj)
query |= Q(character_ownership__user=obj)
formset = super().get_formset(request, obj=obj, **kwargs)
def get_kwargs(self, index):
@ -123,26 +123,26 @@ def user_username(obj):
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
"""
link = reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
),
args=(obj.pk,)
)
user_obj = obj.user if hasattr(obj, 'user') else obj
if user_obj.profile.main_character:
return format_html(
'<strong><a href="{}">{}</a></strong><br>{}',
link,
link,
user_obj.username,
user_obj.profile.main_character.character_name
)
else:
return format_html(
'<strong><a href="{}">{}</a></strong>',
link,
link,
user_obj.username,
)
@ -160,16 +160,16 @@ def user_main_organization(obj):
user_obj = obj.user if hasattr(obj, 'user') else obj
if not user_obj.profile.main_character:
result = None
else:
else:
corporation = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id:
if user_obj.profile.main_character.alliance_id:
result = format_html(
'{}<br>{}',
corporation,
corporation,
user_obj.profile.main_character.alliance_name
)
else:
result = corporation
result = corporation
return result
@ -200,7 +200,7 @@ class MainCorporationsFilter(admin.SimpleListFilter):
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
else:
if qs.model == User:
return qs.filter(
profile__main_character__corporation_id=self.value()
@ -209,7 +209,7 @@ class MainCorporationsFilter(admin.SimpleListFilter):
return qs.filter(
user__profile__main_character__corporation_id=self.value()
)
class MainAllianceFilter(admin.SimpleListFilter):
"""Custom filter to filter on alliances from mains only
@ -234,16 +234,16 @@ class MainAllianceFilter(admin.SimpleListFilter):
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
else:
if qs.model == User:
return qs.filter(profile__main_character__alliance_id=self.value())
return qs.filter(profile__main_character__alliance_id=self.value())
else:
return qs.filter(
user__profile__main_character__alliance_id=self.value()
)
def update_main_character_model(modeladmin, request, queryset):
def update_main_character_model(modeladmin, request, queryset):
tasks_count = 0
for obj in queryset:
if obj.profile.main_character:
@ -251,7 +251,7 @@ def update_main_character_model(modeladmin, request, queryset):
tasks_count += 1
modeladmin.message_user(
request,
request,
'Update from ESI started for {} characters'.format(tasks_count)
)
@ -262,7 +262,7 @@ update_main_character_model.short_description = \
class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model
Behavior of groups and characters columns can be configured via settings
"""
@ -270,7 +270,7 @@ class UserAdmin(BaseUserAdmin):
css = {
"all": ("authentication/css/admin.css",)
}
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.prefetch_related("character_ownerships__character", "groups")
@ -279,8 +279,8 @@ class UserAdmin(BaseUserAdmin):
actions = super(BaseUserAdmin, self).get_actions(request)
actions[update_main_character_model.__name__] = (
update_main_character_model,
update_main_character_model.__name__,
update_main_character_model,
update_main_character_model.__name__,
update_main_character_model.short_description
)
@ -290,21 +290,21 @@ class UserAdmin(BaseUserAdmin):
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
action = make_service_hooks_update_groups_action(svc)
actions[action.__name__] = (
action,
action,
action.__name__,
action.short_description
)
# Create sync nickname action if service implements it
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
action = make_service_hooks_sync_nickname_action(svc)
actions[action.__name__] = (
action, action.__name__,
action, action.__name__,
action.short_description
)
return actions
def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str:
def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str:
"""converts list of strings into HTML with cutoff and tooltip"""
items_truncated_str = ', '.join(my_items[:max_items])
if not my_items:
@ -320,27 +320,27 @@ class UserAdmin(BaseUserAdmin):
items_truncated_str
)
return result
inlines = BaseUserAdmin.inlines + [UserProfileInline]
ordering = ('username', )
ordering = ('username', )
list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True
show_full_result_count = True
list_display = (
user_profile_pic,
user_username,
'_state',
user_username,
'_state',
'_groups',
user_main_organization,
'_characters',
'is_active',
'date_joined',
'_role'
)
)
list_display_links = None
list_filter = (
list_filter = (
'profile__state',
'groups',
MainCorporationsFilter,
'groups',
MainCorporationsFilter,
MainAllianceFilter,
'is_active',
'date_joined',
@ -348,32 +348,32 @@ class UserAdmin(BaseUserAdmin):
'is_superuser'
)
search_fields = (
'username',
'username',
'character_ownerships__character__character_name'
)
def _characters(self, obj):
character_ownerships = list(obj.character_ownerships.all())
characters = [obj.character.character_name for obj in character_ownerships]
return self._list_2_html_w_tooltips(
sorted(characters),
sorted(characters),
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
)
_characters.short_description = 'characters'
def _state(self, obj):
return obj.profile.state.name
_state.short_description = 'state'
_state.admin_order_field = 'profile__state'
def _groups(self, obj):
def _groups(self, obj):
my_groups = sorted([group.name for group in list(obj.groups.all())])
return self._list_2_html_w_tooltips(
my_groups, AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
)
_groups.short_description = 'groups'
def _role(self, obj):
@ -382,11 +382,11 @@ class UserAdmin(BaseUserAdmin):
elif obj.is_staff:
role = 'Staff'
else:
role = 'User'
role = 'User'
return role
_role.short_description = 'role'
def has_change_permission(self, request, obj=None):
return request.user.has_perm('auth.change_user')
@ -399,15 +399,15 @@ class UserAdmin(BaseUserAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""overriding this formfield to have sorted lists in the form"""
if db_field.name == "groups":
kwargs["queryset"] = Group.objects.all().order_by(Lower('name'))
kwargs["queryset"] = Group.objects.all().order_by(Lower('name'))
return super().formfield_for_manytomany(db_field, request, **kwargs)
@admin.register(State)
class StateAdmin(admin.ModelAdmin):
class StateAdmin(admin.ModelAdmin):
list_select_related = True
list_display = ('name', 'priority', '_user_count')
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(user_count=Count("userprofile__id"))
@ -423,22 +423,22 @@ class StateAdmin(admin.ModelAdmin):
}),
('Membership', {
'fields': (
'public',
'member_characters',
'member_corporations',
'public',
'member_characters',
'member_corporations',
'member_alliances'
),
})
)
filter_horizontal = [
'member_characters',
'member_corporations',
'member_alliances',
'member_characters',
'member_corporations',
'member_alliances',
'permissions'
]
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""overriding this formfield to have sorted lists in the form"""
"""overriding this formfield to have sorted lists in the form"""
if db_field.name == "member_characters":
kwargs["queryset"] = EveCharacter.objects.all()\
.order_by(Lower('character_name'))
@ -451,7 +451,7 @@ class StateAdmin(admin.ModelAdmin):
elif db_field.name == "permissions":
kwargs["queryset"] = Permission.objects.select_related("content_type").all()
return super().formfield_for_manytomany(db_field, request, **kwargs)
def has_delete_permission(self, request, obj=None):
if obj == get_guest_state():
return False
@ -465,16 +465,16 @@ class StateAdmin(admin.ModelAdmin):
}),
)
return super(StateAdmin, self).get_fieldsets(request, obj=obj)
class BaseOwnershipAdmin(admin.ModelAdmin):
class Media:
css = {
"all": ("authentication/css/admin.css",)
}
list_select_related = (
'user__profile__state', 'user__profile__main_character', 'character')
'user__profile__state', 'user__profile__main_character', 'character')
list_display = (
user_profile_pic,
user_username,
@ -482,13 +482,13 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
'character',
)
search_fields = (
'user__username',
'character__character_name',
'character__corporation_name',
'user__username',
'character__character_name',
'character__corporation_name',
'character__alliance_name'
)
list_filter = (
MainCorporationsFilter,
list_filter = (
MainCorporationsFilter,
MainAllianceFilter,
)

View File

@ -2,14 +2,14 @@ from django.conf import settings
def _clean_setting(
name: str,
default_value: object,
name: str,
default_value: object,
min_value: int = None,
max_value: int = None,
required_type: type = None
):
"""cleans the input for a custom setting
Will use `default_value` if settings does not exit or has the wrong type
or is outside define boundaries (for int only)
@ -18,22 +18,22 @@ def _clean_setting(
Will assume `min_value` of 0 for int (can be overriden)
Returns cleaned value for setting
"""
"""
if default_value is None and not required_type:
raise ValueError('You must specify a required_type for None defaults')
if not required_type:
required_type = type(default_value)
if min_value is None and required_type == int:
min_value = 0
min_value = 0
if (hasattr(settings, name)
and isinstance(getattr(settings, name), required_type)
and (min_value is None or getattr(settings, name) >= min_value)
and (max_value is None or getattr(settings, name) <= max_value)
):
return getattr(settings, name)
):
return getattr(settings, name)
else:
return default_value
@ -43,4 +43,3 @@ AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)

View File

@ -12,9 +12,9 @@ logger = logging.getLogger(__name__)
class StateBackend(ModelBackend):
@staticmethod
def _get_state_permissions(user_obj):
"""returns permissions for state of given user object"""
"""returns permissions for state of given user object"""
if hasattr(user_obj, "profile") and user_obj.profile:
return Permission.objects.filter(state=user_obj.profile.state)
return Permission.objects.filter(state=user_obj.profile.state)
else:
return Permission.objects.none()

View File

@ -11,4 +11,4 @@ urlpatterns = [
url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
url(r'', include('django.contrib.auth.urls')),
]
]

View File

@ -23,8 +23,7 @@ class CharacterOwnershipManager(Manager):
def create_by_token(self, token):
if not EveCharacter.objects.filter(character_id=token.character_id).exists():
EveCharacter.objects.create_character(token.character_id)
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user,
owner_hash=token.character_owner_hash)
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user, owner_hash=token.character_owner_hash)
class StateQuerySet(QuerySet):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:38
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-07 19:14
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 20:29
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:11
from __future__ import unicode_literals
@ -17,7 +16,7 @@ def create_permissions(apps, schema_editor):
Permission = apps.get_model('auth', 'Permission')
ct = ContentType.objects.get_for_model(User)
Permission.objects.get_or_create(codename="member", content_type=ct, name="member")
Permission.objects.get_or_create(codename="blue_member", content_type=ct, name="blue_member")
Permission.objects.get_or_create(codename="blue_member", content_type=ct, name="blue_member")
class Migration(migrations.Migration):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 05:42
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 21:50
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-12 13:04
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-21 02:28
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 06:47
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 07:11
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-12 00:59
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-11 23:14
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals
@ -107,8 +106,8 @@ def populate_ownerships(apps, schema_editor):
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
unique_character_owners = [t['character_id'] for t in
Token.objects.all().values('character_id').annotate(n=models.Count('user')) if
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id']).exists()]
Token.objects.all().values('character_id').annotate(n=models.Count('user')) if
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id']).exists()]
tokens = Token.objects.filter(character_id__in=unique_character_owners)
for c_id in unique_character_owners:
@ -171,8 +170,7 @@ def recreate_authservicesinfo(apps, schema_editor):
# repopulate main characters
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
AuthServicesInfo.objects.update_or_create(user=profile.user,
defaults={'main_char_id': profile.main_character.character_id})
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'main_char_id': profile.main_character.character_id})
# repopulate states we understand
for profile in UserProfile.objects.exclude(state__name='Guest').filter(

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations

View File

@ -14,15 +14,11 @@ logger = logging.getLogger(__name__)
class State(models.Model):
name = models.CharField(max_length=20, unique=True)
permissions = models.ManyToManyField(Permission, blank=True)
priority = models.IntegerField(unique=True,
help_text="Users get assigned the state with the highest priority available to them.")
priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.")
member_characters = models.ManyToManyField(EveCharacter, blank=True,
help_text="Characters to which this state is available.")
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True,
help_text="Corporations to whose members this state is available.")
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True,
help_text="Alliances to whose members this state is available.")
member_characters = models.ManyToManyField(EveCharacter, blank=True, help_text="Characters to which this state is available.")
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True, help_text="Corporations to whose members this state is available.")
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, help_text="Alliances to whose members this state is available.")
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
objects = StateManager()
@ -74,13 +70,18 @@ class UserProfile(models.Model):
logger.info('Updating {} state to {}'.format(self.user, self.state))
self.save(update_fields=['state'])
notify(
self.user,
self.user,
_('State changed to: %s' % state),
_('Your user\'s state is now: %(state)s')
% ({'state': state}),
'info'
)
from allianceauth.authentication.signals import state_changed
# We need to ensure we get up to date perms here as they will have just changed.
# Clear all attribute caches and reload the model that will get passed to the signals!
self.refresh_from_db()
state_changed.send(
sender=self.__class__, user=self.user, state=self.state
)
@ -114,4 +115,4 @@ class OwnershipRecord(models.Model):
ordering = ['-created']
def __str__(self):
return "%s: %s on %s" % (self.user, self.character, self.created)
return "%s: %s on %s" % (self.user, self.character, self.created)

View File

@ -75,8 +75,7 @@ def create_required_models(sender, instance, created, *args, **kwargs):
@receiver(post_save, sender=Token)
def record_character_ownership(sender, instance, created, *args, **kwargs):
if created:
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
instance.character_name))
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user, instance.character_name))
if instance.user:
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
else:
@ -85,18 +84,14 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
# create character if needed
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name,
instance.character_id))
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name, instance.character_id))
EveCharacter.objects.create_character(instance.character_id)
char = EveCharacter.objects.get(character_id=instance.character_id)
# check if we need to create ownership
if instance.user and not CharacterOwnership.objects.filter(
character__character_id=instance.character_id).exists():
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name,
instance.user))
CharacterOwnership.objects.update_or_create(character=char,
defaults={'owner_hash': instance.character_owner_hash,
'user': instance.user})
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name, instance.user))
CharacterOwnership.objects.update_or_create(character=char, defaults={'owner_hash': instance.character_owner_hash, 'user': instance.user})
@receiver(pre_delete, sender=CharacterOwnership)
@ -156,4 +151,4 @@ def ownership_record_creation(sender, instance, created, *args, **kwargs):
logger.debug("Already have ownership record of {0} by user {1}".format(instance.character, instance.user))
return
logger.info("Character {0} has a new owner {1}. Creating ownership record.".format(instance.character, instance.user))
OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)
OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)

View File

@ -1,12 +1,12 @@
/*
CSS for allianceauth admin site
/*
CSS for allianceauth admin site
*/
/* styling for profile pic */
.img-circle {
border-radius: 50%;
.img-circle {
border-radius: 50%;
}
.column-user_profile_pic {
.column-user_profile_pic {
width: 1px;
white-space: nowrap;
}
@ -26,4 +26,4 @@ CSS for allianceauth admin site
color: black ;
background-color: rgb(255, 255, 204) ;
z-index: 1 ;
}
}

View File

@ -63,7 +63,7 @@
{% endif %}
</div>
</div>
<div class="table visible-xs-block">
<div class="table visible-xs-block">
<p>
<img class="ra-avatar" src="{{ main.portrait_url_64 }}">
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
@ -74,7 +74,7 @@
{{ main.corporation_name }}<br>
{{ main.alliance_name }}
</p>
</div>
</div>
{% endwith %}
{% else %}
<div class="alert alert-danger" role="alert">
@ -121,7 +121,7 @@
{% trans 'Characters' %}
</h3>
</div>
<div class="panel-body">
<div class="panel-body">
<table class="table table-aa hidden-xs">
<thead>
<tr>
@ -132,20 +132,20 @@
</tr>
</thead>
<tbody>
{% for char in characters %}
{% for char in characters %}
<tr>
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td>
</tr>
</tr>
{% endfor %}
</tbody>
</table>
<table class="table table-aa visible-xs-block" style="width: 100%">
<tbody>
{% for char in characters %}
{% for char in characters %}
<tr>
<td class="text-center" style="vertical-align: middle">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
@ -154,7 +154,7 @@
<strong>{{ char.character_name }}</strong><br>
{{ char.corporation_name }}<br>
{{ char.alliance_name|default:"" }}
</td>
</td>
</tr>
{% endfor %}
</tbody>

View File

@ -1,8 +1,8 @@
{% extends 'public/middle_box.html' %}
{% load static %}
{% block page_title %}Login{% endblock %}
{% block middle_box_content %}
{% block middle_box_content %}
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
<img class="img-responsive center-block" src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
</a>
{% endblock %}
{% endblock %}

View File

@ -21,4 +21,4 @@
{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}
{% endblock %}

View File

@ -10,4 +10,4 @@ Link not working? Try copy/pasting this URL into your browser:
This link will expire in {{ expiration_days }} day(s).
If this was not you, it is safe to ignore this email.
If this was not you, it is safe to ignore this email.

View File

@ -1 +1 @@
Confirm your Alliance Auth account email address
Confirm your Alliance Auth account email address

View File

@ -16,15 +16,15 @@ from allianceauth.tests.auth_utils import AuthUtils
from ..admin import (
BaseUserAdmin,
CharacterOwnershipAdmin,
CharacterOwnershipAdmin,
StateAdmin,
MainCorporationsFilter,
MainAllianceFilter,
OwnershipRecordAdmin,
User,
UserAdmin,
UserAdmin,
user_main_organization,
user_profile_pic,
user_profile_pic,
user_username,
update_main_character_model,
make_service_hooks_update_groups_action,
@ -36,7 +36,7 @@ from . import get_admin_change_view_url, get_admin_search_url
MODULE_PATH = 'allianceauth.authentication.admin'
class MockRequest(object):
class MockRequest(object):
def __init__(self, user=None):
self.user = user
@ -51,7 +51,7 @@ class TestCaseWithTestData(TestCase):
EveAllianceInfo, EveCorporationInfo, EveCharacter, Group, User
]:
MyModel.objects.all().delete()
# groups
cls.group_1 = Group.objects.create(
name='Group 1'
@ -84,16 +84,16 @@ class TestCaseWithTestData(TestCase):
alliance = EveAllianceInfo.objects.create(
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
alliance_ticker='WE',
executor_corp_id=2001
)
EveCorporationInfo.objects.create(
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
corporation_ticker='WT',
member_count=42,
alliance=alliance
)
)
cls.user_1 = User.objects.create_user(
character_1.character_name.replace(' ', '_'),
'abc@example.com',
@ -111,7 +111,7 @@ class TestCaseWithTestData(TestCase):
)
cls.user_1.profile.main_character = character_1
cls.user_1.profile.save()
cls.user_1.groups.add(cls.group_1)
cls.user_1.groups.add(cls.group_1)
# user 2 - corp only, staff
character_2 = EveCharacter.objects.create(
@ -125,7 +125,7 @@ class TestCaseWithTestData(TestCase):
EveCorporationInfo.objects.create(
corporation_id=2002,
corporation_name='Daily Plane',
corporation_ticker='DP',
corporation_ticker='DP',
member_count=99,
alliance=None
)
@ -144,7 +144,7 @@ class TestCaseWithTestData(TestCase):
cls.user_2.groups.add(cls.group_2)
cls.user_2.is_staff = True
cls.user_2.save()
# user 3 - no main, no group, superuser
character_3 = EveCharacter.objects.create(
character_id=1101,
@ -157,7 +157,7 @@ class TestCaseWithTestData(TestCase):
EveCorporationInfo.objects.create(
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
corporation_ticker='LC',
member_count=666,
alliance=None
)
@ -186,25 +186,25 @@ def make_generic_search_request(ModelClass: type, search_term: str):
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
c.login(username='superuser', password='secret')
return c.get(
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
)
)
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = CharacterOwnershipAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
c.login(username='superuser', password='secret')
ownership = self.user_1.character_ownerships.first()
response = c.get(get_admin_change_view_url(ownership))
self.assertEqual(response.status_code, 200)
@ -219,18 +219,18 @@ class TestCharacterOwnershipAdmin(TestCaseWithTestData):
class TestOwnershipRecordAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = OwnershipRecordAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
c.login(username='superuser', password='secret')
ownership_record = OwnershipRecord.objects\
.filter(user=self.user_1)\
.first()
@ -245,23 +245,23 @@ class TestOwnershipRecordAdmin(TestCaseWithTestData):
class TestStateAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = StateAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
c.login(username='superuser', password='secret')
guest_state = AuthUtils.get_guest_state()
response = c.get(get_admin_change_view_url(guest_state))
self.assertEqual(response.status_code, 200)
member_state = AuthUtils.get_member_state()
response = c.get(get_admin_change_view_url(member_state))
self.assertEqual(response.status_code, 200)
@ -281,12 +281,12 @@ class TestUserAdmin(TestCaseWithTestData):
model=User, admin_site=AdminSite()
)
self.character_1 = self.user_1.character_ownerships.first().character
def test_user_profile_pic_u1(self):
expected = (
'<img src="https://images.evetech.net/characters/1001/'
'portrait?size=32" class="img-circle">'
)
)
self.assertEqual(user_profile_pic(self.user_1), expected)
def test_user_profile_pic_u3(self):
@ -332,18 +332,18 @@ class TestUserAdmin(TestCaseWithTestData):
expected = 'Lex Luthor'
result = self.modeladmin._characters(self.user_3)
self.assertEqual(result, expected)
def test_groups_u1(self):
def test_groups_u1(self):
expected = 'Group 1'
result = self.modeladmin._groups(self.user_1)
self.assertEqual(result, expected)
def test_groups_u2(self):
def test_groups_u2(self):
expected = 'Group 2'
result = self.modeladmin._groups(self.user_2)
self.assertEqual(result, expected)
def test_groups_u3(self):
def test_groups_u3(self):
result = self.modeladmin._groups(self.user_3)
self.assertIsNone(result)
@ -387,7 +387,7 @@ class TestUserAdmin(TestCaseWithTestData):
expected = None
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
self.assertEqual(expected, result)
# actions
@patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True)
@ -401,14 +401,14 @@ class TestUserAdmin(TestCaseWithTestData):
)
self.assertEqual(mock_task.delay.call_count, 2)
self.assertTrue(mock_message_user.called)
# filters
def test_filter_main_corporations(self):
class UserAdminTest(BaseUserAdmin):
class UserAdminTest(BaseUserAdmin):
list_filter = (MainCorporationsFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
@ -417,7 +417,7 @@ class TestUserAdmin(TestCaseWithTestData):
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
expected = [
(2002, 'Daily Planet'),
(2001, 'Wayne Technologies'),
]
@ -425,20 +425,20 @@ class TestUserAdmin(TestCaseWithTestData):
# Make sure the correct queryset is returned
request = self.factory.get(
'/',
'/',
{'main_corporation_id__exact': self.character_1.corporation_id}
)
request.user = self.user_1
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_1]
self.assertSetEqual(set(queryset), set(expected))
def test_filter_main_alliances(self):
class UserAdminTest(BaseUserAdmin):
class UserAdminTest(BaseUserAdmin):
list_filter = (MainAllianceFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
@ -447,17 +447,17 @@ class TestUserAdmin(TestCaseWithTestData):
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(3001, 'Wayne Enterprises'),
expected = [
(3001, 'Wayne Enterprises'),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get(
'/',
'/',
{'main_alliance_id__exact': self.character_1.alliance_id}
)
request.user = self.user_1
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_1]
@ -468,7 +468,7 @@ class TestUserAdmin(TestCaseWithTestData):
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
c.login(username='superuser', password='secret')
response = c.get(get_admin_change_view_url(self.user_1))
self.assertEqual(response.status_code, 200)
@ -485,8 +485,8 @@ class TestMakeServicesHooksActions(TestCaseWithTestData):
def __init__(self):
super().__init__()
self.name = 'My Service A'
self.name = 'My Service A'
def update_groups(self, user):
pass
@ -498,7 +498,7 @@ class TestMakeServicesHooksActions(TestCaseWithTestData):
def __init__(self):
super().__init__()
self.name = 'My Service B'
def update_groups(self, user):
pass
@ -510,32 +510,32 @@ class TestMakeServicesHooksActions(TestCaseWithTestData):
def sync_nicknames_bulk(self, user):
pass
def test_service_has_update_groups_only(self):
def test_service_has_update_groups_only(self):
service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service)
mock_service = MagicMock(spec=service)
action = make_service_hooks_update_groups_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertTrue(mock_service.update_groups.called)
def test_service_has_update_groups_bulk(self):
def test_service_has_update_groups_bulk(self):
service = self.MyServicesHookTypeB()
mock_service = MagicMock(spec=service)
mock_service = MagicMock(spec=service)
action = make_service_hooks_update_groups_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertFalse(mock_service.update_groups.called)
self.assertTrue(mock_service.update_groups_bulk.called)
def test_service_has_sync_nickname_only(self):
def test_service_has_sync_nickname_only(self):
service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service)
mock_service = MagicMock(spec=service)
action = make_service_hooks_sync_nickname_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertTrue(mock_service.sync_nickname.called)
def test_service_has_sync_nicknames_bulk(self):
def test_service_has_sync_nicknames_bulk(self):
service = self.MyServicesHookTypeB()
mock_service = MagicMock(spec=service)
mock_service = MagicMock(spec=service)
action = make_service_hooks_sync_nickname_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertFalse(mock_service.sync_nickname.called)

View File

@ -9,19 +9,19 @@ MODULE_PATH = 'allianceauth.authentication'
class TestSetAppSetting(TestCase):
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_not_set(self, mock_settings):
def test_default_if_not_set(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
False,
'TEST_SETTING_DUMMY',
False,
)
self.assertEqual(result, False)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_not_set_for_none(self, mock_settings):
def test_default_if_not_set_for_none(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
None,
required_type=int
)
@ -31,8 +31,8 @@ class TestSetAppSetting(TestCase):
def test_true_stays_true(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = True
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
False,
'TEST_SETTING_DUMMY',
False,
)
self.assertEqual(result, True)
@ -40,7 +40,7 @@ class TestSetAppSetting(TestCase):
def test_false_stays_false(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = False
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
False
)
self.assertEqual(result, False)
@ -49,7 +49,7 @@ class TestSetAppSetting(TestCase):
def test_default_for_invalid_type_bool(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
False
)
self.assertEqual(result, False)
@ -58,7 +58,7 @@ class TestSetAppSetting(TestCase):
def test_default_for_invalid_type_int(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
50
)
self.assertEqual(result, 50)
@ -67,7 +67,7 @@ class TestSetAppSetting(TestCase):
def test_default_if_below_minimum_1(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = -5
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=50
)
self.assertEqual(result, 50)
@ -76,7 +76,7 @@ class TestSetAppSetting(TestCase):
def test_default_if_below_minimum_2(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = -50
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=50,
min_value=-10
)
@ -86,7 +86,7 @@ class TestSetAppSetting(TestCase):
def test_default_for_invalid_type_int(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 1000
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=50,
max_value=100
)
@ -97,6 +97,6 @@ class TestSetAppSetting(TestCase):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
with self.assertRaises(ValueError):
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=None
)

View File

@ -23,27 +23,27 @@ class TestStatePermissions(TestCase):
self.permission_2 = AuthUtils.get_permission_by_name(PERMISSION_2)
# group
self.group_1 = Group.objects.create(name="Group 1")
self.group_1 = Group.objects.create(name="Group 1")
self.group_2 = Group.objects.create(name="Group 2")
# state
self.state_1 = AuthUtils.get_member_state()
self.state_1 = AuthUtils.get_member_state()
self.state_2 = AuthUtils.create_state("Other State", 75)
# user
self.user = AuthUtils.create_user("Bruce Wayne")
self.main = AuthUtils.add_main_character_2(self.user, self.user.username, 123)
def test_user_has_user_permissions(self):
self.user.user_permissions.add(self.permission_1)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
def test_user_has_group_permissions(self):
def test_user_has_group_permissions(self):
self.group_1.permissions.add(self.permission_1)
self.user.groups.add(self.group_1)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
@ -55,7 +55,7 @@ class TestStatePermissions(TestCase):
self.assertTrue(user.has_perm(PERMISSION_1))
def test_when_user_changes_state_perms_change_accordingly(self):
self.state_1.permissions.add(self.permission_1)
self.state_1.permissions.add(self.permission_1)
self.state_1.member_characters.add(self.main)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
@ -68,16 +68,16 @@ class TestStatePermissions(TestCase):
self.assertTrue(user.has_perm(PERMISSION_2))
def test_state_permissions_are_returned_for_current_user_object(self):
# verify state permissions are returns for the current user object
# and not for it's instance in the database, which might be outdated
# verify state permissions are returns for the current user object
# and not for it's instance in the database, which might be outdated
self.state_1.permissions.add(self.permission_1)
self.state_2.permissions.add(self.permission_2)
self.state_2.permissions.add(self.permission_2)
self.state_1.member_characters.add(self.main)
user = User.objects.get(pk=self.user.pk)
user.profile.state = self.state_2
self.assertFalse(user.has_perm(PERMISSION_1))
self.assertTrue(user.has_perm(PERMISSION_2))
class TestAuthenticate(TestCase):
@classmethod
@ -138,7 +138,7 @@ class TestAuthenticate(TestCase):
def test_iterate_username(self):
t = Token(character_id=self.unclaimed_character.character_id,
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
username = StateBackend().authenticate(token=t).username
t.character_owner_hash = '4'
username_1 = StateBackend().authenticate(token=t).username

View File

@ -80,11 +80,11 @@ class StateTestCase(TestCase):
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
corp_name='Test Corp', alliance_name='Test Alliance')
corp_name='Test Corp', alliance_name='Test Alliance')
cls.guest_state = get_guest_state()
cls.test_character = EveCharacter.objects.get(character_id='1')
cls.test_corporation = EveCorporationInfo.objects.create(corporation_id='1', corporation_name='Test Corp',
corporation_ticker='TEST', member_count=1)
corporation_ticker='TEST', member_count=1)
cls.test_alliance = EveAllianceInfo.objects.create(alliance_id='1', alliance_name='Test Alliance',
alliance_ticker='TEST', executor_corp_id='1')
cls.member_state = State.objects.create(
@ -215,7 +215,7 @@ class CharacterOwnershipCheckTestCase(TestCase):
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
corp_name='Test Corp', alliance_name='Test Alliance')
corp_name='Test Corp', alliance_name='Test Alliance')
cls.character = EveCharacter.objects.get(character_id=1)
cls.token = Token.objects.create(
user=cls.user,

View File

@ -10,9 +10,9 @@ from django.test import TestCase
from allianceauth.templatetags.admin_status import (
status_overview,
_fetch_list_from_gitlab,
_current_notifications,
_current_version_summary,
_fetch_notification_issues_from_gitlab,
_current_notifications,
_current_version_summary,
_fetch_notification_issues_from_gitlab,
_latests_versions
)
@ -58,10 +58,10 @@ class TestStatusOverviewTag(TestCase):
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status._fetch_celery_queue_length')
@patch(MODULE_PATH + '.admin_status._current_version_summary')
@patch(MODULE_PATH + '.admin_status._current_notifications')
@patch(MODULE_PATH + '.admin_status._current_notifications')
def test_status_overview(
self,
mock_current_notifications,
self,
mock_current_notifications,
mock_current_version_info,
mock_fetch_celery_queue_length
):
@ -82,7 +82,7 @@ class TestStatusOverviewTag(TestCase):
}
mock_current_version_info.return_value = version_info
mock_fetch_celery_queue_length.return_value = 3
result = status_overview()
expected = {
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
@ -111,7 +111,7 @@ class TestNotifications(TestCase):
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement'
)
)
requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES)
# when
result = _fetch_notification_issues_from_gitlab()
@ -127,13 +127,13 @@ class TestNotifications(TestCase):
# then
self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5])
@requests_mock.mock()
@requests_mock.mock()
def test_current_notifications_failed(self, requests_mocker):
# given
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement'
)
)
requests_mocker.get(url, status_code=404)
# when
result = _current_notifications()
@ -163,7 +163,7 @@ class TestVersionTags(TestCase):
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_normal(self, mock_cache):
def test_current_version_info_normal(self, mock_cache):
# given
mock_cache.get_or_set.return_value = GITHUB_TAGS
# when
@ -184,7 +184,7 @@ class TestVersionTags(TestCase):
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags'
)
)
requests_mocker.get(url, status_code=500)
# when
result = _current_version_summary()
@ -197,7 +197,7 @@ class TestVersionTags(TestCase):
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags'
)
)
requests_mocker.get(url, json=GITHUB_TAGS)
# when
result = _current_version_summary()
@ -208,7 +208,7 @@ class TestVersionTags(TestCase):
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_return_no_data(self, mock_cache):
# given
mock_cache.get_or_set.return_value = None
mock_cache.get_or_set.return_value = None
# when
result = _current_version_summary()
# then
@ -218,7 +218,7 @@ class TestVersionTags(TestCase):
class TestLatestsVersion(TestCase):
def test_all_version_types_defined(self):
tags = create_tags_list(
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
)
@ -229,7 +229,7 @@ class TestLatestsVersion(TestCase):
self.assertEqual(beta, Pep440Version('2.1.1a1'))
def test_major_and_minor_not_defined_with_zero(self):
tags = create_tags_list(
['2.1.2', '2.1.1', '2.0.1', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
)
@ -238,9 +238,9 @@ class TestLatestsVersion(TestCase):
self.assertEqual(minor, Pep440Version('2.1.1'))
self.assertEqual(patch, Pep440Version('2.1.2'))
self.assertEqual(beta, Pep440Version('2.1.1a1'))
def test_can_ignore_invalid_versions(self):
tags = create_tags_list(
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', 'invalid']
)
@ -252,9 +252,9 @@ class TestLatestsVersion(TestCase):
class TestFetchListFromGitlab(TestCase):
page_size = 2
def setUp(self):
self.url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
@ -266,8 +266,8 @@ class TestFetchListFromGitlab(TestCase):
page = int(request.qs['page'][0])
start = (page - 1) * cls.page_size
end = start + cls.page_size
return GITHUB_TAGS[start:end]
return GITHUB_TAGS[start:end]
@requests_mock.mock()
def test_can_fetch_one_page_with_header(self, requests_mocker):
headers = {
@ -279,7 +279,7 @@ class TestFetchListFromGitlab(TestCase):
self.assertEqual(requests_mocker.call_count, 1)
@requests_mock.mock()
def test_can_fetch_one_page_wo_header(self, requests_mocker):
def test_can_fetch_one_page_wo_header(self, requests_mocker):
requests_mocker.get(self.url, json=GITHUB_TAGS)
result = _fetch_list_from_gitlab(self.url)
self.assertEqual(result, GITHUB_TAGS)
@ -296,7 +296,7 @@ class TestFetchListFromGitlab(TestCase):
self.assertEqual(requests_mocker.call_count, 1)
@requests_mock.mock()
def test_can_fetch_multiple_pages(self, requests_mocker):
def test_can_fetch_multiple_pages(self, requests_mocker):
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
headers = {
'x-total-pages': str(total_pages)
@ -307,7 +307,7 @@ class TestFetchListFromGitlab(TestCase):
self.assertEqual(requests_mocker.call_count, total_pages)
@requests_mock.mock()
def test_can_fetch_given_number_of_pages_only(self, requests_mocker):
def test_can_fetch_given_number_of_pages_only(self, requests_mocker):
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
headers = {
'x-total-pages': str(total_pages)

View File

@ -9,8 +9,8 @@ app_name = 'authentication'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(
r'^account/login/$',
TemplateView.as_view(template_name='public/login.html'),
r'^account/login/$',
TemplateView.as_view(template_name='public/login.html'),
name='login'
),
url(
@ -19,9 +19,9 @@ urlpatterns = [
name='change_main_character'
),
url(
r'^account/characters/add/$',
views.add_character,
r'^account/characters/add/$',
views.add_character,
name='add_character'
),
),
url(r'^dashboard/$', views.dashboard, name='dashboard'),
]

View File

@ -16,8 +16,8 @@ from esi.decorators import token_required
from esi.models import Token
from django_registration.backends.activation.views import (
RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView,
RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView,
REGISTRATION_SALT
)
from django_registration.signals import user_registered
@ -52,7 +52,7 @@ def dashboard(request):
.filter(character_ownership__user=request.user)\
.select_related()\
.order_by('character_name')
context = {
'groups': groups,
'characters': characters
@ -71,7 +71,7 @@ def main_character_change(request, token):
co = CharacterOwnership.objects.create_by_token(token)
else:
messages.error(
request,
request,
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
)
co = None
@ -138,7 +138,7 @@ class RegistrationView(BaseRegistrationView):
template_name = "public/register.html"
email_body_template = "registration/activation_email.txt"
email_subject_template = "registration/activation_email_subject.txt"
success_url = reverse_lazy('registration_complete')
success_url = reverse_lazy('registration_complete')
def get_success_url(self, user):
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
@ -181,12 +181,12 @@ class RegistrationView(BaseRegistrationView):
# Step 3
class ActivationView(BaseActivationView):
template_name = "registration/activate.html"
success_url = reverse_lazy('registration_activation_complete')
success_url = reverse_lazy('registration_activation_complete')
def validate_key(self, activation_key):
try:
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
return dump
except signing.BadSignature:
return None

View File

@ -3,4 +3,4 @@ from django.contrib import admin
from .models import CorpStats, CorpMember
admin.site.register(CorpStats)
admin.site.register(CorpMember)
admin.site.register(CorpMember)

View File

@ -6,11 +6,13 @@ from allianceauth.corputils import urls
class CorpStats(MenuItemHook):
def __init__(self):
MenuItemHook.__init__(self,
_('Corporation Stats'),
'fas fa-share-alt fa-fw',
'corputils:view',
navactive=['corputils:'])
MenuItemHook.__init__(
self,
_('Corporation Stats'),
'fas fa-share-alt fa-fw',
'corputils:view',
navactive=['corputils:']
)
def render(self, request):
if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm(

View File

@ -37,7 +37,7 @@ class CorpStatsQuerySet(models.QuerySet):
return self.filter(query)
except AssertionError:
logger.debug('User %s has no main character. No corpstats visible.' % user)
return self.none()
return self.none()
class CorpStatsManager(models.Manager):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:36
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:48
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:35
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-26 20:13
from __future__ import unicode_literals
@ -13,8 +12,7 @@ def convert_json_to_members(apps, schema_editor):
for cs in CorpStats.objects.all():
members = json.loads(cs._members)
CorpMember.objects.bulk_create(
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in
members.items()]
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in members.items()]
)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-10 15:34
from __future__ import unicode_literals

View File

@ -6,8 +6,7 @@ from bravado.exception import HTTPForbidden
from django.db import models
from esi.errors import TokenError
from esi.models import Token
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter,\
EveAllianceInfo
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter, EveAllianceInfo
from allianceauth.notifications import notify
from allianceauth.corputils.managers import CorpStatsManager
@ -49,8 +48,7 @@ class CorpStats(models.Model):
def update(self):
try:
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)
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()['corporation_id'] == int(self.corp.corporation_id)
member_ids = c.Corporation.get_corporations_corporation_id_members(
corporation_id=self.corp.corporation_id).result()
@ -58,18 +56,15 @@ 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)]
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in
member_id_chunks]
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in member_id_chunks]
member_list = {}
for name_chunk in member_name_chunks:
member_list.update({m['id']: m['name'] for m in name_chunk})
# bulk create new member models
missing_members = [m_id for m_id in member_ids if
not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
missing_members = [m_id for m_id in member_ids if not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
CorpMember.objects.bulk_create(
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in
missing_members])
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in missing_members])
# purge old members
self.members.exclude(character_id__in=member_ids).delete()
@ -80,21 +75,22 @@ class CorpStats(models.Model):
except TokenError as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self,
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
level="error")
notify(
self.token.user, "%s failed to update with your ESI token." % self,
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
level="error")
self.delete()
except HTTPForbidden as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self,
message="%s: %s" % (e.status_code, e.message), level="error")
notify(self.token.user, "%s failed to update with your ESI token." % self, message="%s: %s" % (e.status_code, e.message), level="error")
self.delete()
except AssertionError:
logger.warning("%s token character no longer in corp." % self)
if self.token.user:
notify(self.token.user, "%s cannot update with your ESI token." % self,
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
notify(
self.token.user, "%s cannot update with your ESI token." % self,
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
self.delete()
@property
@ -127,9 +123,7 @@ class CorpStats(models.Model):
@property
def mains(self):
return self.members.filter(pk__in=[m.pk for m in self.members.all() if
m.main_character and int(m.main_character.character_id) == int(
m.character_id)])
return self.members.filter(pk__in=[m.pk for m in self.members.all() if m.main_character and int(m.main_character.character_id) == int(m.character_id)])
def visible_to(self, user):
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()

File diff suppressed because one or more lines are too long

View File

@ -34,4 +34,4 @@
{% block member_data %}{% endblock %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -58,8 +58,7 @@
{% for id, main in mains.items %}
<tr>
<td class="text-center" style="vertical-align:middle">
<div class="thumbnail"
style="border: 0 none; box-shadow: none; background: transparent;">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ main.main.portrait_url_64 }}" class="img-circle">
<div class="caption text-center">
{{ main.main }}
@ -88,8 +87,7 @@
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/"
class="label label-danger" target="_blank">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
{% trans "Killboard" %}
</a>
</td>
@ -123,10 +121,9 @@
<tr>
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member }}</td>
<td class="text-center"><a
href="https://zkillboard.com/character/{{ member.character_id }}/"
class="label label-danger"
target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
@ -136,10 +133,9 @@
<tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center"><a
href="https://zkillboard.com/character/{{ member.character_id }}/"
class="label label-danger"
target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
</td>
<td class="text-center"></td>
<td class="text-center"></td>
<td class="text-center"></td>
@ -167,9 +163,7 @@
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/"
class="label label-danger"
target="_blank">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
{% trans "Killboard" %}
</a>
</td>

View File

@ -45,4 +45,4 @@
$(document).ready(function(){
$('#table-search').DataTable();
});
{% endblock %}
{% endblock %}

View File

@ -96,7 +96,7 @@ def corpstats_view(request, corp_id=None):
character_ownership__user__profile__main_character__corporation_id=corpstats.corp.corporation_id)
linked_chars = linked_chars.select_related('character_ownership',
'character_ownership__user__profile__main_character') \
'character_ownership__user__profile__main_character') \
.prefetch_related('character_ownership__user__character_ownerships') \
.prefetch_related('character_ownership__user__character_ownerships__character')

View File

@ -97,7 +97,7 @@ class EveAllianceForm(EveEntityForm):
@admin.register(EveCorporationInfo)
class EveCorporationInfoAdmin(admin.ModelAdmin):
search_fields = ['corporation_name']
list_display = ('corporation_name', 'alliance')
list_display = ('corporation_name', 'alliance')
list_select_related = ('alliance',)
list_filter = (('alliance', admin.RelatedOnlyFieldListFilter),)
ordering = ('corporation_name',)
@ -114,9 +114,9 @@ class EveCorporationInfoAdmin(admin.ModelAdmin):
@admin.register(EveAllianceInfo)
class EveAllianceInfoAdmin(admin.ModelAdmin):
search_fields = ['alliance_name']
list_display = ('alliance_name',)
list_display = ('alliance_name',)
ordering = ('alliance_name',)
def has_change_permission(self, request, obj=None):
return False
@ -129,9 +129,9 @@ class EveAllianceInfoAdmin(admin.ModelAdmin):
@admin.register(EveCharacter)
class EveCharacterAdmin(admin.ModelAdmin):
search_fields = [
'character_name',
'corporation_name',
'alliance_name',
'character_name',
'corporation_name',
'alliance_name',
'character_ownership__user__username'
]
list_display = (
@ -141,10 +141,10 @@ class EveCharacterAdmin(admin.ModelAdmin):
'character_ownership', 'character_ownership__user__profile__main_character'
)
list_filter = (
'corporation_name',
'alliance_name',
'corporation_name',
'alliance_name',
(
'character_ownership__user__profile__main_character',
'character_ownership__user__profile__main_character',
admin.RelatedOnlyFieldListFilter
),
)

View File

@ -31,12 +31,11 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
def get_actions(self, request):
actions = super(AutogroupsConfigAdmin, self).get_actions(request)
actions['sync_user_groups'] = (sync_user_groups,
'sync_user_groups',
'Sync all users groups for this Autogroup Config')
'sync_user_groups',
'Sync all users groups for this Autogroup Config')
return actions
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
admin.site.register(ManagedCorpGroup)
admin.site.register(ManagedAllianceGroup)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-12-23 04:30
from __future__ import unicode_literals

View File

@ -57,25 +57,21 @@ class AutogroupsConfig(models.Model):
states = models.ManyToManyField(State, related_name='autogroups')
corp_groups = models.BooleanField(default=False,
help_text="Setting this to false will delete all the created groups.")
corp_groups = models.BooleanField(default=False, help_text="Setting this to false will delete all the created groups.")
corp_group_prefix = models.CharField(max_length=50, default='Corp ', blank=True)
corp_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
alliance_groups = models.BooleanField(default=False,
help_text="Setting this to false will delete all the created groups.")
alliance_groups = models.BooleanField(default=False, help_text="Setting this to false will delete all the created groups.")
alliance_group_prefix = models.CharField(max_length=50, default='Alliance ', blank=True)
alliance_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
corp_managed_groups = models.ManyToManyField(
Group, through='ManagedCorpGroup', related_name='corp_managed_config',
help_text='A list of corporation groups created and maintained by this AutogroupConfig. '
'You should not edit this list unless you know what you\'re doing.')
help_text='A list of corporation groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you\'re doing.')
alliance_managed_groups = models.ManyToManyField(
Group, through='ManagedAllianceGroup', related_name='alliance_managed_config',
help_text='A list of alliance groups created and maintained by this AutogroupConfig. '
'You should not edit this list unless you know what you\'re doing.')
help_text='A list of alliance groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you\'re doing.')
replace_spaces = models.BooleanField(default=False)
replace_spaces_with = models.CharField(

View File

@ -44,7 +44,7 @@ class AutogroupsConfigManagerTestCase(TestCase):
with patch('.models.AutogroupsConfig.update_group_membership_for_user') \
as update_group_membership_for_user:
AutogroupsConfig.objects.update_groups_for_user(
user=member,
user=member,
state=member.profile.state
)

View File

@ -52,8 +52,8 @@ class AutogroupsConfigTestCase(TestCase):
@patch('.models.AutogroupsConfig.update_alliance_group_membership')
@patch('.models.AutogroupsConfig.update_corp_group_membership')
def test_update_group_membership_for_user(
self,
update_corp,
self,
update_corp,
update_alliance
):
agc = AutogroupsConfig.objects.create()
@ -123,9 +123,9 @@ class AutogroupsConfigTestCase(TestCase):
alliance_ticker='alliance_ticker',
executor_corp_id='2345'
)
mock_create_alliance.side_effect = mock_create_alliance_side_effect
obj = AutogroupsConfig.objects.create(alliance_groups=True)
obj.states.add(AuthUtils.get_member_state())
char = EveCharacter.objects.create(
@ -140,7 +140,7 @@ class AutogroupsConfigTestCase(TestCase):
self.member.profile.main_character = char
self.member.profile.save()
# Act
# Act
obj.update_alliance_group_membership(self.member)
group = obj.get_alliance_group(self.alliance)

View File

@ -1,4 +1,4 @@
# this package generates profile URL for eve entities
# this package generates profile URL for eve entities
# on 3rd party websites like evewho and zKillboard
#
# It contains of modules for views and templatetags for templates

View File

@ -3,9 +3,9 @@
from urllib.parse import urljoin, quote
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_SOLARSYSTEM
)
@ -15,28 +15,28 @@ _BASE_URL = 'http://evemaps.dotlan.net'
def _build_url(category: str, name: str) -> str:
"""return url to profile page for an eve entity"""
if category == _ESI_CATEGORY_ALLIANCE:
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == _ESI_CATEGORY_CORPORATION:
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corp'
elif category == _ESI_CATEGORY_REGION:
partial = 'map'
elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system'
else:
raise NotImplementedError(
"Not implemented yet for category:" + category
)
url = urljoin(
_BASE_URL,
'{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
)
return url

View File

@ -1,7 +1,7 @@
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_INVENTORYTYPE
)
@ -10,7 +10,7 @@ _EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
_DEFAULT_IMAGE_SIZE = 32
def _eve_entity_image_url(
def _eve_entity_image_url(
category: str,
entity_id: int,
size: int = 32,
@ -19,7 +19,7 @@ def _eve_entity_image_url(
) -> str:
"""returns image URL for an Eve Online ID.
Supported categories: alliance, corporation, character, inventory_type
Arguments:
- category: category of the ID, see ESI category constants
- entity_id: Eve ID of the entity
@ -33,7 +33,7 @@ def _eve_entity_image_url(
Exceptions:
- Throws ValueError on invalid input
"""
# input validations
categories = {
_ESI_CATEGORY_ALLIANCE: {
@ -54,15 +54,15 @@ def _eve_entity_image_url(
}
}
tenants = ['tranquility', 'singularity']
if not entity_id:
raise ValueError('Invalid entity_id: {}'.format(entity_id))
else:
entity_id = int(entity_id)
if not size or size < 32 or size > 1024 or (size & (size - 1) != 0):
raise ValueError('Invalid size: {}'.format(size))
if category not in categories:
raise ValueError('Invalid category {}'.format(category))
else:
@ -79,7 +79,7 @@ def _eve_entity_image_url(
if tenant and tenant not in tenants:
raise ValueError('Invalid tenant {}'.format(tenant))
# compose result URL
result = '{}/{}/{}/{}?size={}'.format(
_EVE_IMAGE_SERVER_URL,
@ -90,7 +90,7 @@ def _eve_entity_image_url(
)
if tenant:
result += '&tenant={}'.format(tenant)
return result

View File

@ -3,9 +3,9 @@
from urllib.parse import urljoin
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
)
@ -14,21 +14,21 @@ _BASE_URL = 'https://evewho.com'
def _build_url(category: str, eve_id: int) -> str:
"""return url to profile page for an eve entity"""
if category == _ESI_CATEGORY_ALLIANCE:
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == _ESI_CATEGORY_CORPORATION:
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation'
elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character'
else:
raise NotImplementedError(
"Not implemented yet for category:" + category
)
url = urljoin(
_BASE_URL,
'{}/{}'.format(partial, int(eve_id))

View File

@ -12,12 +12,12 @@ class TestEveWho(TestCase):
evewho.alliance_url(12345678),
'https://evewho.com/alliance/12345678'
)
def test_corporation_url(self):
self.assertEqual(
evewho.corporation_url(12345678),
'https://evewho.com/corporation/12345678'
)
)
def test_character_url(self):
self.assertEqual(
@ -49,7 +49,7 @@ class TestDotlan(TestCase):
dotlan.region_url('Black Rise'),
'http://evemaps.dotlan.net/map/Black_Rise'
)
def test_solar_system_url(self):
self.assertEqual(
dotlan.solar_system_url('Jita'),
@ -69,14 +69,14 @@ class TestZkillboard(TestCase):
self.assertEqual(
zkillboard.corporation_url(12345678),
'https://zkillboard.com/corporation/12345678/'
)
)
def test_character_url(self):
self.assertEqual(
zkillboard.character_url(12345678),
'https://zkillboard.com/character/12345678/'
)
def test_region_url(self):
self.assertEqual(
@ -93,34 +93,34 @@ class TestZkillboard(TestCase):
class TestEveImageServer(TestCase):
"""unit test for eveimageserver"""
def test_sizes(self):
def test_sizes(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42),
eveimageserver._eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=32),
eveimageserver._eve_entity_image_url('character', 42, size=32),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=64),
eveimageserver._eve_entity_image_url('character', 42, size=64),
'https://images.evetech.net/characters/42/portrait?size=64'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=128),
eveimageserver._eve_entity_image_url('character', 42, size=128),
'https://images.evetech.net/characters/42/portrait?size=128'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=256),
eveimageserver._eve_entity_image_url('character', 42, size=256),
'https://images.evetech.net/characters/42/portrait?size=256'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=512),
eveimageserver._eve_entity_image_url('character', 42, size=512),
'https://images.evetech.net/characters/42/portrait?size=512'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=1024),
eveimageserver._eve_entity_image_url('character', 42, size=1024),
'https://images.evetech.net/characters/42/portrait?size=1024'
)
with self.assertRaises(ValueError):
@ -128,10 +128,10 @@ class TestEveImageServer(TestCase):
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=0)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=31)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=1025)
@ -141,28 +141,28 @@ class TestEveImageServer(TestCase):
def test_variant(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, variant='portrait'),
eveimageserver._eve_entity_image_url('character', 42, variant='portrait'),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('alliance', 42, variant='logo'),
eveimageserver._eve_entity_image_url('alliance', 42, variant='logo'),
'https://images.evetech.net/alliances/42/logo?size=32'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('character', 42, variant='logo')
def test_alliance(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('alliance', 42),
eveimageserver._eve_entity_image_url('alliance', 42),
'https://images.evetech.net/alliances/42/logo?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('corporation', 42),
eveimageserver._eve_entity_image_url('corporation', 42),
'https://images.evetech.net/corporations/42/logo?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42),
eveimageserver._eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
with self.assertRaises(ValueError):
@ -171,16 +171,16 @@ class TestEveImageServer(TestCase):
def test_tenants(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, tenant='tranquility'),
eveimageserver._eve_entity_image_url('character', 42, tenant='tranquility'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, tenant='singularity'),
eveimageserver._eve_entity_image_url('character', 42, tenant='singularity'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('character', 42, tenant='xxx')
def test_alliance_logo_url(self):
expected = 'https://images.evetech.net/alliances/42/logo?size=128'
self.assertEqual(eveimageserver.alliance_logo_url(42, 128), expected)
@ -201,4 +201,4 @@ class TestEveImageServer(TestCase):
def test_type_render_url(self):
expected = 'https://images.evetech.net/types/42/render?size=128'
self.assertEqual(eveimageserver.type_render_url(42, 128), expected)
self.assertEqual(eveimageserver.type_render_url(42, 128), expected)

View File

@ -38,15 +38,15 @@ class TestTemplateTags(TestCase):
member_count=42,
alliance=self.my_alliance
)
self.my_region_id = 8001
self.my_region_name = 'Southpark'
self.my_solar_system_id = 9001
self.my_solar_system_name = 'Gotham'
def test_evewho_character_url(self):
def test_evewho_character_url(self):
self.assertEqual(
evelinks.evewho_character_url(self.my_character),
evewho.character_url(self.my_character.character_id),
@ -59,9 +59,9 @@ class TestTemplateTags(TestCase):
evelinks.evewho_character_url(self.my_character.character_id),
evewho.character_url(self.my_character.character_id),
)
def test_evewho_corporation_url(self):
def test_evewho_corporation_url(self):
self.assertEqual(
evelinks.evewho_corporation_url(self.my_character),
evewho.corporation_url(self.my_character.corporation_id),
@ -80,7 +80,7 @@ class TestTemplateTags(TestCase):
)
def test_evewho_alliance_url(self):
def test_evewho_alliance_url(self):
self.assertEqual(
evelinks.evewho_alliance_url(self.my_character),
evewho.alliance_url(self.my_character.alliance_id),
@ -100,12 +100,12 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.evewho_alliance_url(self.my_character.alliance_id),
evewho.alliance_url(self.my_character.alliance_id),
)
)
# dotlan
def test_dotlan_corporation_url(self):
def test_dotlan_corporation_url(self):
self.assertEqual(
evelinks.dotlan_corporation_url(self.my_character),
dotlan.corporation_url(self.my_character.corporation_name),
@ -121,10 +121,10 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.dotlan_corporation_url(self.my_character.corporation_name),
dotlan.corporation_url(self.my_character.corporation_name),
)
)
def test_dotlan_alliance_url(self):
def test_dotlan_alliance_url(self):
self.assertEqual(
evelinks.dotlan_alliance_url(self.my_character),
dotlan.alliance_url(self.my_character.alliance_name),
@ -144,32 +144,32 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.dotlan_alliance_url(self.my_character.alliance_name),
dotlan.alliance_url(self.my_character.alliance_name),
)
)
def test_dotlan_region_url(self):
def test_dotlan_region_url(self):
self.assertEqual(
evelinks.dotlan_region_url(self.my_region_name),
dotlan.region_url(self.my_region_name),
)
)
self.assertEqual(
evelinks.dotlan_region_url(None),
''
)
)
def test_dotlan_solar_system_url(self):
def test_dotlan_solar_system_url(self):
self.assertEqual(
evelinks.dotlan_solar_system_url(self.my_solar_system_name),
dotlan.solar_system_url(self.my_solar_system_name),
)
)
self.assertEqual(
evelinks.dotlan_solar_system_url(None),
''
)
)
# zkillboard
def test_zkillboard_character_url(self):
def test_zkillboard_character_url(self):
self.assertEqual(
evelinks.zkillboard_character_url(self.my_character),
zkillboard.character_url(self.my_character.character_id),
@ -182,9 +182,9 @@ class TestTemplateTags(TestCase):
evelinks.zkillboard_character_url(self.my_character.character_id),
zkillboard.character_url(self.my_character.character_id),
)
def test_zkillboard_corporation_url(self):
def test_zkillboard_corporation_url(self):
self.assertEqual(
evelinks.zkillboard_corporation_url(self.my_character),
zkillboard.corporation_url(self.my_character.corporation_id),
@ -200,10 +200,10 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.zkillboard_corporation_url(self.my_character.corporation_id),
zkillboard.corporation_url(self.my_character.corporation_id),
)
)
def test_zkillboard_alliance_url(self):
def test_zkillboard_alliance_url(self):
self.assertEqual(
evelinks.zkillboard_alliance_url(self.my_character),
zkillboard.alliance_url(self.my_character.alliance_id),
@ -223,29 +223,29 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.zkillboard_alliance_url(self.my_character.alliance_id),
zkillboard.alliance_url(self.my_character.alliance_id),
)
)
def test_zkillboard_region_url(self):
def test_zkillboard_region_url(self):
self.assertEqual(
evelinks.zkillboard_region_url(self.my_region_id),
zkillboard.region_url(self.my_region_id),
)
)
self.assertEqual(
evelinks.zkillboard_region_url(None),
''
)
)
def test_zkillboard_solar_system_url(self):
def test_zkillboard_solar_system_url(self):
self.assertEqual(
evelinks.zkillboard_solar_system_url(self.my_solar_system_id),
zkillboard.solar_system_url(self.my_solar_system_id),
)
)
self.assertEqual(
evelinks.zkillboard_solar_system_url(None),
''
)
)
# image URLs
@ -254,12 +254,12 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.character_portrait_url(123),
EveCharacter.generic_portrait_url(123)
),
self.assertEqual(
evelinks.character_portrait_url(123, 128),
EveCharacter.generic_portrait_url(123, 128)
)
self.assertEqual(
evelinks.character_portrait_url(123, 99),
@ -267,7 +267,7 @@ class TestTemplateTags(TestCase):
)
self.assertEqual(
evelinks.character_portrait_url(self.my_character),
self.my_character.portrait_url()
self.my_character.portrait_url()
)
self.assertEqual(
evelinks.character_portrait_url(None),
@ -286,7 +286,7 @@ class TestTemplateTags(TestCase):
)
self.assertEqual(
evelinks.corporation_logo_url(123, 99),
''
''
)
self.assertEqual(
evelinks.corporation_logo_url(self.my_corporation),
@ -303,7 +303,7 @@ class TestTemplateTags(TestCase):
def test_alliance_logo_url(self):
self.assertEqual(
self.assertEqual(
evelinks.alliance_logo_url(123),
EveAllianceInfo.generic_logo_url(123)
),
@ -314,7 +314,7 @@ class TestTemplateTags(TestCase):
self.assertEqual(
evelinks.alliance_logo_url(123, 99),
''
)
)
self.assertEqual(
evelinks.alliance_logo_url(self.my_alliance),
self.my_alliance.logo_url()
@ -338,10 +338,10 @@ class TestTemplateTags(TestCase):
expected = eveimageserver.type_icon_url(123, 128)
self.assertEqual(evelinks.type_icon_url(123, 128), expected)
expected = ''
self.assertEqual(evelinks.type_icon_url(123, 99), expected)
expected = ''
self.assertEqual(evelinks.type_icon_url(None), expected)
@ -351,9 +351,9 @@ class TestTemplateTags(TestCase):
expected = eveimageserver.type_render_url(123, 128)
self.assertEqual(evelinks.type_render_url(123, 128), expected)
expected = ''
self.assertEqual(evelinks.type_render_url(123, 99), expected)
expected = ''
self.assertEqual(evelinks.type_render_url(None), expected)
self.assertEqual(evelinks.type_render_url(None), expected)

View File

@ -3,10 +3,10 @@
from urllib.parse import urljoin
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_SOLARSYSTEM
)
@ -16,11 +16,11 @@ _BASE_URL = 'https://zkillboard.com'
def _build_url(category: str, eve_id: int) -> str:
"""return url to profile page for an eve entity"""
if category == _ESI_CATEGORY_ALLIANCE:
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == _ESI_CATEGORY_CORPORATION:
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation'
elif category == _ESI_CATEGORY_CHARACTER:
@ -31,12 +31,12 @@ def _build_url(category: str, eve_id: int) -> str:
elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system'
else:
raise NotImplementedError(
"Not implemented yet for category:" + category
)
url = urljoin(
_BASE_URL,
'{}/{}/'.format(partial, int(eve_id))

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:39
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 20:20
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-26 01:49
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-11-01 04:20
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-16 23:22
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-02 19:23
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-18 13:20
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-28 02:16
from __future__ import unicode_literals

View File

@ -51,7 +51,7 @@ class EveAllianceInfo(models.Model):
) -> str:
"""image URL for the given alliance ID"""
return eveimageserver.alliance_logo_url(alliance_id, size)
def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""image URL of this alliance"""
return self.generic_logo_url(self.alliance_id, size)
@ -91,9 +91,7 @@ class EveCorporationInfo(models.Model):
provider = EveCorporationProviderManager()
class Meta:
indexes = [
models.Index(fields=['ceo_id',]),
]
indexes = [models.Index(fields=['ceo_id',]),]
def update_corporation(self, corp: providers.Corporation = None):
if corp is None:
@ -157,11 +155,11 @@ class EveCharacter(models.Model):
class Meta:
indexes = [
models.Index(fields=['corporation_id',]),
models.Index(fields=['alliance_id',]),
models.Index(fields=['corporation_name',]),
models.Index(fields=['alliance_name',]),
]
models.Index(fields=['corporation_id',]),
models.Index(fields=['alliance_id',]),
models.Index(fields=['corporation_name',]),
models.Index(fields=['alliance_name',]),
]
@property
def alliance(self) -> Union[EveAllianceInfo, None]:
@ -224,7 +222,7 @@ class EveCharacter(models.Model):
def portrait_url_128(self) -> str:
"""image URL for this character"""
return self.portrait_url(128)
@property
def portrait_url_256(self) -> str:
"""image URL for this character"""
@ -275,7 +273,7 @@ class EveCharacter(models.Model):
def alliance_logo_url_128(self) -> str:
"""image URL for alliance of this character or empty string"""
return self.alliance_logo_url(128)
@property
def alliance_logo_url_256(self) -> str:
"""image URL for alliance of this character or empty string"""

Some files were not shown because too many files have changed in this diff Show More