diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..93b7b093
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d973dd57..20ec9e74 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -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
\ No newline at end of file
+ - if: $CI_COMMIT_TAG
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index e7d9ffff..9d74dbc1 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -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`
\ No newline at end of file
+Please include the results of the command `pip list`
diff --git a/.gitlab/issue_templates/Feature Request.md b/.gitlab/issue_templates/Feature Request.md
index b3351169..df9d466b 100644
--- a/.gitlab/issue_templates/Feature Request.md
+++ b/.gitlab/issue_templates/Feature Request.md
@@ -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.
\ No newline at end of file
+- Describe why its useful to you or others.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..63a70cba
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -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))
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 6f00923b..a36db8a1 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -20,4 +20,4 @@ formats: all
python:
version: 3.7
install:
- - requirements: docs/requirements.txt
\ No newline at end of file
+ - requirements: docs/requirements.txt
diff --git a/LICENSE b/LICENSE
index d6a93266..d7f10513 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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.
-
diff --git a/allianceauth/__init__.py b/allianceauth/__init__.py
index be0e9ad4..0c8c5e16 100644
--- a/allianceauth/__init__.py
+++ b/allianceauth/__init__.py
@@ -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__)
diff --git a/allianceauth/analytics/__init__.py b/allianceauth/analytics/__init__.py
new file mode 100644
index 00000000..5e4f9545
--- /dev/null
+++ b/allianceauth/analytics/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'allianceauth.analytics.apps.AnalyticsConfig'
diff --git a/allianceauth/analytics/admin.py b/allianceauth/analytics/admin.py
new file mode 100644
index 00000000..28a38b48
--- /dev/null
+++ b/allianceauth/analytics/admin.py
@@ -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',)
diff --git a/allianceauth/analytics/apps.py b/allianceauth/analytics/apps.py
new file mode 100644
index 00000000..85050412
--- /dev/null
+++ b/allianceauth/analytics/apps.py
@@ -0,0 +1,9 @@
+from django.apps import AppConfig
+
+
+class AnalyticsConfig(AppConfig):
+ name = 'allianceauth.analytics'
+ label = 'analytics'
+
+ def ready(self):
+ import allianceauth.analytics.signals
diff --git a/allianceauth/analytics/fixtures/disable_analytics.json b/allianceauth/analytics/fixtures/disable_analytics.json
new file mode 100644
index 00000000..d97ff6c1
--- /dev/null
+++ b/allianceauth/analytics/fixtures/disable_analytics.json
@@ -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"
+ }
+ }
+]
diff --git a/allianceauth/analytics/middleware.py b/allianceauth/analytics/middleware.py
new file mode 100644
index 00000000..ea636b6c
--- /dev/null
+++ b/allianceauth/analytics/middleware.py
@@ -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
diff --git a/allianceauth/analytics/migrations/0001_initial.py b/allianceauth/analytics/migrations/0001_initial.py
new file mode 100644
index 00000000..c5a1f33a
--- /dev/null
+++ b/allianceauth/analytics/migrations/0001_initial.py
@@ -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')),
+ ],
+ ),
+ ]
diff --git a/allianceauth/analytics/migrations/0002_add_AA_Team_Token.py b/allianceauth/analytics/migrations/0002_add_AA_Team_Token.py
new file mode 100644
index 00000000..13071b8a
--- /dev/null
+++ b/allianceauth/analytics/migrations/0002_add_AA_Team_Token.py
@@ -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)
+ ]
diff --git a/allianceauth/analytics/migrations/0003_Generate_Identifier.py b/allianceauth/analytics/migrations/0003_Generate_Identifier.py
new file mode 100644
index 00000000..5c4daba8
--- /dev/null
+++ b/allianceauth/analytics/migrations/0003_Generate_Identifier.py
@@ -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)
+ ]
diff --git a/allianceauth/analytics/migrations/0004_auto_20211015_0502.py b/allianceauth/analytics/migrations/0004_auto_20211015_0502.py
new file mode 100644
index 00000000..c9da3708
--- /dev/null
+++ b/allianceauth/analytics/migrations/0004_auto_20211015_0502.py
@@ -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)
+ ]
diff --git a/allianceauth/analytics/migrations/__init__.py b/allianceauth/analytics/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/allianceauth/analytics/models.py b/allianceauth/analytics/models.py
new file mode 100644
index 00000000..bc290162
--- /dev/null
+++ b/allianceauth/analytics/models.py
@@ -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)
diff --git a/allianceauth/analytics/signals.py b/allianceauth/analytics/signals.py
new file mode 100644
index 00000000..b3943220
--- /dev/null
+++ b/allianceauth/analytics/signals.py
@@ -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)
diff --git a/allianceauth/analytics/tasks.py b/allianceauth/analytics/tasks.py
new file mode 100644
index 00000000..7fe22477
--- /dev/null
+++ b/allianceauth/analytics/tasks.py
@@ -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
diff --git a/allianceauth/analytics/tests/__init__.py b/allianceauth/analytics/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/allianceauth/analytics/tests/test_middleware.py b/allianceauth/analytics/tests/test_middleware.py
new file mode 100644
index 00000000..eecfbbc7
--- /dev/null
+++ b/allianceauth/analytics/tests/test_middleware.py
@@ -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)
diff --git a/allianceauth/analytics/tests/test_models.py b/allianceauth/analytics/tests/test_models.py
new file mode 100644
index 00000000..fd424f11
--- /dev/null
+++ b/allianceauth/analytics/tests/test_models.py
@@ -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))
diff --git a/allianceauth/analytics/tests/test_tasks.py b/allianceauth/analytics/tests/test_tasks.py
new file mode 100644
index 00000000..badc59f5
--- /dev/null
+++ b/allianceauth/analytics/tests/test_tasks.py
@@ -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'}]
diff --git a/allianceauth/analytics/tests/test_utils.py b/allianceauth/analytics/tests/test_utils.py
new file mode 100644
index 00000000..0da45c23
--- /dev/null
+++ b/allianceauth/analytics/tests/test_utils.py
@@ -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)
diff --git a/allianceauth/analytics/utils.py b/allianceauth/analytics/utils.py
new file mode 100644
index 00000000..e8c57927
--- /dev/null
+++ b/allianceauth/analytics/utils.py
@@ -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
diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py
index 6b45ec80..b39623cf 100644
--- a/allianceauth/authentication/admin.py
+++ b/allianceauth/authentication/admin.py
@@ -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(
'{}
{}',
- link,
+ link,
user_obj.username,
user_obj.profile.main_character.character_name
)
else:
return format_html(
'{}',
- 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(
'{}
{}',
- 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,
)
diff --git a/allianceauth/authentication/app_settings.py b/allianceauth/authentication/app_settings.py
index 9494953f..449bef08 100644
--- a/allianceauth/authentication/app_settings.py
+++ b/allianceauth/authentication/app_settings.py
@@ -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)
-
diff --git a/allianceauth/authentication/backends.py b/allianceauth/authentication/backends.py
index f6fb2e0d..d0cecf4a 100644
--- a/allianceauth/authentication/backends.py
+++ b/allianceauth/authentication/backends.py
@@ -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()
diff --git a/allianceauth/authentication/hmac_urls.py b/allianceauth/authentication/hmac_urls.py
index a4d225d7..edf7b973 100644
--- a/allianceauth/authentication/hmac_urls.py
+++ b/allianceauth/authentication/hmac_urls.py
@@ -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')),
-]
\ No newline at end of file
+]
diff --git a/allianceauth/authentication/managers.py b/allianceauth/authentication/managers.py
index b97963a6..3b2e8ed0 100755
--- a/allianceauth/authentication/managers.py
+++ b/allianceauth/authentication/managers.py
@@ -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):
diff --git a/allianceauth/authentication/migrations/0001_initial.py b/allianceauth/authentication/migrations/0001_initial.py
index 8c5f1f2a..3f9454e6 100644
--- a/allianceauth/authentication/migrations/0001_initial.py
+++ b/allianceauth/authentication/migrations/0001_initial.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:38
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0002_auto_20160907_1914.py b/allianceauth/authentication/migrations/0002_auto_20160907_1914.py
index c6614a92..8045e83e 100644
--- a/allianceauth/authentication/migrations/0002_auto_20160907_1914.py
+++ b/allianceauth/authentication/migrations/0002_auto_20160907_1914.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-07 19:14
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0003_authservicesinfo_state.py b/allianceauth/authentication/migrations/0003_authservicesinfo_state.py
index cac17cd3..9ac14c83 100644
--- a/allianceauth/authentication/migrations/0003_authservicesinfo_state.py
+++ b/allianceauth/authentication/migrations/0003_authservicesinfo_state.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 20:29
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0004_create_permissions.py b/allianceauth/authentication/migrations/0004_create_permissions.py
index 3dc4acfb..f63928fa 100644
--- a/allianceauth/authentication/migrations/0004_create_permissions.py
+++ b/allianceauth/authentication/migrations/0004_create_permissions.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0005_delete_perms.py b/allianceauth/authentication/migrations/0005_delete_perms.py
index e10f4253..a674c95c 100644
--- a/allianceauth/authentication/migrations/0005_delete_perms.py
+++ b/allianceauth/authentication/migrations/0005_delete_perms.py
@@ -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):
diff --git a/allianceauth/authentication/migrations/0006_auto_20160910_0542.py b/allianceauth/authentication/migrations/0006_auto_20160910_0542.py
index d7a24797..0bdd3498 100644
--- a/allianceauth/authentication/migrations/0006_auto_20160910_0542.py
+++ b/allianceauth/authentication/migrations/0006_auto_20160910_0542.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 05:42
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0007_remove_authservicesinfo_is_blue.py b/allianceauth/authentication/migrations/0007_remove_authservicesinfo_is_blue.py
index 037c2092..8150d6b8 100644
--- a/allianceauth/authentication/migrations/0007_remove_authservicesinfo_is_blue.py
+++ b/allianceauth/authentication/migrations/0007_remove_authservicesinfo_is_blue.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 21:50
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0008_set_state.py b/allianceauth/authentication/migrations/0008_set_state.py
index 8951bcba..039409bd 100644
--- a/allianceauth/authentication/migrations/0008_set_state.py
+++ b/allianceauth/authentication/migrations/0008_set_state.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-12 13:04
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0009_auto_20161021_0228.py b/allianceauth/authentication/migrations/0009_auto_20161021_0228.py
index 5123ac22..853a01db 100644
--- a/allianceauth/authentication/migrations/0009_auto_20161021_0228.py
+++ b/allianceauth/authentication/migrations/0009_auto_20161021_0228.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-21 02:28
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0010_only_one_authservicesinfo.py b/allianceauth/authentication/migrations/0010_only_one_authservicesinfo.py
index c93c5387..b08e5283 100644
--- a/allianceauth/authentication/migrations/0010_only_one_authservicesinfo.py
+++ b/allianceauth/authentication/migrations/0010_only_one_authservicesinfo.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 06:47
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0011_authservicesinfo_user_onetoonefield.py b/allianceauth/authentication/migrations/0011_authservicesinfo_user_onetoonefield.py
index ef83dbec..ef694024 100644
--- a/allianceauth/authentication/migrations/0011_authservicesinfo_user_onetoonefield.py
+++ b/allianceauth/authentication/migrations/0011_authservicesinfo_user_onetoonefield.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 07:11
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0012_remove_add_delete_authservicesinfo_permissions.py b/allianceauth/authentication/migrations/0012_remove_add_delete_authservicesinfo_permissions.py
index 0ba0fdf0..208ea120 100644
--- a/allianceauth/authentication/migrations/0012_remove_add_delete_authservicesinfo_permissions.py
+++ b/allianceauth/authentication/migrations/0012_remove_add_delete_authservicesinfo_permissions.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-12 00:59
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0013_service_modules.py b/allianceauth/authentication/migrations/0013_service_modules.py
index 450aa339..c7801dbd 100644
--- a/allianceauth/authentication/migrations/0013_service_modules.py
+++ b/allianceauth/authentication/migrations/0013_service_modules.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-11 23:14
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0014_fleetup_permission.py b/allianceauth/authentication/migrations/0014_fleetup_permission.py
index 48f1aee1..cfe60109 100644
--- a/allianceauth/authentication/migrations/0014_fleetup_permission.py
+++ b/allianceauth/authentication/migrations/0014_fleetup_permission.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals
diff --git a/allianceauth/authentication/migrations/0015_user_profiles.py b/allianceauth/authentication/migrations/0015_user_profiles.py
index 983871b5..dccd1808 100644
--- a/allianceauth/authentication/migrations/0015_user_profiles.py
+++ b/allianceauth/authentication/migrations/0015_user_profiles.py
@@ -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(
diff --git a/allianceauth/authentication/migrations/0017_remove_fleetup_permission.py b/allianceauth/authentication/migrations/0017_remove_fleetup_permission.py
index 819eac7f..f83b7a6a 100644
--- a/allianceauth/authentication/migrations/0017_remove_fleetup_permission.py
+++ b/allianceauth/authentication/migrations/0017_remove_fleetup_permission.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
diff --git a/allianceauth/authentication/models.py b/allianceauth/authentication/models.py
index c71721ef..e95fe031 100755
--- a/allianceauth/authentication/models.py
+++ b/allianceauth/authentication/models.py
@@ -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)
\ No newline at end of file
+ return "%s: %s on %s" % (self.user, self.character, self.created)
diff --git a/allianceauth/authentication/signals.py b/allianceauth/authentication/signals.py
index 2667ec8d..335a1c00 100644
--- a/allianceauth/authentication/signals.py
+++ b/allianceauth/authentication/signals.py
@@ -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)
\ No newline at end of file
+ OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)
diff --git a/allianceauth/authentication/static/authentication/css/admin.css b/allianceauth/authentication/static/authentication/css/admin.css
index 489edd2e..7ea91049 100644
--- a/allianceauth/authentication/static/authentication/css/admin.css
+++ b/allianceauth/authentication/static/authentication/css/admin.css
@@ -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 ;
-}
\ No newline at end of file
+}
diff --git a/allianceauth/authentication/templates/authentication/dashboard.html b/allianceauth/authentication/templates/authentication/dashboard.html
index d4401aa1..49dfa762 100644
--- a/allianceauth/authentication/templates/authentication/dashboard.html
+++ b/allianceauth/authentication/templates/authentication/dashboard.html
@@ -63,7 +63,7 @@
{% endif %}
-
@@ -74,7 +74,7 @@
{{ main.corporation_name }}
{{ main.alliance_name }}
{{ char.corporation_name }} {{ char.alliance_name|default:"" }} - |
+
||||||||||||||||||||
-
+
{{ main.main }}
@@ -88,8 +87,7 @@
{{ alt.corporation_name }} |
{{ alt.alliance_name }} |
-
+
{% trans "Killboard" %}
|
@@ -123,10 +121,9 @@
{{ member }} |
- {% trans "Killboard" %} |
+
+ {% trans "Killboard" %}
+ |
{{ member.character_ownership.user.profile.main_character.character_name }} |
{{ member.character_ownership.user.profile.main_character.corporation_name }} |
{{ member.character_ownership.user.profile.main_character.alliance_name }} |
@@ -136,10 +133,9 @@
{{ member.character_name }} |
- {% trans "Killboard" %} |
+
+ {% trans "Killboard" %}
+ |
|
|
@@ -167,9 +163,7 @@
| {{ member.character_name }} |
-
+
{% trans "Killboard" %}
|
diff --git a/allianceauth/corputils/templates/corputils/search.html b/allianceauth/corputils/templates/corputils/search.html
index 85d482b9..3f9176bb 100644
--- a/allianceauth/corputils/templates/corputils/search.html
+++ b/allianceauth/corputils/templates/corputils/search.html
@@ -45,4 +45,4 @@
$(document).ready(function(){
$('#table-search').DataTable();
});
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/allianceauth/corputils/views.py b/allianceauth/corputils/views.py
index 92853ce6..fd818896 100644
--- a/allianceauth/corputils/views.py
+++ b/allianceauth/corputils/views.py
@@ -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')
diff --git a/allianceauth/eveonline/admin.py b/allianceauth/eveonline/admin.py
index 9c0447a1..1333fa9d 100644
--- a/allianceauth/eveonline/admin.py
+++ b/allianceauth/eveonline/admin.py
@@ -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
),
)
diff --git a/allianceauth/eveonline/autogroups/admin.py b/allianceauth/eveonline/autogroups/admin.py
index f06af858..888be4ed 100644
--- a/allianceauth/eveonline/autogroups/admin.py
+++ b/allianceauth/eveonline/autogroups/admin.py
@@ -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)
-
diff --git a/allianceauth/eveonline/autogroups/migrations/0001_initial.py b/allianceauth/eveonline/autogroups/migrations/0001_initial.py
index 8441e1ed..3c552693 100644
--- a/allianceauth/eveonline/autogroups/migrations/0001_initial.py
+++ b/allianceauth/eveonline/autogroups/migrations/0001_initial.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-12-23 04:30
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/autogroups/models.py b/allianceauth/eveonline/autogroups/models.py
index 23b7bf23..8cf6f319 100644
--- a/allianceauth/eveonline/autogroups/models.py
+++ b/allianceauth/eveonline/autogroups/models.py
@@ -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(
diff --git a/allianceauth/eveonline/autogroups/tests/test_managers.py b/allianceauth/eveonline/autogroups/tests/test_managers.py
index ebf5eb02..e4e42ef0 100644
--- a/allianceauth/eveonline/autogroups/tests/test_managers.py
+++ b/allianceauth/eveonline/autogroups/tests/test_managers.py
@@ -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
)
diff --git a/allianceauth/eveonline/autogroups/tests/test_models.py b/allianceauth/eveonline/autogroups/tests/test_models.py
index 44917d3c..45044608 100644
--- a/allianceauth/eveonline/autogroups/tests/test_models.py
+++ b/allianceauth/eveonline/autogroups/tests/test_models.py
@@ -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)
diff --git a/allianceauth/eveonline/evelinks/__init__.py b/allianceauth/eveonline/evelinks/__init__.py
index 2fb4f6b8..6f725dfd 100644
--- a/allianceauth/eveonline/evelinks/__init__.py
+++ b/allianceauth/eveonline/evelinks/__init__.py
@@ -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
diff --git a/allianceauth/eveonline/evelinks/dotlan.py b/allianceauth/eveonline/evelinks/dotlan.py
index ebd0e7bd..b782eabd 100644
--- a/allianceauth/eveonline/evelinks/dotlan.py
+++ b/allianceauth/eveonline/evelinks/dotlan.py
@@ -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
diff --git a/allianceauth/eveonline/evelinks/eveimageserver.py b/allianceauth/eveonline/evelinks/eveimageserver.py
index d3323446..d2f51c27 100644
--- a/allianceauth/eveonline/evelinks/eveimageserver.py
+++ b/allianceauth/eveonline/evelinks/eveimageserver.py
@@ -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
diff --git a/allianceauth/eveonline/evelinks/evewho.py b/allianceauth/eveonline/evelinks/evewho.py
index 57d748ac..3062ba45 100644
--- a/allianceauth/eveonline/evelinks/evewho.py
+++ b/allianceauth/eveonline/evelinks/evewho.py
@@ -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))
diff --git a/allianceauth/eveonline/evelinks/tests/test_evelinks.py b/allianceauth/eveonline/evelinks/tests/test_evelinks.py
index a9baeb93..32f79f05 100644
--- a/allianceauth/eveonline/evelinks/tests/test_evelinks.py
+++ b/allianceauth/eveonline/evelinks/tests/test_evelinks.py
@@ -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)
\ No newline at end of file
+ self.assertEqual(eveimageserver.type_render_url(42, 128), expected)
diff --git a/allianceauth/eveonline/evelinks/tests/test_templatetags.py b/allianceauth/eveonline/evelinks/tests/test_templatetags.py
index 4fb8327a..db5c8178 100644
--- a/allianceauth/eveonline/evelinks/tests/test_templatetags.py
+++ b/allianceauth/eveonline/evelinks/tests/test_templatetags.py
@@ -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)
\ No newline at end of file
+ self.assertEqual(evelinks.type_render_url(None), expected)
diff --git a/allianceauth/eveonline/evelinks/zkillboard.py b/allianceauth/eveonline/evelinks/zkillboard.py
index 255964b0..0023d071 100644
--- a/allianceauth/eveonline/evelinks/zkillboard.py
+++ b/allianceauth/eveonline/evelinks/zkillboard.py
@@ -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))
diff --git a/allianceauth/eveonline/migrations/0001_initial.py b/allianceauth/eveonline/migrations/0001_initial.py
index cf3ebde0..e2c2589d 100644
--- a/allianceauth/eveonline/migrations/0001_initial.py
+++ b/allianceauth/eveonline/migrations/0001_initial.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:39
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0002_remove_eveapikeypair_error_count.py b/allianceauth/eveonline/migrations/0002_remove_eveapikeypair_error_count.py
index 52fca062..5c7de29e 100644
--- a/allianceauth/eveonline/migrations/0002_remove_eveapikeypair_error_count.py
+++ b/allianceauth/eveonline/migrations/0002_remove_eveapikeypair_error_count.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 20:20
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0003_auto_20161026_0149.py b/allianceauth/eveonline/migrations/0003_auto_20161026_0149.py
index beffba20..6ec80863 100644
--- a/allianceauth/eveonline/migrations/0003_auto_20161026_0149.py
+++ b/allianceauth/eveonline/migrations/0003_auto_20161026_0149.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-26 01:49
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0004_eveapikeypair_sso_verified.py b/allianceauth/eveonline/migrations/0004_eveapikeypair_sso_verified.py
index 38a196c0..140aff15 100644
--- a/allianceauth/eveonline/migrations/0004_eveapikeypair_sso_verified.py
+++ b/allianceauth/eveonline/migrations/0004_eveapikeypair_sso_verified.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-11-01 04:20
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0005_remove_eveallianceinfo_member_count.py b/allianceauth/eveonline/migrations/0005_remove_eveallianceinfo_member_count.py
index f9a7bfab..604146f2 100644
--- a/allianceauth/eveonline/migrations/0005_remove_eveallianceinfo_member_count.py
+++ b/allianceauth/eveonline/migrations/0005_remove_eveallianceinfo_member_count.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-16 23:22
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0006_allow_null_evecharacter_alliance.py b/allianceauth/eveonline/migrations/0006_allow_null_evecharacter_alliance.py
index d6e44ec5..f78a2d3c 100644
--- a/allianceauth/eveonline/migrations/0006_allow_null_evecharacter_alliance.py
+++ b/allianceauth/eveonline/migrations/0006_allow_null_evecharacter_alliance.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-02 19:23
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0007_unique_id_name.py b/allianceauth/eveonline/migrations/0007_unique_id_name.py
index 6aa0b593..f91733c8 100644
--- a/allianceauth/eveonline/migrations/0007_unique_id_name.py
+++ b/allianceauth/eveonline/migrations/0007_unique_id_name.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-18 13:20
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0008_remove_apikeys.py b/allianceauth/eveonline/migrations/0008_remove_apikeys.py
index 158fccaf..e8475693 100644
--- a/allianceauth/eveonline/migrations/0008_remove_apikeys.py
+++ b/allianceauth/eveonline/migrations/0008_remove_apikeys.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/migrations/0009_on_delete.py b/allianceauth/eveonline/migrations/0009_on_delete.py
index ad534248..9cfa03e2 100644
--- a/allianceauth/eveonline/migrations/0009_on_delete.py
+++ b/allianceauth/eveonline/migrations/0009_on_delete.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-28 02:16
from __future__ import unicode_literals
diff --git a/allianceauth/eveonline/models.py b/allianceauth/eveonline/models.py
index 9a55e4fa..04181a38 100644
--- a/allianceauth/eveonline/models.py
+++ b/allianceauth/eveonline/models.py
@@ -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"""
diff --git a/allianceauth/eveonline/providers.py b/allianceauth/eveonline/providers.py
index dc25ba35..1373c49d 100644
--- a/allianceauth/eveonline/providers.py
+++ b/allianceauth/eveonline/providers.py
@@ -159,7 +159,7 @@ class EveProvider(object):
class EveSwaggerProvider(EveProvider):
- def __init__(self, token=None, adapter=None):
+ def __init__(self, token=None, adapter=None):
if settings.DEBUG:
self._client = None
logger.info(
diff --git a/allianceauth/eveonline/tasks.py b/allianceauth/eveonline/tasks.py
index 5f3f4d42..9562d520 100644
--- a/allianceauth/eveonline/tasks.py
+++ b/allianceauth/eveonline/tasks.py
@@ -40,7 +40,7 @@ def update_character(character_id):
@shared_task
def run_model_update():
"""Update all alliances, corporations and characters from ESI"""
-
+
# update existing corp models
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
update_corp.apply_async(
@@ -54,7 +54,7 @@ def run_model_update():
)
# update existing character models
- character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
+ character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
for character_ids_chunk in chunks(character_ids, CHUNK_SIZE):
affiliations_raw = providers.provider.client.Character\
.post_characters_affiliation(characters=character_ids_chunk).result()
@@ -62,39 +62,39 @@ def run_model_update():
.post_universe_names(ids=character_ids_chunk).result()
affiliations = {
- affiliation.get('character_id'): affiliation
+ affiliation.get('character_id'): affiliation
for affiliation in affiliations_raw
}
# add character names to affiliations
- for character in character_names:
+ for character in character_names:
character_id = character.get('id')
if character_id in affiliations:
affiliations[character_id]['name'] = character.get('name')
# fetch current characters
characters = EveCharacter.objects.filter(character_id__in=character_ids_chunk)\
- .values('character_id', 'corporation_id', 'alliance_id', 'character_name')
-
+ .values('character_id', 'corporation_id', 'alliance_id', 'character_name')
+
for character in characters:
character_id = character.get('character_id')
if character_id in affiliations:
affiliation = affiliations[character_id]
-
+
corp_changed = (
character.get('corporation_id') != affiliation.get('corporation_id')
)
-
+
alliance_id = character.get('alliance_id')
if not alliance_id:
alliance_id = None
alliance_changed = alliance_id != affiliation.get('alliance_id')
-
+
name_changed = False
fetched_name = affiliation.get('name', False)
if fetched_name:
name_changed = character.get('character_name') != fetched_name
-
- if corp_changed or alliance_changed or name_changed:
+
+ if corp_changed or alliance_changed or name_changed:
update_character.apply_async(
args=[character.get('character_id')], priority=TASK_PRIORITY
)
diff --git a/allianceauth/eveonline/templatetags/evelinks.py b/allianceauth/eveonline/templatetags/evelinks.py
index bf507965..e5f44440 100644
--- a/allianceauth/eveonline/templatetags/evelinks.py
+++ b/allianceauth/eveonline/templatetags/evelinks.py
@@ -1,14 +1,14 @@
# This module defines template tags for evelinks URLs and eve image URLs
-#
+#
# Many tags will work both with their respective eveonline object
# and their respective eve entity ID
-#
+#
# Example:
# character URL on evewho: {{ my_character|evewho_character_url}}
# character URL on evewho: {{ 1456384556|evewho_character_url}}
-#
+#
# For more examples see examples.html
-#
+#
# To add templatetags for additional providers just add the respective
# template functions and let them call the generic functions
@@ -25,59 +25,59 @@ _DEFAULT_IMAGE_SIZE = 32
# generic functions
def _generic_character_url(
- provider: object,
- obj_prop: str,
+ provider: object,
+ obj_prop: str,
eve_obj: EveCharacter
) -> str:
"""returns character URL for given provider and object"""
my_func = getattr(provider, 'character_url')
- if isinstance(eve_obj, EveCharacter):
+ if isinstance(eve_obj, EveCharacter):
return my_func(getattr(eve_obj, obj_prop))
-
+
elif eve_obj is None:
return ''
else:
return my_func(eve_obj)
-
+
def _generic_corporation_url(
- provider: object,
- obj_prop: str,
+ provider: object,
+ obj_prop: str,
eve_obj: object
) -> str:
"""returns corporation URL for given provider and object"""
my_func = getattr(provider, 'corporation_url')
- if isinstance(eve_obj, (EveCharacter, EveCorporationInfo)):
+ if isinstance(eve_obj, (EveCharacter, EveCorporationInfo)):
return my_func(getattr(eve_obj, obj_prop))
elif eve_obj is None:
return ''
-
+
else:
return my_func(eve_obj)
-
+
def _generic_alliance_url(
- provider: object,
+ provider: object,
obj_prop: str,
eve_obj: object
) -> str:
"""returns alliance URL for given provider and object"""
my_func = getattr(provider, 'alliance_url')
-
+
if isinstance(eve_obj, EveCharacter):
- if eve_obj.alliance_id:
+ if eve_obj.alliance_id:
return my_func(getattr(eve_obj, obj_prop))
else:
return ''
-
- elif isinstance(eve_obj, EveAllianceInfo):
+
+ elif isinstance(eve_obj, EveAllianceInfo):
return my_func(getattr(eve_obj, obj_prop))
elif eve_obj is None:
return ''
-
+
else:
return my_func(eve_obj)
@@ -91,7 +91,7 @@ def _generic_evelinks_url(
my_func = getattr(provider, provider_func)
if eve_obj is None:
return ''
-
+
else:
return my_func(eve_obj)
@@ -99,29 +99,29 @@ def _generic_evelinks_url(
# evewho
@register.filter
-def evewho_character_url(eve_obj: EveCharacter) -> str:
+def evewho_character_url(eve_obj: EveCharacter) -> str:
"""generates an evewho URL for the given object
Works with allianceauth.eveonline objects and eve entity IDs
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_character_url(evewho, 'character_id', eve_obj)
-
+
@register.filter
def evewho_corporation_url(eve_obj: object) -> str:
"""generates an evewho URL for the given object
Works with allianceauth.eveonline objects and eve entity IDs
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_corporation_url(evewho, 'corporation_id', eve_obj)
-
+
@register.filter
-def evewho_alliance_url(eve_obj: object) -> str:
+def evewho_alliance_url(eve_obj: object) -> str:
"""generates an evewho URL for the given object
Works with allianceauth.eveonline objects and eve entity IDs
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_alliance_url(evewho, 'alliance_id', eve_obj)
@@ -131,69 +131,69 @@ def evewho_alliance_url(eve_obj: object) -> str:
def dotlan_corporation_url(eve_obj: object) -> str:
"""generates a dotlan URL for the given object
Works with allianceauth.eveonline objects and eve entity names
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_corporation_url(dotlan, 'corporation_name', eve_obj)
-
+
@register.filter
-def dotlan_alliance_url(eve_obj: object) -> str:
+def dotlan_alliance_url(eve_obj: object) -> str:
"""generates a dotlan URL for the given object
Works with allianceauth.eveonline objects and eve entity names
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_alliance_url(dotlan, 'alliance_name', eve_obj)
@register.filter
-def dotlan_region_url(eve_obj: object) -> str:
+def dotlan_region_url(eve_obj: object) -> str:
"""generates a dotlan URL for the given object
Works with eve entity names
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_evelinks_url(dotlan, 'region_url', eve_obj)
@register.filter
-def dotlan_solar_system_url(eve_obj: object) -> str:
+def dotlan_solar_system_url(eve_obj: object) -> str:
"""generates a dotlan URL for the given object
Works with eve entity names
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_evelinks_url(dotlan, 'solar_system_url', eve_obj)
# zkillboard
@register.filter
-def zkillboard_character_url(eve_obj: EveCharacter) -> str:
+def zkillboard_character_url(eve_obj: EveCharacter) -> str:
"""generates a zkillboard URL for the given object
Works with allianceauth.eveonline objects and eve entity IDs
- Returns URL or empty string
- """
+ Returns URL or empty string
+ """
return _generic_character_url(zkillboard, 'character_id', eve_obj)
-
+
@register.filter
def zkillboard_corporation_url(eve_obj: object) -> str:
"""generates a zkillboard URL for the given object
Works with allianceauth.eveonline objects and eve entity IDs
- Returns URL or empty string
+ Returns URL or empty string
"""
return _generic_corporation_url(zkillboard, 'corporation_id', eve_obj)
-
+
@register.filter
-def zkillboard_alliance_url(eve_obj: object) -> str:
+def zkillboard_alliance_url(eve_obj: object) -> str:
"""generates a zkillboard URL for the given object
Works with allianceauth.eveonline objects and eve entity IDs
- Returns URL or empty string
+ Returns URL or empty string
"""
return _generic_alliance_url(zkillboard, 'alliance_id', eve_obj)
@register.filter
-def zkillboard_region_url(eve_obj: object) -> str:
+def zkillboard_region_url(eve_obj: object) -> str:
"""generates a zkillboard URL for the given object
Works with eve entity IDs
Returns URL or empty string
@@ -202,7 +202,7 @@ def zkillboard_region_url(eve_obj: object) -> str:
@register.filter
-def zkillboard_solar_system_url(eve_obj: object) -> str:
+def zkillboard_solar_system_url(eve_obj: object) -> str:
"""generates zkillboard URL for the given object
Works with eve entity IDs
Returns URL or empty string
@@ -214,20 +214,20 @@ def zkillboard_solar_system_url(eve_obj: object) -> str:
@register.filter
def character_portrait_url(
- eve_obj: object,
+ eve_obj: object,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""generates an image URL for the given object
Works with EveCharacter objects or character IDs
Returns URL or empty string
"""
- if isinstance(eve_obj, EveCharacter):
+ if isinstance(eve_obj, EveCharacter):
return eve_obj.portrait_url(size)
elif eve_obj is None:
return ''
-
- else:
+
+ else:
try:
return EveCharacter.generic_portrait_url(eve_obj, size)
except ValueError:
@@ -236,23 +236,23 @@ def character_portrait_url(
@register.filter
def corporation_logo_url(
- eve_obj: object,
+ eve_obj: object,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""generates image URL for the given object
Works with EveCharacter, EveCorporationInfo objects or corporation IDs
Returns URL or empty string
"""
- if isinstance(eve_obj, EveCorporationInfo):
+ if isinstance(eve_obj, EveCorporationInfo):
return eve_obj.logo_url(size)
- elif isinstance(eve_obj, EveCharacter):
+ elif isinstance(eve_obj, EveCharacter):
return eve_obj.corporation_logo_url(size)
elif eve_obj is None:
return ''
-
- else:
+
+ else:
try:
return EveCorporationInfo.generic_logo_url(eve_obj, size)
except ValueError:
@@ -261,23 +261,23 @@ def corporation_logo_url(
@register.filter
def alliance_logo_url(
- eve_obj: object,
+ eve_obj: object,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""generates image URL for the given object
Works with EveCharacter, EveAllianceInfo objects or alliance IDs
Returns URL or empty string
"""
- if isinstance(eve_obj, EveAllianceInfo):
+ if isinstance(eve_obj, EveAllianceInfo):
return eve_obj.logo_url(size)
- elif isinstance(eve_obj, EveCharacter):
+ elif isinstance(eve_obj, EveCharacter):
return eve_obj.alliance_logo_url(size)
elif eve_obj is None:
return ''
-
- else:
+
+ else:
try:
return EveAllianceInfo.generic_logo_url(eve_obj, size)
except ValueError:
@@ -286,10 +286,10 @@ def alliance_logo_url(
@register.filter
def type_icon_url(
- type_id: int,
+ type_id: int,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
- """generates a icon image URL for the given type ID
+ """generates a icon image URL for the given type ID
Returns URL or empty string
"""
try:
@@ -300,10 +300,10 @@ def type_icon_url(
@register.filter
def type_render_url(
- type_id: int,
+ type_id: int,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
- """generates a render image URL for the given type ID
+ """generates a render image URL for the given type ID
Returns URL or empty string
"""
try:
diff --git a/allianceauth/eveonline/templatetags/examples.html b/allianceauth/eveonline/templatetags/examples.html
index 61e5d096..180aa3f8 100644
--- a/allianceauth/eveonline/templatetags/examples.html
+++ b/allianceauth/eveonline/templatetags/examples.html
@@ -1,4 +1,4 @@
-
- |