mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-13 14:30:17 +02:00
Merge branch 'v2.9.x' into 'master'
Bring up Master to 2.9.0 See merge request allianceauth/allianceauth!1344
This commit is contained in:
commit
eddb5480e9
24
.editorconfig
Normal file
24
.editorconfig
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{yaml,yml,less}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Makefiles always use tabs for indentation
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.bat]
|
||||||
|
indent_style = tab
|
@ -1,4 +1,5 @@
|
|||||||
stages:
|
stages:
|
||||||
|
- pre-commit
|
||||||
- gitlab
|
- gitlab
|
||||||
- test
|
- test
|
||||||
- deploy
|
- deploy
|
||||||
@ -13,6 +14,18 @@ before_script:
|
|||||||
- python -V
|
- python -V
|
||||||
- pip install wheel tox
|
- 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:
|
sast:
|
||||||
stage: gitlab
|
stage: gitlab
|
||||||
before_script: []
|
before_script: []
|
||||||
@ -20,22 +33,13 @@ sast:
|
|||||||
dependency_scanning:
|
dependency_scanning:
|
||||||
stage: gitlab
|
stage: gitlab
|
||||||
before_script:
|
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
|
- redis-server --daemonize yes
|
||||||
- python -V
|
- python -V
|
||||||
- pip install wheel tox
|
- 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:
|
test-3.7-core:
|
||||||
image: python:3.7-buster
|
image: python:3.7-bullseye
|
||||||
script:
|
script:
|
||||||
- tox -e py37-core
|
- tox -e py37-core
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -44,7 +48,7 @@ test-3.7-core:
|
|||||||
cobertura: coverage.xml
|
cobertura: coverage.xml
|
||||||
|
|
||||||
test-3.8-core:
|
test-3.8-core:
|
||||||
image: python:3.8-buster
|
image: python:3.8-bullseye
|
||||||
script:
|
script:
|
||||||
- tox -e py38-core
|
- tox -e py38-core
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -52,17 +56,17 @@ test-3.8-core:
|
|||||||
reports:
|
reports:
|
||||||
cobertura: coverage.xml
|
cobertura: coverage.xml
|
||||||
|
|
||||||
test-3.6-all:
|
test-3.9-core:
|
||||||
image: python:3.6-buster
|
image: python:3.9-bullseye
|
||||||
script:
|
script:
|
||||||
- tox -e py36-all
|
- tox -e py39-core
|
||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
reports:
|
reports:
|
||||||
cobertura: coverage.xml
|
cobertura: coverage.xml
|
||||||
|
|
||||||
test-3.7-all:
|
test-3.7-all:
|
||||||
image: python:3.7-buster
|
image: python:3.7-bullseye
|
||||||
script:
|
script:
|
||||||
- tox -e py37-all
|
- tox -e py37-all
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -71,7 +75,7 @@ test-3.7-all:
|
|||||||
cobertura: coverage.xml
|
cobertura: coverage.xml
|
||||||
|
|
||||||
test-3.8-all:
|
test-3.8-all:
|
||||||
image: python:3.8-buster
|
image: python:3.8-bullseye
|
||||||
script:
|
script:
|
||||||
- tox -e py38-all
|
- tox -e py38-all
|
||||||
artifacts:
|
artifacts:
|
||||||
@ -79,9 +83,18 @@ test-3.8-all:
|
|||||||
reports:
|
reports:
|
||||||
cobertura: coverage.xml
|
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:
|
deploy_production:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
image: python:3.8-buster
|
image: python:3.9-bullseye
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- pip install twine wheel
|
- pip install twine wheel
|
||||||
|
28
.pre-commit-config.yaml
Normal file
28
.pre-commit-config.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Apply to all files without committing:
|
||||||
|
# pre-commit run --all-files
|
||||||
|
# Update this file:
|
||||||
|
# pre-commit autoupdate
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.0.1
|
||||||
|
hooks:
|
||||||
|
- id: check-case-conflict
|
||||||
|
- id: check-json
|
||||||
|
- id: check-xml
|
||||||
|
- id: check-yaml
|
||||||
|
- id: fix-byte-order-marker
|
||||||
|
- id: trailing-whitespace
|
||||||
|
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
|
||||||
|
- id: mixed-line-ending
|
||||||
|
args: [ '--fix=lf' ]
|
||||||
|
- id: fix-encoding-pragma
|
||||||
|
args: [ '--remove' ]
|
||||||
|
|
||||||
|
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||||
|
rev: 2.3.5
|
||||||
|
hooks:
|
||||||
|
- id: editorconfig-checker
|
||||||
|
exclude: ^(LICENSE|allianceauth\/static\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))
|
1
LICENSE
1
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
|
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
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License.
|
Public License instead of this License.
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This will make sure the app is always imported when
|
# This will make sure the app is always imported when
|
||||||
# Django starts so that shared_task will use this app.
|
# Django starts so that shared_task will use this app.
|
||||||
|
|
||||||
__version__ = '2.8.7'
|
__version__ = '2.9.0'
|
||||||
__title__ = 'Alliance Auth'
|
__title__ = 'Alliance Auth'
|
||||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||||
NAME = '%s v%s' % (__title__, __version__)
|
NAME = '%s v%s' % (__title__, __version__)
|
||||||
|
1
allianceauth/analytics/__init__.py
Normal file
1
allianceauth/analytics/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'allianceauth.analytics.apps.AnalyticsConfig'
|
21
allianceauth/analytics/admin.py
Normal file
21
allianceauth/analytics/admin.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AnalyticsIdentifier)
|
||||||
|
class AnalyticsIdentifierAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['identifier', ]
|
||||||
|
list_display = ('identifier',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AnalyticsTokens)
|
||||||
|
class AnalyticsTokensAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['name', ]
|
||||||
|
list_display = ('name', 'type',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(AnalyticsPath)
|
||||||
|
class AnalyticsPathAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['ignore_path', ]
|
||||||
|
list_display = ('ignore_path',)
|
9
allianceauth/analytics/apps.py
Normal file
9
allianceauth/analytics/apps.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyticsConfig(AppConfig):
|
||||||
|
name = 'allianceauth.analytics'
|
||||||
|
label = 'analytics'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import allianceauth.analytics.signals
|
21
allianceauth/analytics/fixtures/disable_analytics.json
Normal file
21
allianceauth/analytics/fixtures/disable_analytics.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "analytics.AnalyticsTokens",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "AA Team Public Google Analytics (Universal)",
|
||||||
|
"type": "GA-V4",
|
||||||
|
"token": "UA-186249766-2",
|
||||||
|
"send_page_views": "False",
|
||||||
|
"send_celery_tasks": "False",
|
||||||
|
"send_stats": "False"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "analytics.AnalyticsIdentifier",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"identifier": "ab33e241fbf042b6aa77c7655a768af7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
49
allianceauth/analytics/middleware.py
Normal file
49
allianceauth/analytics/middleware.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
from .models import AnalyticsTokens, AnalyticsIdentifier
|
||||||
|
from .tasks import send_ga_tracking_web_view
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyticsMiddleware(MiddlewareMixin):
|
||||||
|
def process_response(self, request, response):
|
||||||
|
"""Django Middleware: Process Page Views and creates Analytics Celery Tasks"""
|
||||||
|
analyticstokens = AnalyticsTokens.objects.all()
|
||||||
|
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
|
||||||
|
try:
|
||||||
|
title = BeautifulSoup(
|
||||||
|
response.content, "html.parser").html.head.title.text
|
||||||
|
except AttributeError:
|
||||||
|
title = ''
|
||||||
|
for token in analyticstokens:
|
||||||
|
# Check if Page View Sending is Disabled
|
||||||
|
if token.send_page_views is False:
|
||||||
|
continue
|
||||||
|
# Check Exclusions
|
||||||
|
ignore = False
|
||||||
|
for ignore_path in token.ignore_paths.values():
|
||||||
|
ignore_path_regex = re.compile(ignore_path["ignore_path"])
|
||||||
|
if re.search(ignore_path_regex, request.path) is not None:
|
||||||
|
ignore = True
|
||||||
|
|
||||||
|
if ignore is True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tracking_id = token.token
|
||||||
|
locale = request.LANGUAGE_CODE
|
||||||
|
path = request.path
|
||||||
|
try:
|
||||||
|
useragent = request.headers["User-Agent"]
|
||||||
|
except KeyError:
|
||||||
|
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
|
||||||
|
send_ga_tracking_web_view.s(tracking_id=tracking_id,
|
||||||
|
client_id=client_id,
|
||||||
|
page=path,
|
||||||
|
title=title,
|
||||||
|
locale=locale,
|
||||||
|
useragent=useragent).\
|
||||||
|
apply_async(priority=9)
|
||||||
|
return response
|
42
allianceauth/analytics/migrations/0001_initial.py
Normal file
42
allianceauth/analytics/migrations/0001_initial.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Generated by Django 3.1.4 on 2020-12-30 13:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AnalyticsIdentifier',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('identifier', models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AnalyticsPath',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ignore_path', models.CharField(default='/example/', max_length=254)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AnalyticsTokens',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=254)),
|
||||||
|
('type', models.CharField(choices=[('GA-U', 'Google Analytics Universal'), ('GA-V4', 'Google Analytics V4')], max_length=254)),
|
||||||
|
('token', models.CharField(max_length=254)),
|
||||||
|
('send_page_views', models.BooleanField(default=False)),
|
||||||
|
('send_celery_tasks', models.BooleanField(default=False)),
|
||||||
|
('send_stats', models.BooleanField(default=False)),
|
||||||
|
('ignore_paths', models.ManyToManyField(blank=True, to='analytics.AnalyticsPath')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
34
allianceauth/analytics/migrations/0002_add_AA_Team_Token.py
Normal file
34
allianceauth/analytics/migrations/0002_add_AA_Team_Token.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 3.1.4 on 2020-12-30 08:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def add_aa_team_token(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||||
|
token = Tokens()
|
||||||
|
token.type = 'GA-U'
|
||||||
|
token.token = 'UA-186249766-2'
|
||||||
|
token.send_page_views = True
|
||||||
|
token.send_celery_tasks = True
|
||||||
|
token.send_stats = True
|
||||||
|
token.name = 'AA Team Public Google Analytics (Universal)'
|
||||||
|
token.save()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_aa_team_token(apps, schema_editor):
|
||||||
|
# Have to define some code to remove this identifier
|
||||||
|
# In case of migration rollback?
|
||||||
|
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||||
|
token = Tokens.objects.filter(token="UA-186249766-2").delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('analytics', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(add_aa_team_token, remove_aa_team_token)
|
||||||
|
]
|
@ -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)
|
||||||
|
]
|
31
allianceauth/analytics/migrations/0004_auto_20211015_0502.py
Normal file
31
allianceauth/analytics/migrations/0004_auto_20211015_0502.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 3.1.13 on 2021-10-15 05:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
|
||||||
|
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
|
||||||
|
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
|
||||||
|
user_notifications_count = AnalyticsPath.objects.create(ignore_path=r"^\/user_notifications_count\/.*")
|
||||||
|
|
||||||
|
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||||
|
token = Tokens.objects.get(token="UA-186249766-2")
|
||||||
|
token.ignore_paths.add(admin, user_notifications_count)
|
||||||
|
|
||||||
|
|
||||||
|
def undo_modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
|
||||||
|
# nothing should need to migrate away here?
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('analytics', '0003_Generate_Identifier'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(modify_aa_team_token_add_page_ignore_paths, undo_modify_aa_team_token_add_page_ignore_paths)
|
||||||
|
]
|
0
allianceauth/analytics/migrations/__init__.py
Normal file
0
allianceauth/analytics/migrations/__init__.py
Normal file
38
allianceauth/analytics/models.py
Normal file
38
allianceauth/analytics/models.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyticsIdentifier(models.Model):
|
||||||
|
|
||||||
|
identifier = models.UUIDField(default=uuid4,
|
||||||
|
editable=False)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.pk and AnalyticsIdentifier.objects.exists():
|
||||||
|
# Force a single object
|
||||||
|
raise ValidationError('There is can be only one \
|
||||||
|
AnalyticsIdentifier instance')
|
||||||
|
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
|
||||||
|
return super(AnalyticsIdentifier, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyticsPath(models.Model):
|
||||||
|
ignore_path = models.CharField(max_length=254, default="/example/", help_text="Regex Expression, If matched no Analytics Page View is sent")
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyticsTokens(models.Model):
|
||||||
|
|
||||||
|
class Analytics_Type(models.TextChoices):
|
||||||
|
GA_U = 'GA-U', _('Google Analytics Universal')
|
||||||
|
GA_V4 = 'GA-V4', _('Google Analytics V4')
|
||||||
|
|
||||||
|
name = models.CharField(max_length=254)
|
||||||
|
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
|
||||||
|
token = models.CharField(max_length=254, blank=False)
|
||||||
|
send_page_views = models.BooleanField(default=False)
|
||||||
|
send_celery_tasks = models.BooleanField(default=False)
|
||||||
|
send_stats = models.BooleanField(default=False)
|
||||||
|
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)
|
50
allianceauth/analytics/signals.py
Normal file
50
allianceauth/analytics/signals.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from allianceauth.analytics.tasks import analytics_event
|
||||||
|
from celery.signals import task_failure, task_success
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@task_failure.connect
|
||||||
|
def process_failure_signal(
|
||||||
|
exception, traceback,
|
||||||
|
sender, task_id, signal,
|
||||||
|
args, kwargs, einfo, **kw):
|
||||||
|
logger.debug("Celery task_failure signal %s" % sender.__class__.__name__)
|
||||||
|
|
||||||
|
category = sender.__module__
|
||||||
|
|
||||||
|
if 'allianceauth.analytics' not in category:
|
||||||
|
if category.endswith(".tasks"):
|
||||||
|
category = category[:-6]
|
||||||
|
|
||||||
|
action = sender.__name__
|
||||||
|
|
||||||
|
label = f"{exception.__class__.__name__}"
|
||||||
|
|
||||||
|
analytics_event(category=category,
|
||||||
|
action=action,
|
||||||
|
label=label)
|
||||||
|
|
||||||
|
|
||||||
|
@task_success.connect
|
||||||
|
def celery_success_signal(sender, result=None, **kw):
|
||||||
|
logger.debug("Celery task_success signal %s" % sender.__class__.__name__)
|
||||||
|
|
||||||
|
category = sender.__module__
|
||||||
|
|
||||||
|
if 'allianceauth.analytics' not in category:
|
||||||
|
if category.endswith(".tasks"):
|
||||||
|
category = category[:-6]
|
||||||
|
|
||||||
|
action = sender.__name__
|
||||||
|
label = "Success"
|
||||||
|
|
||||||
|
value = 0
|
||||||
|
if isinstance(result, int):
|
||||||
|
value = result
|
||||||
|
|
||||||
|
analytics_event(category=category,
|
||||||
|
action=action,
|
||||||
|
label=label,
|
||||||
|
value=value)
|
207
allianceauth/analytics/tasks.py
Normal file
207
allianceauth/analytics/tasks.py
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from django.conf import settings
|
||||||
|
from django.apps import apps
|
||||||
|
from celery import shared_task
|
||||||
|
from allianceauth import __version__
|
||||||
|
from .models import AnalyticsTokens, AnalyticsIdentifier
|
||||||
|
from .utils import (
|
||||||
|
install_stat_addons,
|
||||||
|
install_stat_tokens,
|
||||||
|
install_stat_users)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
BASE_URL = "https://www.google-analytics.com/"
|
||||||
|
|
||||||
|
DEBUG_URL = f"{BASE_URL}debug/collect"
|
||||||
|
COLLECTION_URL = f"{BASE_URL}collect"
|
||||||
|
|
||||||
|
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
|
||||||
|
# Force sending of analytics data during in a debug/test environemt
|
||||||
|
# Usefull for developers working on this feature.
|
||||||
|
logger.warning(
|
||||||
|
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
|
||||||
|
"This debug instance will send analytics data!")
|
||||||
|
DEBUG_URL = COLLECTION_URL
|
||||||
|
|
||||||
|
ANALYTICS_URL = COLLECTION_URL
|
||||||
|
|
||||||
|
if settings.DEBUG is True:
|
||||||
|
ANALYTICS_URL = DEBUG_URL
|
||||||
|
|
||||||
|
|
||||||
|
def analytics_event(category: str,
|
||||||
|
action: str,
|
||||||
|
label: str,
|
||||||
|
value: int = 0,
|
||||||
|
event_type: str = 'Celery'):
|
||||||
|
"""
|
||||||
|
Send a Google Analytics Event for each token stored
|
||||||
|
Includes check for if its enabled/disabled
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-------
|
||||||
|
`category` (str): Celery Namespace
|
||||||
|
`action` (str): Task Name
|
||||||
|
`label` (str): Optional, Task Success/Exception
|
||||||
|
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
||||||
|
`event_type` (str): Optional, Celery or Stats only, Default to Celery
|
||||||
|
"""
|
||||||
|
analyticstokens = AnalyticsTokens.objects.all()
|
||||||
|
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
|
||||||
|
for token in analyticstokens:
|
||||||
|
if event_type == 'Celery':
|
||||||
|
allowed = token.send_celery_tasks
|
||||||
|
elif event_type == 'Stats':
|
||||||
|
allowed = token.send_stats
|
||||||
|
else:
|
||||||
|
allowed = False
|
||||||
|
|
||||||
|
if allowed is True:
|
||||||
|
tracking_id = token.token
|
||||||
|
send_ga_tracking_celery_event.s(tracking_id=tracking_id,
|
||||||
|
client_id=client_id,
|
||||||
|
category=category,
|
||||||
|
action=action,
|
||||||
|
label=label,
|
||||||
|
value=value).\
|
||||||
|
apply_async(priority=9)
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task()
|
||||||
|
def analytics_daily_stats():
|
||||||
|
"""Celery Task: Do not call directly
|
||||||
|
|
||||||
|
Gathers a series of daily statistics and sends analytics events containing them"""
|
||||||
|
users = install_stat_users()
|
||||||
|
tokens = install_stat_tokens()
|
||||||
|
addons = install_stat_addons()
|
||||||
|
logger.debug("Running Daily Analytics Upload")
|
||||||
|
|
||||||
|
analytics_event(category='allianceauth.analytics',
|
||||||
|
action='send_install_stats',
|
||||||
|
label='existence',
|
||||||
|
value=1,
|
||||||
|
event_type='Stats')
|
||||||
|
analytics_event(category='allianceauth.analytics',
|
||||||
|
action='send_install_stats',
|
||||||
|
label='users',
|
||||||
|
value=users,
|
||||||
|
event_type='Stats')
|
||||||
|
analytics_event(category='allianceauth.analytics',
|
||||||
|
action='send_install_stats',
|
||||||
|
label='tokens',
|
||||||
|
value=tokens,
|
||||||
|
event_type='Stats')
|
||||||
|
analytics_event(category='allianceauth.analytics',
|
||||||
|
action='send_install_stats',
|
||||||
|
label='addons',
|
||||||
|
value=addons,
|
||||||
|
event_type='Stats')
|
||||||
|
|
||||||
|
for appconfig in apps.get_app_configs():
|
||||||
|
analytics_event(category='allianceauth.analytics',
|
||||||
|
action='send_extension_stats',
|
||||||
|
label=appconfig.label,
|
||||||
|
value=1,
|
||||||
|
event_type='Stats')
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task()
|
||||||
|
def send_ga_tracking_web_view(
|
||||||
|
tracking_id: str,
|
||||||
|
client_id: str,
|
||||||
|
page: str,
|
||||||
|
title: str,
|
||||||
|
locale: str,
|
||||||
|
useragent: str) -> requests.Response:
|
||||||
|
|
||||||
|
"""Celery Task: Do not call directly
|
||||||
|
|
||||||
|
Sends Page View events to GA, Called only via analytics.middleware
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
`tracking_id` (str): Unique Server Identifier
|
||||||
|
`client_id` (str): GA Token
|
||||||
|
`page` (str): Page Path
|
||||||
|
`title` (str): Page Title
|
||||||
|
`locale` (str): Browser Language
|
||||||
|
`useragent` (str): Browser UserAgent
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
requests.Reponse Object
|
||||||
|
"""
|
||||||
|
headers = {"User-Agent": useragent}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'v': '1',
|
||||||
|
'tid': tracking_id,
|
||||||
|
'cid': client_id,
|
||||||
|
't': 'pageview',
|
||||||
|
'dp': page,
|
||||||
|
'dt': title,
|
||||||
|
'ul': locale,
|
||||||
|
'ua': useragent,
|
||||||
|
'aip': 1,
|
||||||
|
'an': "allianceauth",
|
||||||
|
'av': __version__
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
ANALYTICS_URL, data=payload,
|
||||||
|
timeout=5, headers=headers)
|
||||||
|
logger.debug(f"Analytics Page View HTTP{response.status_code}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task()
|
||||||
|
def send_ga_tracking_celery_event(
|
||||||
|
tracking_id: str,
|
||||||
|
client_id: str,
|
||||||
|
category: str,
|
||||||
|
action: str,
|
||||||
|
label: str,
|
||||||
|
value: int) -> requests.Response:
|
||||||
|
"""Celery Task: Do not call directly
|
||||||
|
|
||||||
|
Sends Page View events to GA, Called only via analytics.middleware
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
`tracking_id` (str): Unique Server Identifier
|
||||||
|
`client_id` (str): GA Token
|
||||||
|
`category` (str): Celery Namespace
|
||||||
|
`action` (str): Task Name
|
||||||
|
`label` (str): Optional, Task Success/Exception
|
||||||
|
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
requests.Reponse Object
|
||||||
|
"""
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'v': '1',
|
||||||
|
'tid': tracking_id,
|
||||||
|
'cid': client_id,
|
||||||
|
't': 'event',
|
||||||
|
'ec': category,
|
||||||
|
'ea': action,
|
||||||
|
'el': label,
|
||||||
|
'ev': value,
|
||||||
|
'aip': 1,
|
||||||
|
'an': "allianceauth",
|
||||||
|
'av': __version__
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
ANALYTICS_URL, data=payload,
|
||||||
|
timeout=5, headers=headers)
|
||||||
|
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
|
||||||
|
return response
|
0
allianceauth/analytics/tests/__init__.py
Normal file
0
allianceauth/analytics/tests/__init__.py
Normal file
23
allianceauth/analytics/tests/test_middleware.py
Normal file
23
allianceauth/analytics/tests/test_middleware.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from allianceauth.analytics.middleware import AnalyticsMiddleware
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from django.test.testcases import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnalyticsMiddleware(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.middleware = AnalyticsMiddleware()
|
||||||
|
self.request = Mock()
|
||||||
|
self.request.headers = {
|
||||||
|
"User-Agent": "AUTOMATED TEST"
|
||||||
|
}
|
||||||
|
self.request.path = '/testURL/'
|
||||||
|
self.request.session = {}
|
||||||
|
self.request.LANGUAGE_CODE = 'en'
|
||||||
|
self.response = Mock()
|
||||||
|
self.response.content = 'hello world'
|
||||||
|
|
||||||
|
def test_middleware(self):
|
||||||
|
response = self.middleware.process_response(self.request, self.response)
|
||||||
|
self.assertEqual(self.response, response)
|
26
allianceauth/analytics/tests/test_models.py
Normal file
26
allianceauth/analytics/tests/test_models.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from allianceauth.analytics.models import AnalyticsIdentifier
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
from django.test.testcases import TestCase
|
||||||
|
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
|
||||||
|
# Identifiers
|
||||||
|
uuid_1 = "ab33e241fbf042b6aa77c7655a768af7"
|
||||||
|
uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnalyticsIdentifier(TestCase):
|
||||||
|
|
||||||
|
def test_identifier_random(self):
|
||||||
|
self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4)
|
||||||
|
|
||||||
|
def test_identifier_singular(self):
|
||||||
|
AnalyticsIdentifier.objects.all().delete()
|
||||||
|
AnalyticsIdentifier.objects.create(identifier=uuid_1)
|
||||||
|
# Yeah i have multiple asserts here, they all do the same thing
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
AnalyticsIdentifier.objects.create(identifier=uuid_2)
|
||||||
|
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
|
||||||
|
self.assertEqual(AnalyticsIdentifier.objects.get(pk=1).identifier, UUID(uuid_1))
|
119
allianceauth/analytics/tests/test_tasks.py
Normal file
119
allianceauth/analytics/tests/test_tasks.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
from allianceauth.analytics.tasks import (
|
||||||
|
analytics_event,
|
||||||
|
send_ga_tracking_celery_event,
|
||||||
|
send_ga_tracking_web_view)
|
||||||
|
from django.test.testcases import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnalyticsTasks(TestCase):
|
||||||
|
def test_analytics_event(self):
|
||||||
|
analytics_event(
|
||||||
|
category='allianceauth.analytics',
|
||||||
|
action='send_tests',
|
||||||
|
label='test',
|
||||||
|
value=1,
|
||||||
|
event_type='Stats')
|
||||||
|
|
||||||
|
def test_send_ga_tracking_web_view_sent(self):
|
||||||
|
# This test sends if the event SENDS to google
|
||||||
|
# Not if it was successful
|
||||||
|
tracking_id = 'UA-186249766-2'
|
||||||
|
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||||
|
page = '/index/'
|
||||||
|
title = 'Hello World'
|
||||||
|
locale = 'en'
|
||||||
|
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
response = send_ga_tracking_web_view(
|
||||||
|
tracking_id,
|
||||||
|
client_id,
|
||||||
|
page,
|
||||||
|
title,
|
||||||
|
locale,
|
||||||
|
useragent)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_send_ga_tracking_web_view_success(self):
|
||||||
|
tracking_id = 'UA-186249766-2'
|
||||||
|
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||||
|
page = '/index/'
|
||||||
|
title = 'Hello World'
|
||||||
|
locale = 'en'
|
||||||
|
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
json_response = send_ga_tracking_web_view(
|
||||||
|
tracking_id,
|
||||||
|
client_id,
|
||||||
|
page,
|
||||||
|
title,
|
||||||
|
locale,
|
||||||
|
useragent).json()
|
||||||
|
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
||||||
|
|
||||||
|
def test_send_ga_tracking_web_view_invalid_token(self):
|
||||||
|
tracking_id = 'UA-IntentionallyBadTrackingID-2'
|
||||||
|
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||||
|
page = '/index/'
|
||||||
|
title = 'Hello World'
|
||||||
|
locale = 'en'
|
||||||
|
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
json_response = send_ga_tracking_web_view(
|
||||||
|
tracking_id,
|
||||||
|
client_id,
|
||||||
|
page,
|
||||||
|
title,
|
||||||
|
locale,
|
||||||
|
useragent).json()
|
||||||
|
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
|
||||||
|
self.assertEqual(json_response["hitParsingResult"][0]["parserMessage"][1]["description"], "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.")
|
||||||
|
|
||||||
|
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
|
||||||
|
|
||||||
|
def test_send_ga_tracking_celery_event_sent(self):
|
||||||
|
tracking_id = 'UA-186249766-2'
|
||||||
|
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||||
|
category = 'test'
|
||||||
|
action = 'test'
|
||||||
|
label = 'test'
|
||||||
|
value = '1'
|
||||||
|
response = send_ga_tracking_celery_event(
|
||||||
|
tracking_id,
|
||||||
|
client_id,
|
||||||
|
category,
|
||||||
|
action,
|
||||||
|
label,
|
||||||
|
value)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_send_ga_tracking_celery_event_success(self):
|
||||||
|
tracking_id = 'UA-186249766-2'
|
||||||
|
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||||
|
category = 'test'
|
||||||
|
action = 'test'
|
||||||
|
label = 'test'
|
||||||
|
value = '1'
|
||||||
|
json_response = send_ga_tracking_celery_event(
|
||||||
|
tracking_id,
|
||||||
|
client_id,
|
||||||
|
category,
|
||||||
|
action,
|
||||||
|
label,
|
||||||
|
value).json()
|
||||||
|
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
||||||
|
|
||||||
|
def test_send_ga_tracking_celery_event_invalid_token(self):
|
||||||
|
tracking_id = 'UA-IntentionallyBadTrackingID-2'
|
||||||
|
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||||
|
category = 'test'
|
||||||
|
action = 'test'
|
||||||
|
label = 'test'
|
||||||
|
value = '1'
|
||||||
|
json_response = send_ga_tracking_celery_event(
|
||||||
|
tracking_id,
|
||||||
|
client_id,
|
||||||
|
category,
|
||||||
|
action,
|
||||||
|
label,
|
||||||
|
value).json()
|
||||||
|
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
|
||||||
|
self.assertEqual(json_response["hitParsingResult"][0]["parserMessage"][1]["description"], "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.")
|
||||||
|
|
||||||
|
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
|
55
allianceauth/analytics/tests/test_utils.py
Normal file
55
allianceauth/analytics/tests/test_utils.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
from allianceauth.authentication.models import User
|
||||||
|
from esi.models import Token
|
||||||
|
from allianceauth.analytics.utils import install_stat_users, install_stat_tokens, install_stat_addons
|
||||||
|
|
||||||
|
from django.test.testcases import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
def create_testdata():
|
||||||
|
User.objects.all().delete()
|
||||||
|
User.objects.create_user(
|
||||||
|
'user_1'
|
||||||
|
'abc@example.com',
|
||||||
|
'password'
|
||||||
|
)
|
||||||
|
User.objects.create_user(
|
||||||
|
'user_2'
|
||||||
|
'abc@example.com',
|
||||||
|
'password'
|
||||||
|
)
|
||||||
|
#Token.objects.all().delete()
|
||||||
|
#Token.objects.create(
|
||||||
|
# character_id=101,
|
||||||
|
# character_name='character1',
|
||||||
|
# access_token='my_access_token'
|
||||||
|
#)
|
||||||
|
#Token.objects.create(
|
||||||
|
# character_id=102,
|
||||||
|
# character_name='character2',
|
||||||
|
# access_token='my_access_token'
|
||||||
|
#)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnalyticsUtils(TestCase):
|
||||||
|
|
||||||
|
def test_install_stat_users(self):
|
||||||
|
create_testdata()
|
||||||
|
expected = 2
|
||||||
|
|
||||||
|
users = install_stat_users()
|
||||||
|
self.assertEqual(users, expected)
|
||||||
|
|
||||||
|
#def test_install_stat_tokens(self):
|
||||||
|
# create_testdata()
|
||||||
|
# expected = 2
|
||||||
|
#
|
||||||
|
# tokens = install_stat_tokens()
|
||||||
|
# self.assertEqual(tokens, expected)
|
||||||
|
|
||||||
|
def test_install_stat_addons(self):
|
||||||
|
# this test does what its testing...
|
||||||
|
# but helpful for existing as a sanity check
|
||||||
|
expected = len(list(apps.get_app_configs()))
|
||||||
|
addons = install_stat_addons()
|
||||||
|
self.assertEqual(addons, expected)
|
36
allianceauth/analytics/utils.py
Normal file
36
allianceauth/analytics/utils.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from django.apps import apps
|
||||||
|
from allianceauth.authentication.models import User
|
||||||
|
from esi.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
def install_stat_users() -> int:
|
||||||
|
"""Count and Return the number of User accounts
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
The Number of User objects"""
|
||||||
|
users = User.objects.count()
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
def install_stat_tokens() -> int:
|
||||||
|
"""Count and Return the number of ESI Tokens Stored
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
The Number of Token Objects"""
|
||||||
|
tokens = Token.objects.count()
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
|
def install_stat_addons() -> int:
|
||||||
|
"""Count and Return the number of Django Applications Installed
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
The Number of Installed Apps"""
|
||||||
|
addons = len(list(apps.get_app_configs()))
|
||||||
|
return addons
|
@ -43,4 +43,3 @@ AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \
|
|||||||
|
|
||||||
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
|
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
|
||||||
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)
|
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)
|
||||||
|
|
||||||
|
@ -23,8 +23,7 @@ class CharacterOwnershipManager(Manager):
|
|||||||
def create_by_token(self, token):
|
def create_by_token(self, token):
|
||||||
if not EveCharacter.objects.filter(character_id=token.character_id).exists():
|
if not EveCharacter.objects.filter(character_id=token.character_id).exists():
|
||||||
EveCharacter.objects.create_character(token.character_id)
|
EveCharacter.objects.create_character(token.character_id)
|
||||||
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user,
|
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user, owner_hash=token.character_owner_hash)
|
||||||
owner_hash=token.character_owner_hash)
|
|
||||||
|
|
||||||
|
|
||||||
class StateQuerySet(QuerySet):
|
class StateQuerySet(QuerySet):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-05 21:38
|
# Generated by Django 1.10.1 on 2016-09-05 21:38
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-07 19:14
|
# Generated by Django 1.10.1 on 2016-09-07 19:14
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-09 20:29
|
# Generated by Django 1.10.1 on 2016-09-09 20:29
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-09 23:19
|
# Generated by Django 1.10.1 on 2016-09-09 23:19
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-09 23:11
|
# Generated by Django 1.10.1 on 2016-09-09 23:11
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-10 05:42
|
# Generated by Django 1.10.1 on 2016-09-10 05:42
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-10 21:50
|
# Generated by Django 1.10.1 on 2016-09-10 21:50
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-12 13:04
|
# Generated by Django 1.10.1 on 2016-09-12 13:04
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.2 on 2016-10-21 02:28
|
# Generated by Django 1.10.2 on 2016-10-21 02:28
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2017-01-07 06:47
|
# Generated by Django 1.10.1 on 2017-01-07 06:47
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2017-01-07 07:11
|
# Generated by Django 1.10.1 on 2017-01-07 07:11
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-01-12 00:59
|
# Generated by Django 1.10.5 on 2017-01-12 00:59
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.2 on 2016-12-11 23:14
|
# Generated by Django 1.10.2 on 2016-12-11 23:14
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-09 23:19
|
# Generated by Django 1.10.1 on 2016-09-09 23:19
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-22 23:09
|
# Generated by Django 1.10.5 on 2017-03-22 23:09
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
@ -171,8 +170,7 @@ def recreate_authservicesinfo(apps, schema_editor):
|
|||||||
|
|
||||||
# repopulate main characters
|
# repopulate main characters
|
||||||
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
||||||
AuthServicesInfo.objects.update_or_create(user=profile.user,
|
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'main_char_id': profile.main_character.character_id})
|
||||||
defaults={'main_char_id': profile.main_character.character_id})
|
|
||||||
|
|
||||||
# repopulate states we understand
|
# repopulate states we understand
|
||||||
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
|
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
@ -14,15 +14,11 @@ logger = logging.getLogger(__name__)
|
|||||||
class State(models.Model):
|
class State(models.Model):
|
||||||
name = models.CharField(max_length=20, unique=True)
|
name = models.CharField(max_length=20, unique=True)
|
||||||
permissions = models.ManyToManyField(Permission, blank=True)
|
permissions = models.ManyToManyField(Permission, blank=True)
|
||||||
priority = models.IntegerField(unique=True,
|
priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.")
|
||||||
help_text="Users get assigned the state with the highest priority available to them.")
|
|
||||||
|
|
||||||
member_characters = models.ManyToManyField(EveCharacter, blank=True,
|
member_characters = models.ManyToManyField(EveCharacter, blank=True, help_text="Characters to which this state is available.")
|
||||||
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_corporations = models.ManyToManyField(EveCorporationInfo, blank=True,
|
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, help_text="Alliances to whose members this state is available.")
|
||||||
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.")
|
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
|
||||||
|
|
||||||
objects = StateManager()
|
objects = StateManager()
|
||||||
@ -81,6 +77,11 @@ class UserProfile(models.Model):
|
|||||||
'info'
|
'info'
|
||||||
)
|
)
|
||||||
from allianceauth.authentication.signals import state_changed
|
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(
|
state_changed.send(
|
||||||
sender=self.__class__, user=self.user, state=self.state
|
sender=self.__class__, user=self.user, state=self.state
|
||||||
)
|
)
|
||||||
|
@ -75,8 +75,7 @@ def create_required_models(sender, instance, created, *args, **kwargs):
|
|||||||
@receiver(post_save, sender=Token)
|
@receiver(post_save, sender=Token)
|
||||||
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
|
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user, instance.character_name))
|
||||||
instance.character_name))
|
|
||||||
if instance.user:
|
if instance.user:
|
||||||
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
||||||
else:
|
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()
|
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
|
||||||
# create character if needed
|
# create character if needed
|
||||||
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
|
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,
|
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name, instance.character_id))
|
||||||
instance.character_id))
|
|
||||||
EveCharacter.objects.create_character(instance.character_id)
|
EveCharacter.objects.create_character(instance.character_id)
|
||||||
char = EveCharacter.objects.get(character_id=instance.character_id)
|
char = EveCharacter.objects.get(character_id=instance.character_id)
|
||||||
# check if we need to create ownership
|
# check if we need to create ownership
|
||||||
if instance.user and not CharacterOwnership.objects.filter(
|
if instance.user and not CharacterOwnership.objects.filter(
|
||||||
character__character_id=instance.character_id).exists():
|
character__character_id=instance.character_id).exists():
|
||||||
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name,
|
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name, instance.user))
|
||||||
instance.user))
|
CharacterOwnership.objects.update_or_create(character=char, defaults={'owner_hash': instance.character_owner_hash, 'user': instance.user})
|
||||||
CharacterOwnership.objects.update_or_create(character=char,
|
|
||||||
defaults={'owner_hash': instance.character_owner_hash,
|
|
||||||
'user': instance.user})
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=CharacterOwnership)
|
@receiver(pre_delete, sender=CharacterOwnership)
|
||||||
|
@ -6,11 +6,13 @@ from allianceauth.corputils import urls
|
|||||||
|
|
||||||
class CorpStats(MenuItemHook):
|
class CorpStats(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(
|
||||||
|
self,
|
||||||
_('Corporation Stats'),
|
_('Corporation Stats'),
|
||||||
'fas fa-share-alt fa-fw',
|
'fas fa-share-alt fa-fw',
|
||||||
'corputils:view',
|
'corputils:view',
|
||||||
navactive=['corputils:'])
|
navactive=['corputils:']
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm(
|
if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm(
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-12-14 21:36
|
# Generated by Django 1.10.1 on 2016-12-14 21:36
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-12-14 21:48
|
# Generated by Django 1.10.1 on 2016-12-14 21:48
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-26 20:13
|
# Generated by Django 1.10.5 on 2017-03-26 20:13
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
@ -13,8 +12,7 @@ def convert_json_to_members(apps, schema_editor):
|
|||||||
for cs in CorpStats.objects.all():
|
for cs in CorpStats.objects.all():
|
||||||
members = json.loads(cs._members)
|
members = json.loads(cs._members)
|
||||||
CorpMember.objects.bulk_create(
|
CorpMember.objects.bulk_create(
|
||||||
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in
|
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in members.items()]
|
||||||
members.items()]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.2 on 2017-06-10 15:34
|
# Generated by Django 1.11.2 on 2017-06-10 15:34
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ from bravado.exception import HTTPForbidden
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from esi.errors import TokenError
|
from esi.errors import TokenError
|
||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter,\
|
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter, EveAllianceInfo
|
||||||
EveAllianceInfo
|
|
||||||
from allianceauth.notifications import notify
|
from allianceauth.notifications import notify
|
||||||
|
|
||||||
from allianceauth.corputils.managers import CorpStatsManager
|
from allianceauth.corputils.managers import CorpStatsManager
|
||||||
@ -49,8 +48,7 @@ class CorpStats(models.Model):
|
|||||||
def update(self):
|
def update(self):
|
||||||
try:
|
try:
|
||||||
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||||
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()['corporation_id'] == int(self.corp.corporation_id)
|
||||||
'corporation_id'] == int(self.corp.corporation_id)
|
|
||||||
member_ids = c.Corporation.get_corporations_corporation_id_members(
|
member_ids = c.Corporation.get_corporations_corporation_id_members(
|
||||||
corporation_id=self.corp.corporation_id).result()
|
corporation_id=self.corp.corporation_id).result()
|
||||||
|
|
||||||
@ -58,18 +56,15 @@ class CorpStats(models.Model):
|
|||||||
# the swagger spec doesn't have a maxItems count
|
# the swagger spec doesn't have a maxItems count
|
||||||
# manual testing says we can do over 350, but let's not risk it
|
# manual testing says we can do over 350, but let's not risk it
|
||||||
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
|
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
|
||||||
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in
|
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in member_id_chunks]
|
||||||
member_id_chunks]
|
|
||||||
member_list = {}
|
member_list = {}
|
||||||
for name_chunk in member_name_chunks:
|
for name_chunk in member_name_chunks:
|
||||||
member_list.update({m['id']: m['name'] for m in name_chunk})
|
member_list.update({m['id']: m['name'] for m in name_chunk})
|
||||||
|
|
||||||
# bulk create new member models
|
# bulk create new member models
|
||||||
missing_members = [m_id for m_id in member_ids if
|
missing_members = [m_id for m_id in member_ids if not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
|
||||||
not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
|
|
||||||
CorpMember.objects.bulk_create(
|
CorpMember.objects.bulk_create(
|
||||||
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in
|
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in missing_members])
|
||||||
missing_members])
|
|
||||||
|
|
||||||
# purge old members
|
# purge old members
|
||||||
self.members.exclude(character_id__in=member_ids).delete()
|
self.members.exclude(character_id__in=member_ids).delete()
|
||||||
@ -80,20 +75,21 @@ class CorpStats(models.Model):
|
|||||||
except TokenError as e:
|
except TokenError as e:
|
||||||
logger.warning("%s failed to update: %s" % (self, e))
|
logger.warning("%s failed to update: %s" % (self, e))
|
||||||
if self.token.user:
|
if self.token.user:
|
||||||
notify(self.token.user, "%s failed to update with your ESI token." % self,
|
notify(
|
||||||
|
self.token.user, "%s failed to update with your ESI token." % self,
|
||||||
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
|
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
|
||||||
level="error")
|
level="error")
|
||||||
self.delete()
|
self.delete()
|
||||||
except HTTPForbidden as e:
|
except HTTPForbidden as e:
|
||||||
logger.warning("%s failed to update: %s" % (self, e))
|
logger.warning("%s failed to update: %s" % (self, e))
|
||||||
if self.token.user:
|
if self.token.user:
|
||||||
notify(self.token.user, "%s failed to update with your ESI token." % self,
|
notify(self.token.user, "%s failed to update with your ESI token." % self, message="%s: %s" % (e.status_code, e.message), level="error")
|
||||||
message="%s: %s" % (e.status_code, e.message), level="error")
|
|
||||||
self.delete()
|
self.delete()
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
logger.warning("%s token character no longer in corp." % self)
|
logger.warning("%s token character no longer in corp." % self)
|
||||||
if self.token.user:
|
if self.token.user:
|
||||||
notify(self.token.user, "%s cannot update with your ESI token." % self,
|
notify(
|
||||||
|
self.token.user, "%s cannot update with your ESI token." % self,
|
||||||
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
|
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
@ -127,9 +123,7 @@ class CorpStats(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def mains(self):
|
def mains(self):
|
||||||
return self.members.filter(pk__in=[m.pk for m in self.members.all() if
|
return self.members.filter(pk__in=[m.pk for m in self.members.all() if m.main_character and int(m.main_character.character_id) == int(m.character_id)])
|
||||||
m.main_character and int(m.main_character.character_id) == int(
|
|
||||||
m.character_id)])
|
|
||||||
|
|
||||||
def visible_to(self, user):
|
def visible_to(self, user):
|
||||||
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
|
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
|
||||||
|
@ -58,8 +58,7 @@
|
|||||||
{% for id, main in mains.items %}
|
{% for id, main in mains.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center" style="vertical-align:middle">
|
<td class="text-center" style="vertical-align:middle">
|
||||||
<div class="thumbnail"
|
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||||
style="border: 0 none; box-shadow: none; background: transparent;">
|
|
||||||
<img src="{{ main.main.portrait_url_64 }}" class="img-circle">
|
<img src="{{ main.main.portrait_url_64 }}" class="img-circle">
|
||||||
<div class="caption text-center">
|
<div class="caption text-center">
|
||||||
{{ main.main }}
|
{{ main.main }}
|
||||||
@ -88,8 +87,7 @@
|
|||||||
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
||||||
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
||||||
<td class="text-center" style="width:5%">
|
<td class="text-center" style="width:5%">
|
||||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/"
|
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
|
||||||
class="label label-danger" target="_blank">
|
|
||||||
{% trans "Killboard" %}
|
{% trans "Killboard" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -123,10 +121,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||||
<td class="text-center">{{ member }}</td>
|
<td class="text-center">{{ member }}</td>
|
||||||
<td class="text-center"><a
|
<td class="text-center">
|
||||||
href="https://zkillboard.com/character/{{ member.character_id }}/"
|
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
|
||||||
class="label label-danger"
|
</td>
|
||||||
target="_blank">{% trans "Killboard" %}</a></td>
|
|
||||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
||||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
||||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
|
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
|
||||||
@ -136,10 +133,9 @@
|
|||||||
<tr class="danger">
|
<tr class="danger">
|
||||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||||
<td class="text-center">{{ member.character_name }}</td>
|
<td class="text-center">{{ member.character_name }}</td>
|
||||||
<td class="text-center"><a
|
<td class="text-center">
|
||||||
href="https://zkillboard.com/character/{{ member.character_id }}/"
|
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
|
||||||
class="label label-danger"
|
</td>
|
||||||
target="_blank">{% trans "Killboard" %}</a></td>
|
|
||||||
<td class="text-center"></td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center"></td>
|
<td class="text-center"></td>
|
||||||
<td class="text-center"></td>
|
<td class="text-center"></td>
|
||||||
@ -167,9 +163,7 @@
|
|||||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||||
<td class="text-center">{{ member.character_name }}</td>
|
<td class="text-center">{{ member.character_name }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/"
|
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
|
||||||
class="label label-danger"
|
|
||||||
target="_blank">
|
|
||||||
{% trans "Killboard" %}
|
{% trans "Killboard" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -39,4 +39,3 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
|
|||||||
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
|
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
|
||||||
admin.site.register(ManagedCorpGroup)
|
admin.site.register(ManagedCorpGroup)
|
||||||
admin.site.register(ManagedAllianceGroup)
|
admin.site.register(ManagedAllianceGroup)
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.6 on 2017-12-23 04:30
|
# Generated by Django 1.11.6 on 2017-12-23 04:30
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -57,25 +57,21 @@ class AutogroupsConfig(models.Model):
|
|||||||
|
|
||||||
states = models.ManyToManyField(State, related_name='autogroups')
|
states = models.ManyToManyField(State, related_name='autogroups')
|
||||||
|
|
||||||
corp_groups = models.BooleanField(default=False,
|
corp_groups = models.BooleanField(default=False, help_text="Setting this to false will delete all the created groups.")
|
||||||
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_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)
|
corp_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
|
||||||
|
|
||||||
alliance_groups = models.BooleanField(default=False,
|
alliance_groups = models.BooleanField(default=False, help_text="Setting this to false will delete all the created groups.")
|
||||||
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_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)
|
alliance_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
|
||||||
|
|
||||||
corp_managed_groups = models.ManyToManyField(
|
corp_managed_groups = models.ManyToManyField(
|
||||||
Group, through='ManagedCorpGroup', related_name='corp_managed_config',
|
Group, through='ManagedCorpGroup', related_name='corp_managed_config',
|
||||||
help_text='A list of corporation groups created and maintained by this AutogroupConfig. '
|
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.')
|
||||||
'You should not edit this list unless you know what you\'re doing.')
|
|
||||||
|
|
||||||
alliance_managed_groups = models.ManyToManyField(
|
alliance_managed_groups = models.ManyToManyField(
|
||||||
Group, through='ManagedAllianceGroup', related_name='alliance_managed_config',
|
Group, through='ManagedAllianceGroup', related_name='alliance_managed_config',
|
||||||
help_text='A list of alliance groups created and maintained by this AutogroupConfig. '
|
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.')
|
||||||
'You should not edit this list unless you know what you\'re doing.')
|
|
||||||
|
|
||||||
replace_spaces = models.BooleanField(default=False)
|
replace_spaces = models.BooleanField(default=False)
|
||||||
replace_spaces_with = models.CharField(
|
replace_spaces_with = models.CharField(
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-10 20:20
|
# Generated by Django 1.10.1 on 2016-09-10 20:20
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.2 on 2016-10-26 01:49
|
# Generated by Django 1.10.2 on 2016-10-26 01:49
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.2 on 2016-11-01 04:20
|
# Generated by Django 1.10.2 on 2016-11-01 04:20
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-12-16 23:22
|
# Generated by Django 1.10.1 on 2016-12-16 23:22
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2017-01-02 19:23
|
# Generated by Django 1.10.1 on 2017-01-02 19:23
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-01-18 13:20
|
# Generated by Django 1.10.5 on 2017-01-18 13:20
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-22 23:09
|
# Generated by Django 1.10.5 on 2017-03-22 23:09
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.5 on 2017-09-28 02:16
|
# Generated by Django 1.11.5 on 2017-09-28 02:16
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -91,9 +91,7 @@ class EveCorporationInfo(models.Model):
|
|||||||
provider = EveCorporationProviderManager()
|
provider = EveCorporationProviderManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [
|
indexes = [models.Index(fields=['ceo_id',]),]
|
||||||
models.Index(fields=['ceo_id',]),
|
|
||||||
]
|
|
||||||
|
|
||||||
def update_corporation(self, corp: providers.Corporation = None):
|
def update_corporation(self, corp: providers.Corporation = None):
|
||||||
if corp is None:
|
if corp is None:
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
|
|
@ -5,6 +5,4 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
class FatlinkForm(forms.Form):
|
class FatlinkForm(forms.Form):
|
||||||
fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
|
fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
|
||||||
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1,
|
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1, max_value=2147483647, help_text=_('minutes'))
|
||||||
max_value=2147483647, help_text=_('minutes'))
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-05 22:20
|
# Generated by Django 1.10.1 on 2016-09-05 22:20
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-06 23:54
|
# Generated by Django 1.10.1 on 2016-09-06 23:54
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -30,4 +30,3 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
$(document).ready(function(){
|
$(document).ready(function () {
|
||||||
$("[rel=tooltip]").tooltip();
|
$("[rel=tooltip]").tooltip();
|
||||||
|
});
|
||||||
{% endblock extra_script %}
|
{% endblock extra_script %}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
$(document).ready(function(){
|
$(document).ready(function () {
|
||||||
$("[rel=tooltip]").tooltip();
|
$("[rel=tooltip]").tooltip();
|
||||||
|
});
|
||||||
{% endblock extra_script %}
|
{% endblock extra_script %}
|
||||||
|
@ -135,8 +135,7 @@ def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
|
|||||||
stat_list.sort(key=lambda stat: stat.mainchar.character_name)
|
stat_list.sort(key=lambda stat: stat.mainchar.character_name)
|
||||||
stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
|
stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
|
||||||
|
|
||||||
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
|
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, 'previous_month': start_of_previous_month, 'corpid': corpid}
|
||||||
'previous_month': start_of_previous_month, 'corpid': corpid}
|
|
||||||
if datetime.datetime.now() > start_of_next_month:
|
if datetime.datetime.now() > start_of_next_month:
|
||||||
context.update({'next_month': start_of_next_month})
|
context.update({'next_month': start_of_next_month})
|
||||||
|
|
||||||
@ -163,16 +162,14 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
|
|||||||
|
|
||||||
for fat in fats_in_span.exclude(character__corporation_id__in=fat_stats):
|
for fat in fats_in_span.exclude(character__corporation_id__in=fat_stats):
|
||||||
if EveCorporationInfo.objects.filter(corporation_id=fat.character.corporation_id).exists():
|
if EveCorporationInfo.objects.filter(corporation_id=fat.character.corporation_id).exists():
|
||||||
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month,
|
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month, start_of_next_month)
|
||||||
start_of_next_month)
|
|
||||||
|
|
||||||
# collect and sort stats
|
# collect and sort stats
|
||||||
stat_list = [fat_stats[x] for x in fat_stats]
|
stat_list = [fat_stats[x] for x in fat_stats]
|
||||||
stat_list.sort(key=lambda stat: stat.corp.corporation_name)
|
stat_list.sort(key=lambda stat: stat.corp.corporation_name)
|
||||||
stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
|
stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
|
||||||
|
|
||||||
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
|
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, 'previous_month': start_of_previous_month}
|
||||||
'previous_month': start_of_previous_month}
|
|
||||||
if datetime.datetime.now() > start_of_next_month:
|
if datetime.datetime.now() > start_of_next_month:
|
||||||
context.update({'next_month': start_of_next_month})
|
context.update({'next_month': start_of_next_month})
|
||||||
|
|
||||||
@ -199,8 +196,7 @@ def fatlink_personal_statistics_view(request, year=datetime.date.today().year):
|
|||||||
monthlystats = [(i + 1, datetime.date(year, i + 1, 1).strftime("%h"), monthlystats[i]) for i in range(12)]
|
monthlystats = [(i + 1, datetime.date(year, i + 1, 1).strftime("%h"), monthlystats[i]) for i in range(12)]
|
||||||
|
|
||||||
if datetime.datetime.now() > datetime.datetime(year + 1, 1, 1):
|
if datetime.datetime.now() > datetime.datetime(year + 1, 1, 1):
|
||||||
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1,
|
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1, 'next_year': year + 1}
|
||||||
'next_year': year + 1}
|
|
||||||
else:
|
else:
|
||||||
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1}
|
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1}
|
||||||
|
|
||||||
@ -229,9 +225,11 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
|||||||
for fat in personal_fats:
|
for fat in personal_fats:
|
||||||
ship_statistics[fat.shiptype] = ship_statistics.setdefault(fat.shiptype, 0) + 1
|
ship_statistics[fat.shiptype] = ship_statistics.setdefault(fat.shiptype, 0) + 1
|
||||||
n_fats += 1
|
n_fats += 1
|
||||||
context = {'user': user, 'shipStats': sorted(ship_statistics.items()), 'month': start_of_month.strftime("%h"),
|
context = {
|
||||||
|
'user': user, 'shipStats': sorted(ship_statistics.items()), 'month': start_of_month.strftime("%h"),
|
||||||
'year': year, 'n_fats': n_fats, 'char_id': char_id, 'previous_month': start_of_previous_month,
|
'year': year, 'n_fats': n_fats, 'char_id': char_id, 'previous_month': start_of_previous_month,
|
||||||
'next_month': start_of_next_month}
|
'next_month': start_of_next_month
|
||||||
|
}
|
||||||
|
|
||||||
created_fats = Fatlink.objects.filter(creator=user).filter(fatdatetime__gte=start_of_month).filter(
|
created_fats = Fatlink.objects.filter(creator=user).filter(fatdatetime__gte=start_of_month).filter(
|
||||||
fatdatetime__lt=start_of_next_month)
|
fatdatetime__lt=start_of_next_month)
|
||||||
@ -257,8 +255,7 @@ def click_fatlink_view(request, token, fat_hash=None):
|
|||||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
||||||
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||||
location['solar_system_name'] = \
|
location['solar_system_name'] = \
|
||||||
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()[
|
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
|
||||||
'name']
|
|
||||||
if location['station_id']:
|
if location['station_id']:
|
||||||
location['station_name'] = \
|
location['station_name'] = \
|
||||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||||
|
@ -95,6 +95,7 @@ class HasLeaderFilter(admin.SimpleListFilter):
|
|||||||
|
|
||||||
|
|
||||||
class GroupAdmin(admin.ModelAdmin):
|
class GroupAdmin(admin.ModelAdmin):
|
||||||
|
list_select_related = ('authgroup',)
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
list_display = (
|
list_display = (
|
||||||
'name',
|
'name',
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-06 23:54
|
# Generated by Django 1.10.1 on 2016-09-06 23:54
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-09 23:22
|
# Generated by Django 1.10.1 on 2016-09-09 23:22
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.2 on 2016-12-04 10:25
|
# Generated by Django 1.10.2 on 2016-12-04 10:25
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
@ -61,8 +60,7 @@ def reverse_group_models(apps, schema_editor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if len(group.authgroup.description):
|
if len(group.authgroup.description):
|
||||||
GroupDescription.objects.update_or_create(group=group,
|
GroupDescription.objects.update_or_create(group=group, defaults={'description': group.authgroup.description})
|
||||||
defaults={'description': group.authgroup.description})
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -77,8 +75,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AuthGroup',
|
name='AuthGroup',
|
||||||
fields=[
|
fields=[
|
||||||
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True,
|
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='auth.Group')),
|
||||||
serialize=False, to='auth.Group')),
|
|
||||||
('internal', models.BooleanField(default=True, help_text='Internal group, users cannot see, join or request to join this group.<br>Used for groups such as Members, Corp_*, Alliance_* etc.<br><b>Overrides Hidden and Open options when selected.</b>')),
|
('internal', models.BooleanField(default=True, help_text='Internal group, users cannot see, join or request to join this group.<br>Used for groups such as Members, Corp_*, Alliance_* etc.<br><b>Overrides Hidden and Open options when selected.</b>')),
|
||||||
('hidden', models.BooleanField(default=True, help_text='Group is hidden from users but can still join with the correct link.')),
|
('hidden', models.BooleanField(default=True, help_text='Group is hidden from users but can still join with the correct link.')),
|
||||||
('open', models.BooleanField(default=False, help_text='Group is open and users will be automatically added upon request. <br>If the group is not open users will need their request manually approved.')),
|
('open', models.BooleanField(default=False, help_text='Group is open and users will be automatically added upon request. <br>If the group is not open users will need their request manually approved.')),
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-02-04 06:11
|
# Generated by Django 1.10.5 on 2017-02-04 06:11
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-02-04 07:17
|
# Generated by Django 1.10.5 on 2017-02-04 07:17
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.5 on 2017-09-28 02:16
|
# Generated by Django 1.11.5 on 2017-09-28 02:16
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.10 on 2018-02-23 23:09
|
# Generated by Django 1.11.10 on 2018-02-23 23:09
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -81,8 +81,7 @@ class AuthGroup(models.Model):
|
|||||||
help_text="Internal group, users cannot see, join or request to join this group.<br>"
|
help_text="Internal group, users cannot see, join or request to join this group.<br>"
|
||||||
"Used for groups such as Members, Corp_*, Alliance_* etc.<br>"
|
"Used for groups such as Members, Corp_*, Alliance_* etc.<br>"
|
||||||
"<b>Overrides Hidden and Open options when selected.</b>")
|
"<b>Overrides Hidden and Open options when selected.</b>")
|
||||||
hidden = models.BooleanField(default=True,
|
hidden = models.BooleanField(default=True, help_text="Group is hidden from users but can still join with the correct link.")
|
||||||
help_text="Group is hidden from users but can still join with the correct link.")
|
|
||||||
open = models.BooleanField(default=False,
|
open = models.BooleanField(default=False,
|
||||||
help_text="Group is open and users will be automatically added upon request. <br>"
|
help_text="Group is open and users will be automatically added upon request. <br>"
|
||||||
"If the group is not open users will need their request manually approved.")
|
"If the group is not open users will need their request manually approved.")
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
{% block extra_script %}
|
{% block extra_script %}
|
||||||
$.fn.dataTable.moment = function(format, locale) {
|
$.fn.dataTable.moment = function(format, locale) {
|
||||||
var types = $.fn.dataTable.ext.type;
|
let types = $.fn.dataTable.ext.type;
|
||||||
|
|
||||||
// Add type detection
|
// Add type detection
|
||||||
types.detect.unshift(function(d) {
|
types.detect.unshift(function(d) {
|
||||||
|
@ -97,4 +97,3 @@ class TestCheckGroupsOnStateChange(TestCase):
|
|||||||
self.assertEqual(self.user.profile.state, self.test_state_2)
|
self.assertEqual(self.user.profile.state, self.test_state_2)
|
||||||
groups = self.user.groups.all()
|
groups = self.user.groups.all()
|
||||||
self.assertIn(state_group, groups)
|
self.assertIn(state_group, groups)
|
||||||
|
|
@ -78,8 +78,7 @@ def group_membership_audit(request, group_id):
|
|||||||
# Check its a joinable group i.e. not corp or internal
|
# Check its a joinable group i.e. not corp or internal
|
||||||
# And the user has permission to manage it
|
# And the user has permission to manage it
|
||||||
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||||
logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
|
logger.warning("User %s attempted to view the membership of group %s but permission was denied" % (request.user, group_id))
|
||||||
(request.user, group_id))
|
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
@ -140,15 +139,13 @@ def group_membership_list(request, group_id):
|
|||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(GroupManager.can_manage_groups)
|
@user_passes_test(GroupManager.can_manage_groups)
|
||||||
def group_membership_remove(request, group_id, user_id):
|
def group_membership_remove(request, group_id, user_id):
|
||||||
logger.debug("group_membership_remove called by user %s for group id %s on user id %s" %
|
logger.debug("group_membership_remove called by user %s for group id %s on user id %s" % (request.user, group_id, user_id))
|
||||||
(request.user, group_id, user_id))
|
|
||||||
group = get_object_or_404(Group, id=group_id)
|
group = get_object_or_404(Group, id=group_id)
|
||||||
try:
|
try:
|
||||||
# Check its a joinable group i.e. not corp or internal
|
# Check its a joinable group i.e. not corp or internal
|
||||||
# And the user has permission to manage it
|
# And the user has permission to manage it
|
||||||
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||||
logger.warning("User %s attempted to remove a user from group %s but permission was denied" % (request.user,
|
logger.warning("User %s attempted to remove a user from group %s but permission was denied" % (request.user, group_id))
|
||||||
group_id))
|
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -194,8 +191,7 @@ def group_accept_request(request, group_request_id):
|
|||||||
_('Accepted application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
_('Accepted application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||||
|
|
||||||
except PermissionDenied as p:
|
except PermissionDenied as p:
|
||||||
logger.warning("User %s attempted to accept group join request %s but permission was denied" %
|
logger.warning("User %s attempted to accept group join request %s but permission was denied" % (request.user, group_request_id))
|
||||||
(request.user, group_request_id))
|
|
||||||
raise p
|
raise p
|
||||||
except:
|
except:
|
||||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||||
@ -221,14 +217,12 @@ def group_reject_request(request, group_request_id):
|
|||||||
log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=0,request_actor=request.user)
|
log = RequestLog(request_type=group_request.leave_request,group=group_request.group,request_info=group_request.__str__(),action=0,request_actor=request.user)
|
||||||
log.save()
|
log.save()
|
||||||
group_request.delete()
|
group_request.delete()
|
||||||
notify(group_request.user, "Group Application Rejected", level="danger",
|
notify(group_request.user, "Group Application Rejected", level="danger", message="Your application to %s has been rejected." % group_request.group)
|
||||||
message="Your application to %s has been rejected." % group_request.group)
|
|
||||||
messages.success(request,
|
messages.success(request,
|
||||||
_('Rejected application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
_('Rejected application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||||
|
|
||||||
except PermissionDenied as p:
|
except PermissionDenied as p:
|
||||||
logger.warning("User %s attempted to reject group join request %s but permission was denied" %
|
logger.warning("User %s attempted to reject group join request %s but permission was denied" % (request.user, group_request_id))
|
||||||
(request.user, group_request_id))
|
|
||||||
raise p
|
raise p
|
||||||
except:
|
except:
|
||||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||||
@ -262,8 +256,7 @@ def group_leave_accept_request(request, group_request_id):
|
|||||||
messages.success(request,
|
messages.success(request,
|
||||||
_('Accepted application from %(mainchar)s to leave %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
_('Accepted application from %(mainchar)s to leave %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||||
except PermissionDenied as p:
|
except PermissionDenied as p:
|
||||||
logger.warning("User %s attempted to accept group leave request %s but permission was denied" %
|
logger.warning("User %s attempted to accept group leave request %s but permission was denied" % (request.user, group_request_id))
|
||||||
(request.user, group_request_id))
|
|
||||||
raise p
|
raise p
|
||||||
except:
|
except:
|
||||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||||
@ -291,13 +284,11 @@ def group_leave_reject_request(request, group_request_id):
|
|||||||
group_request.delete()
|
group_request.delete()
|
||||||
logger.info("User %s rejected group leave request from user %s for group %s" % (
|
logger.info("User %s rejected group leave request from user %s for group %s" % (
|
||||||
request.user, group_request.user, group_request.group.name))
|
request.user, group_request.user, group_request.group.name))
|
||||||
notify(group_request.user, "Group Leave Request Rejected", level="danger",
|
notify(group_request.user, "Group Leave Request Rejected", level="danger", message="Your request to leave %s has been rejected." % group_request.group)
|
||||||
message="Your request to leave %s has been rejected." % group_request.group)
|
|
||||||
messages.success(request, _('Rejected application from %(mainchar)s to leave %(group)s.') % {
|
messages.success(request, _('Rejected application from %(mainchar)s to leave %(group)s.') % {
|
||||||
"mainchar": group_request.main_char, "group": group_request.group})
|
"mainchar": group_request.main_char, "group": group_request.group})
|
||||||
except PermissionDenied as p:
|
except PermissionDenied as p:
|
||||||
logger.warning("User %s attempted to reject group leave request %s but permission was denied" %
|
logger.warning("User %s attempted to reject group leave request %s but permission was denied" % (request.user, group_request_id))
|
||||||
(request.user, group_request_id))
|
|
||||||
raise p
|
raise p
|
||||||
except:
|
except:
|
||||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||||
@ -337,20 +328,17 @@ def group_request_add(request, group_id):
|
|||||||
group = Group.objects.get(id=group_id)
|
group = Group.objects.get(id=group_id)
|
||||||
state = request.user.profile.state
|
state = request.user.profile.state
|
||||||
if not GroupManager.joinable_group(group, state):
|
if not GroupManager.joinable_group(group, state):
|
||||||
logger.warning("User %s attempted to join group id %s but it is not a joinable group" %
|
logger.warning("User %s attempted to join group id %s but it is not a joinable group" % (request.user, group_id))
|
||||||
(request.user, group_id))
|
|
||||||
messages.warning(request, _("You cannot join that group"))
|
messages.warning(request, _("You cannot join that group"))
|
||||||
return redirect('groupmanagement:groups')
|
return redirect('groupmanagement:groups')
|
||||||
if group in request.user.groups.all():
|
if group in request.user.groups.all():
|
||||||
# User is already a member of this group.
|
# User is already a member of this group.
|
||||||
logger.warning("User %s attempted to join group id %s but they are already a member." %
|
logger.warning("User %s attempted to join group id %s but they are already a member." % (request.user, group_id))
|
||||||
(request.user, group_id))
|
|
||||||
messages.warning(request, _("You are already a member of that group."))
|
messages.warning(request, _("You are already a member of that group."))
|
||||||
return redirect('groupmanagement:groups')
|
return redirect('groupmanagement:groups')
|
||||||
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
||||||
# Does not have the required permission, trying to join a non-public group
|
# Does not have the required permission, trying to join a non-public group
|
||||||
logger.warning("User %s attempted to join group id %s but it is not a public group" %
|
logger.warning("User %s attempted to join group id %s but it is not a public group" % (request.user, group_id))
|
||||||
(request.user, group_id))
|
|
||||||
messages.warning(request, _("You cannot join that group"))
|
messages.warning(request, _("You cannot join that group"))
|
||||||
return redirect('groupmanagement:groups')
|
return redirect('groupmanagement:groups')
|
||||||
if group.authgroup.open:
|
if group.authgroup.open:
|
||||||
@ -381,13 +369,11 @@ def group_request_leave(request, group_id):
|
|||||||
logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id))
|
logger.debug("group_request_leave called by user %s for group id %s" % (request.user, group_id))
|
||||||
group = Group.objects.get(id=group_id)
|
group = Group.objects.get(id=group_id)
|
||||||
if not GroupManager.check_internal_group(group):
|
if not GroupManager.check_internal_group(group):
|
||||||
logger.warning("User %s attempted to leave group id %s but it is not a joinable group" %
|
logger.warning("User %s attempted to leave group id %s but it is not a joinable group" % (request.user, group_id))
|
||||||
(request.user, group_id))
|
|
||||||
messages.warning(request, _("You cannot leave that group"))
|
messages.warning(request, _("You cannot leave that group"))
|
||||||
return redirect('groupmanagement:groups')
|
return redirect('groupmanagement:groups')
|
||||||
if group not in request.user.groups.all():
|
if group not in request.user.groups.all():
|
||||||
logger.debug("User %s attempted to leave group id %s but they are not a member" %
|
logger.debug("User %s attempted to leave group id %s but they are not a member" % (request.user, group_id))
|
||||||
(request.user, group_id))
|
|
||||||
messages.warning(request, _("You are not a member of that group"))
|
messages.warning(request, _("You are not a member of that group"))
|
||||||
return redirect('groupmanagement:groups')
|
return redirect('groupmanagement:groups')
|
||||||
if group.authgroup.open:
|
if group.authgroup.open:
|
||||||
|
@ -122,4 +122,3 @@ def get_hooks(name):
|
|||||||
"""
|
"""
|
||||||
register_all_hooks()
|
register_all_hooks()
|
||||||
return _hooks.get(name, [])
|
return _hooks.get(name, [])
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ from .models import Application
|
|||||||
|
|
||||||
class ApplicationsMenu(MenuItemHook):
|
class ApplicationsMenu(MenuItemHook):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
MenuItemHook.__init__(self,
|
MenuItemHook.__init__(
|
||||||
|
self,
|
||||||
_('Applications'),
|
_('Applications'),
|
||||||
'far fa-file fa-fw',
|
'far fa-file fa-fw',
|
||||||
'hrapplications:index',
|
'hrapplications:index',
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
# Generated by Django 1.10.1 on 2016-09-05 21:39
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.4 on 2017-08-23 19:46
|
# Generated by Django 1.11.4 on 2017-08-23 19:46
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10 on 2017-10-20 13:51
|
# Generated by Django 1.10 on 2017-10-20 13:51
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-03-27 03:29
|
# Generated by Django 1.10.5 on 2017-03-27 03:29
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.2 on 2017-06-08 02:54
|
# Generated by Django 1.11.2 on 2017-06-08 02:54
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
@ -42,14 +42,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'hrapplications:personal_view' personal_app.id %}"
|
<a href="{% url 'hrapplications:personal_view' personal_app.id %}" class="btn btn-primary">
|
||||||
class="btn btn-primary">
|
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<span class="glyphicon glyphicon-eye-open"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if personal_app.approved == None %}
|
{% if personal_app.approved == None %}
|
||||||
<a href="{% url 'hrapplications:personal_removal' personal_app.id %}"
|
<a href="{% url 'hrapplications:personal_removal' personal_app.id %}" class="btn btn-danger">
|
||||||
class="btn btn-danger">
|
|
||||||
<span class="glyphicon glyphicon-remove"></span>
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -105,8 +103,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'hrapplications:view' app.id %}"
|
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
||||||
class="btn btn-primary">
|
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<span class="glyphicon glyphicon-eye-open"></span>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -150,13 +147,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'hrapplications:view' app.id %}"
|
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
||||||
class="btn btn-primary">
|
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<span class="glyphicon glyphicon-eye-open"></span>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.hrapplications.delete_application %}
|
{% if perms.hrapplications.delete_application %}
|
||||||
<a href="{% url 'hrapplications:remove' app.id %}"
|
<a href="{% url 'hrapplications:remove' app.id %}" class="btn btn-danger">
|
||||||
class="btn btn-danger">
|
|
||||||
<span class="glyphicon glyphicon-remove"></span>
|
<span class="glyphicon glyphicon-remove"></span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -175,18 +170,15 @@
|
|||||||
|
|
||||||
{% if perms.auth.human_resources %}
|
{% if perms.auth.human_resources %}
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
|
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">{% trans "Close" %}</span></button>
|
||||||
class="sr-only">{% trans "Close" %}</span></button>
|
|
||||||
<h4 class="modal-title" id="myModalLabel">{% trans "Application Search" %}</h4>
|
<h4 class="modal-title" id="myModalLabel">{% trans "Application Search" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form class="form-signin" role="form"
|
<form class="form-signin" role="form" action={% url 'hrapplications:search' %} method="POST">
|
||||||
action={% url 'hrapplications:search' %} method="POST">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ search_form|bootstrap }}
|
{{ search_form|bootstrap }}
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -56,18 +56,15 @@
|
|||||||
|
|
||||||
{% if perms.auth.human_resources %}
|
{% if perms.auth.human_resources %}
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
|
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">{% trans "Close" %}</span></button>
|
||||||
class="sr-only">{% trans "Close" %}</span></button>
|
|
||||||
<h4 class="modal-title" id="myModalLabel">{% trans "Application Search" %}</h4>
|
<h4 class="modal-title" id="myModalLabel">{% trans "Application Search" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form class="form-signin" role="form"
|
<form class="form-signin" role="form" action={% url 'hrapplications:search' %} method="POST">
|
||||||
action={% url 'hrapplications:search' %} method="POST">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ search_form|bootstrap }}
|
{{ search_form|bootstrap }}
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -49,8 +49,7 @@
|
|||||||
{% for char in app.characters %}
|
{% for char in app.characters %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<img class="ra-avatar img-responsive img-circle"
|
<img class="ra-avatar img-responsive img-circle" src="{{ char.portrait_url_32 }}">
|
||||||
src="{{ char.portrait_url_32 }}">
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ char.character_name }}</td>
|
<td class="text-center">{{ char.character_name }}</td>
|
||||||
<td class="text-center">{{ char.corporation_name }}</td>
|
<td class="text-center">{{ char.corporation_name }}</td>
|
||||||
@ -77,25 +76,20 @@
|
|||||||
{% if app.approved == None %}
|
{% if app.approved == None %}
|
||||||
{% if app.reviewer == user %}
|
{% if app.reviewer == user %}
|
||||||
{% if perms.hrapplications.approve_application %}
|
{% if perms.hrapplications.approve_application %}
|
||||||
<a href="{% url 'hrapplications:approve' app.id %}"
|
<a href="{% url 'hrapplications:approve' app.id %}" class="btn btn-success">{% trans "Approve" %}</a>
|
||||||
class="btn btn-success">{% trans "Approve" %}</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.hrapplications.reject_application %}
|
{% if perms.hrapplications.reject_application %}
|
||||||
<a href="{% url 'hrapplications:reject' app.id %}"
|
<a href="{% url 'hrapplications:reject' app.id %}" class="btn btn-danger">{% trans "Reject" %}</a>
|
||||||
class="btn btn-danger">{% trans "Reject" %}</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.hrapplications.delete_application %}
|
{% if perms.hrapplications.delete_application %}
|
||||||
<a href="{% url 'hrapplications:remove' app.id %}"
|
<a href="{% url 'hrapplications:remove' app.id %}" class="btn btn-danger">{% trans "Delete" %}</a>
|
||||||
class="btn btn-danger">{% trans "Delete" %}</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif not app.reviewer %}
|
{% elif not app.reviewer %}
|
||||||
<a href="{% url 'hrapplications:mark_in_progress' app.id %}"
|
<a href="{% url 'hrapplications:mark_in_progress' app.id %}" class="btn btn-warning">{% trans "Mark in Progress" %}</a>
|
||||||
class="btn btn-warning">{% trans "Mark in Progress" %}</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.hrapplications.add_applicationcomment %}
|
{% if perms.hrapplications.add_applicationcomment %}
|
||||||
<button type="button" class="btn btn-primary" data-toggle="modal"
|
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">{% trans "Comment" %}</button>
|
||||||
data-target="#myModal">{% trans "Comment" %}</button>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -104,15 +98,12 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading" role="tab" id="headingThree">
|
<div class="panel-heading" role="tab" id="headingThree">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
<a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||||
href="#collapseThree" aria-expanded="false"
|
|
||||||
aria-controls="collapseThree">
|
|
||||||
{% trans 'Comments' %} ({{ comments.count }})
|
{% trans 'Comments' %} ({{ comments.count }})
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseThree" class="panel-collapse collapse" role="tabpanel"
|
<div id="collapseThree" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingThree">
|
||||||
aria-labelledby="headingThree">
|
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% for comment in comments %}
|
{% for comment in comments %}
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
@ -136,8 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if perms.hrapplications.add_applicationcomment %}
|
{% if perms.hrapplications.add_applicationcomment %}
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
|
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
@ -63,15 +63,13 @@ def hr_application_create_view(request, form_id=None):
|
|||||||
application.save()
|
application.save()
|
||||||
for question in app_form.questions.all():
|
for question in app_form.questions.all():
|
||||||
response = ApplicationResponse(question=question, application=application)
|
response = ApplicationResponse(question=question, application=application)
|
||||||
response.answer = "\n".join(request.POST.getlist(str(question.pk),
|
response.answer = "\n".join(request.POST.getlist(str(question.pk), ""))
|
||||||
""))
|
|
||||||
response.save()
|
response.save()
|
||||||
logger.info("%s created %s" % (request.user, application))
|
logger.info("%s created %s" % (request.user, application))
|
||||||
return redirect('hrapplications:personal_view', application.id)
|
return redirect('hrapplications:personal_view', application.id)
|
||||||
else:
|
else:
|
||||||
questions = app_form.questions.all()
|
questions = app_form.questions.all()
|
||||||
return render(request, 'hrapplications/create.html',
|
return render(request, 'hrapplications/create.html', context={'questions': questions, 'corp': app_form.corp})
|
||||||
context={'questions': questions, 'corp': app_form.corp})
|
|
||||||
else:
|
else:
|
||||||
choices = []
|
choices = []
|
||||||
for app_form in ApplicationForm.objects.all():
|
for app_form in ApplicationForm.objects.all():
|
||||||
@ -171,8 +169,7 @@ def hr_application_approve(request, app_id):
|
|||||||
logger.info("User %s approving %s" % (request.user, app))
|
logger.info("User %s approving %s" % (request.user, app))
|
||||||
app.approved = True
|
app.approved = True
|
||||||
app.save()
|
app.save()
|
||||||
notify(app.user, "Application Accepted", message="Your application to %s has been approved." % app.form.corp,
|
notify(app.user, "Application Accepted", message="Your application to %s has been approved." % app.form.corp, level="success")
|
||||||
level="success")
|
|
||||||
else:
|
else:
|
||||||
logger.warn("User %s not authorized to approve %s" % (request.user, app))
|
logger.warn("User %s not authorized to approve %s" % (request.user, app))
|
||||||
return redirect('hrapplications:index')
|
return redirect('hrapplications:index')
|
||||||
@ -188,8 +185,7 @@ def hr_application_reject(request, app_id):
|
|||||||
logger.info("User %s rejecting %s" % (request.user, app))
|
logger.info("User %s rejecting %s" % (request.user, app))
|
||||||
app.approved = False
|
app.approved = False
|
||||||
app.save()
|
app.save()
|
||||||
notify(app.user, "Application Rejected", message="Your application to %s has been rejected." % app.form.corp,
|
notify(app.user, "Application Rejected", message="Your application to %s has been rejected." % app.form.corp, level="danger")
|
||||||
level="danger")
|
|
||||||
else:
|
else:
|
||||||
logger.warn("User %s not authorized to reject %s" % (request.user, app))
|
logger.warn("User %s not authorized to reject %s" % (request.user, app))
|
||||||
return redirect('hrapplications:index')
|
return redirect('hrapplications:index')
|
||||||
@ -248,8 +244,7 @@ def hr_application_mark_in_progress(request, app_id):
|
|||||||
app.reviewer = request.user
|
app.reviewer = request.user
|
||||||
app.reviewer_character = request.user.profile.main_character
|
app.reviewer_character = request.user.profile.main_character
|
||||||
app.save()
|
app.save()
|
||||||
notify(app.user, "Application In Progress",
|
notify(app.user, "Application In Progress", message="Your application to %s is being reviewed by %s" % (app.form.corp, app.reviewer_str))
|
||||||
message="Your application to %s is being reviewed by %s" % (app.form.corp, app.reviewer_str))
|
|
||||||
else:
|
else:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"User %s unable to mark %s in progress: already being reviewed by %s" % (request.user, app, app.reviewer))
|
"User %s unable to mark %s in progress: already being reviewed by %s" % (request.user, app, app.reviewer))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user