mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
227 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b932b1188 | ||
|
|
f62153c746 | ||
|
|
88216c3f81 | ||
|
|
dc983c31e3 | ||
|
|
4204c44bde | ||
|
|
8da0122d17 | ||
|
|
c9fcf6e6bf | ||
|
|
36866cc59b | ||
|
|
298bdd98ed | ||
|
|
819018748d | ||
|
|
32e8e0fdd0 | ||
|
|
7625060a12 | ||
|
|
672cb13bfe | ||
|
|
7170f75b89 | ||
|
|
8f60c7a00a | ||
|
|
34ae6e402c | ||
|
|
0905e48994 | ||
|
|
02fcf7d500 | ||
|
|
8d8da50946 | ||
|
|
c1499d173f | ||
|
|
b149baa4e5 | ||
|
|
4807c69b5e | ||
|
|
ebefa0e307 | ||
|
|
468e7433f9 | ||
|
|
3ca313f907 | ||
|
|
820065fc04 | ||
|
|
3eddeefe28 | ||
|
|
82d7d7e3bf | ||
|
|
93194b4f2d | ||
|
|
fa335253d3 | ||
|
|
d1af9416b3 | ||
|
|
f4ac2ea400 | ||
|
|
31c1f8bb7d | ||
|
|
57f7178f1e | ||
|
|
17d4a4c415 | ||
|
|
18ce433fa0 | ||
|
|
1eadb1d934 | ||
|
|
59a8f8a967 | ||
|
|
2dc07b5519 | ||
|
|
3454520dfe | ||
|
|
7a195d4158 | ||
|
|
b73072dec0 | ||
|
|
1ca5e38bd9 | ||
|
|
ecb737c6a5 | ||
|
|
7063f53cdf | ||
|
|
017424b9d4 | ||
|
|
f6c26cf2ec | ||
|
|
9a422bd4ca | ||
|
|
47fec23f2e | ||
|
|
399ef1917d | ||
|
|
9db443ba54 | ||
|
|
0f2f5ea0ba | ||
|
|
1f781c5037 | ||
|
|
36dedfcbd2 | ||
|
|
13a05606fb | ||
|
|
90ad7790e1 | ||
|
|
6b8341ab5a | ||
|
|
d15f42b3fd | ||
|
|
cc60b26f5a | ||
|
|
36ff0af993 | ||
|
|
f17c94a9e1 | ||
|
|
7e3ba476f3 | ||
|
|
dd1313a2a9 | ||
|
|
763003bd7d | ||
|
|
f3217443dd | ||
|
|
a713ae1914 | ||
|
|
5815bac0df | ||
|
|
6154d2c2e7 | ||
|
|
b34661b35d | ||
|
|
a9a7e03b80 | ||
|
|
23c797ef64 | ||
|
|
da102618a0 | ||
|
|
51ee281b14 | ||
|
|
9133232c20 | ||
|
|
9cbabee126 | ||
|
|
4026523a2e | ||
|
|
7fbf96623b | ||
|
|
273bda173e | ||
|
|
7bd5838ea1 | ||
|
|
b232d9ab17 | ||
|
|
a11b870664 | ||
|
|
a27aae5d1c | ||
|
|
117ef63d90 | ||
|
|
1bde3d5672 | ||
|
|
d2355b1ec8 | ||
|
|
191d474a8e | ||
|
|
ec9a9733be | ||
|
|
cf7a8cedf1 | ||
|
|
18cbb994d5 | ||
|
|
663388a0c2 | ||
|
|
7a943591ec | ||
|
|
cd189927fe | ||
|
|
8772349309 | ||
|
|
cf20100cb5 | ||
|
|
9b9c2ddc04 | ||
|
|
34839e8344 | ||
|
|
89ef4f4cbc | ||
|
|
2cc7f46aae | ||
|
|
8d255fb720 | ||
|
|
67cf68ad87 | ||
|
|
db1971d4c2 | ||
|
|
63c1521cba | ||
|
|
ba7ef11505 | ||
|
|
d2e494b9be | ||
|
|
98bab0b180 | ||
|
|
c4efb2a11f | ||
|
|
94e4895f29 | ||
|
|
70eb1b5b50 | ||
|
|
e247a94db3 | ||
|
|
714431c932 | ||
|
|
b026277ab0 | ||
|
|
11855f0b54 | ||
|
|
635fbfe2c8 | ||
|
|
b10233daf0 | ||
|
|
1aa3187491 | ||
|
|
59f17a88f0 | ||
|
|
75db3195d4 | ||
|
|
afe3fea757 | ||
|
|
1072c00a28 | ||
|
|
b221c1ce24 | ||
|
|
1617c775ee | ||
|
|
cc88a02001 | ||
|
|
ae5d0f4a2f | ||
|
|
067e2c424e | ||
|
|
d64675a3b0 | ||
|
|
17a6b3225e | ||
|
|
b83f591dc2 | ||
|
|
be2fbe862b | ||
|
|
f95bee0921 | ||
|
|
2f9ae8b054 | ||
|
|
74651dd30a | ||
|
|
9cdcd8365c | ||
|
|
f5d70a2c48 | ||
|
|
f40ebbfba4 | ||
|
|
2551a9dd64 | ||
|
|
e3b01ccbc9 | ||
|
|
267a392945 | ||
|
|
634d021bf2 | ||
|
|
4e8bfba738 | ||
|
|
297f98f046 | ||
|
|
27dad05927 | ||
|
|
697e9dd772 | ||
|
|
312951ea3f | ||
|
|
e4bf96cfb6 | ||
|
|
3bd6baa8f9 | ||
|
|
06e38dcd93 | ||
|
|
f47b9eee5b | ||
|
|
0d4cab66b2 | ||
|
|
dc23ee8ad2 | ||
|
|
65f2efc890 | ||
|
|
def30900b4 | ||
|
|
d7fabccddd | ||
|
|
45289e1d17 | ||
|
|
e7bafaa4d8 | ||
|
|
ba3f1507be | ||
|
|
7b9bf08aa3 | ||
|
|
360458f574 | ||
|
|
def6431052 | ||
|
|
a47bd8d7c7 | ||
|
|
22a270aedb | ||
|
|
54bce4315b | ||
|
|
c930f7bbeb | ||
|
|
8c1f06d7b8 | ||
|
|
815b6fa030 | ||
|
|
7c05217900 | ||
|
|
64ee273953 | ||
|
|
d8c2944966 | ||
|
|
7669c9e55d | ||
|
|
71c9faaf28 | ||
|
|
236c70316c | ||
|
|
0d0686f58a | ||
|
|
3706a1aedf | ||
|
|
47f1b77320 | ||
|
|
8dec242a93 | ||
|
|
6b934060dd | ||
|
|
ff88a16163 | ||
|
|
e81a66b74b | ||
|
|
2ff200c566 | ||
|
|
091a2637ea | ||
|
|
6c7729308c | ||
|
|
0195ef23d5 | ||
|
|
a7afa4a0c3 | ||
|
|
004100091f | ||
|
|
20231ce198 | ||
|
|
0851a6d085 | ||
|
|
0cd36ad5bc | ||
|
|
7618dd0f91 | ||
|
|
cf49a2cb65 | ||
|
|
cbdce18633 | ||
|
|
a0d14eb1d3 | ||
|
|
53ce4d2453 | ||
|
|
1ddb041d6d | ||
|
|
43cbfd1c47 | ||
|
|
b9a8495a43 | ||
|
|
e296477880 | ||
|
|
bd5c2d8cbc | ||
|
|
ccd40d5c68 | ||
|
|
7f8ca4fad2 | ||
|
|
4bb9a7155d | ||
|
|
2ac79954f3 | ||
|
|
585e1f47f3 | ||
|
|
a33c474b35 | ||
|
|
61c3d8964b | ||
|
|
1c927c5820 | ||
|
|
ff0fa0329d | ||
|
|
e51ea439ca | ||
|
|
113977b19f | ||
|
|
8f39b50b6d | ||
|
|
95b309c358 | ||
|
|
cf3df3b715 | ||
|
|
d815028c4d | ||
|
|
ac5570abe2 | ||
|
|
84ad571aa4 | ||
|
|
38e7705ae7 | ||
|
|
0b6af014fa | ||
|
|
2401f2299d | ||
|
|
919768c8bb | ||
|
|
24db21463b | ||
|
|
1e029af83a | ||
|
|
53dd8ce606 | ||
|
|
0f4003366d | ||
|
|
2b31be789d | ||
|
|
bf1b4bb549 | ||
|
|
dd42b807f0 | ||
|
|
542fbafd98 | ||
|
|
37b9f5c882 | ||
|
|
5bde9a6952 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -69,11 +69,7 @@ celerybeat-schedule
|
||||
#gitlab configs
|
||||
.gitlab/
|
||||
|
||||
#transifex
|
||||
.tx/
|
||||
|
||||
#other
|
||||
.flake8
|
||||
.pylintrc
|
||||
Makefile
|
||||
.isort.cfg
|
||||
|
||||
100
.gitlab-ci.yml
100
.gitlab-ci.yml
@@ -5,16 +5,16 @@
|
||||
- merge_requests
|
||||
|
||||
stages:
|
||||
- pre-commit
|
||||
- gitlab
|
||||
- test
|
||||
- deploy
|
||||
- docker
|
||||
- pre-commit
|
||||
- gitlab
|
||||
- test
|
||||
- deploy
|
||||
- docker
|
||||
|
||||
include:
|
||||
- template: Dependency-Scanning.gitlab-ci.yml
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
- template: Dependency-Scanning.gitlab-ci.yml
|
||||
- template: Security/SAST.gitlab-ci.yml
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
|
||||
before_script:
|
||||
- apt-get update && apt-get install redis-server -y
|
||||
@@ -42,16 +42,20 @@ sast:
|
||||
dependency_scanning:
|
||||
stage: gitlab
|
||||
before_script:
|
||||
- apt-get update && apt-get install redis-server libmariadb-dev -y
|
||||
- redis-server --daemonize yes
|
||||
- python -V
|
||||
- pip install wheel tox
|
||||
- apt-get update && apt-get install redis-server libmariadb-dev -y
|
||||
- redis-server --daemonize yes
|
||||
- python -V
|
||||
- pip install wheel tox
|
||||
|
||||
secret_detection:
|
||||
stage: gitlab
|
||||
before_script: []
|
||||
|
||||
test-3.8-core:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
script:
|
||||
- tox -e py38-core
|
||||
- tox -e py38-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -63,7 +67,7 @@ test-3.9-core:
|
||||
<<: *only-default
|
||||
image: python:3.9-bullseye
|
||||
script:
|
||||
- tox -e py39-core
|
||||
- tox -e py39-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -75,7 +79,7 @@ test-3.10-core:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
script:
|
||||
- tox -e py310-core
|
||||
- tox -e py310-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -85,9 +89,21 @@ test-3.10-core:
|
||||
|
||||
test-3.11-core:
|
||||
<<: *only-default
|
||||
image: python:3.11-rc-bullseye
|
||||
image: python:3.11-bullseye
|
||||
script:
|
||||
- tox -e py311-core
|
||||
- tox -e py311-core
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
test-pvpy-core:
|
||||
<<: *only-default
|
||||
image: pypy:3.9-bullseye
|
||||
script:
|
||||
- tox -e pypy-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -100,7 +116,7 @@ test-3.8-all:
|
||||
<<: *only-default
|
||||
image: python:3.8-bullseye
|
||||
script:
|
||||
- tox -e py38-all
|
||||
- tox -e py38-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -112,7 +128,7 @@ test-3.9-all:
|
||||
<<: *only-default
|
||||
image: python:3.9-bullseye
|
||||
script:
|
||||
- tox -e py39-all
|
||||
- tox -e py39-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -124,7 +140,7 @@ test-3.10-all:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
script:
|
||||
- tox -e py310-all
|
||||
- tox -e py310-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -134,9 +150,22 @@ test-3.10-all:
|
||||
|
||||
test-3.11-all:
|
||||
<<: *only-default
|
||||
image: python:3.11-rc-bullseye
|
||||
image: python:3.11-bullseye
|
||||
script:
|
||||
- tox -e py311-all
|
||||
- tox -e py311-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
||||
|
||||
test-pvpy-all:
|
||||
<<: *only-default
|
||||
image: pypy:3.9-bullseye
|
||||
script:
|
||||
- tox -e pypy-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
@@ -145,6 +174,31 @@ test-3.11-all:
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
build-test:
|
||||
stage: test
|
||||
image: python:3.10-bullseye
|
||||
|
||||
before_script:
|
||||
- python -m pip install --upgrade pip
|
||||
- python -m pip install --upgrade build
|
||||
- python -m pip install --upgrade setuptools wheel
|
||||
|
||||
script:
|
||||
- python -m build
|
||||
|
||||
artifacts:
|
||||
when: always
|
||||
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
- dist/*
|
||||
expire_in: 1 year
|
||||
|
||||
test-docs:
|
||||
<<: *only-default
|
||||
image: python:3.10-bullseye
|
||||
script:
|
||||
- tox -e docs
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.10-bullseye
|
||||
@@ -184,6 +238,8 @@ build-image:
|
||||
docker image push --all-tags $CI_REGISTRY_IMAGE/auth
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: delayed
|
||||
start_in: 10 minutes
|
||||
|
||||
build-image-dev:
|
||||
before_script: []
|
||||
|
||||
6
.isort.cfg
Normal file
6
.isort.cfg
Normal file
@@ -0,0 +1,6 @@
|
||||
[settings]
|
||||
profile=django
|
||||
sections=FUTURE,STDLIB,THIRDPARTY,DJANGO,ESI,FIRSTPARTY,LOCALFOLDER
|
||||
known_esi=esi
|
||||
known_django=django
|
||||
skip_gitignore=true
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.1.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-json
|
||||
@@ -13,27 +13,49 @@ repos:
|
||||
- id: check-yaml
|
||||
- id: fix-byte-order-marker
|
||||
- id: trailing-whitespace
|
||||
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- id: end-of-file-fixer
|
||||
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
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.4.0
|
||||
rev: 2.7.1
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
exclude: ^(LICENSE|allianceauth\/static\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))
|
||||
exclude: |
|
||||
(?x)(
|
||||
LICENSE|
|
||||
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.31.0
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [ --py38-plus ]
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v1.20.0
|
||||
rev: v2.2.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: [ --include-version-classifiers ]
|
||||
|
||||
@@ -11,7 +11,7 @@ build:
|
||||
apt_packages:
|
||||
- redis
|
||||
tools:
|
||||
python: "3.10"
|
||||
python: "3.8"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
|
||||
10
.tx/config
Normal file
10
.tx/config
Normal file
@@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = zh-Hans: zh_Hans
|
||||
|
||||
[o:alliance-auth:p:alliance-auth:r:django-po]
|
||||
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
minimum_perc = 0
|
||||
10
.tx/config_20230406134150.bak
Executable file
10
.tx/config_20230406134150.bak
Executable file
@@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = zh-Hans:zh_Hans
|
||||
|
||||
[alliance-auth.django-po]
|
||||
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
||||
minimum_perc = 0
|
||||
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
@@ -36,7 +36,7 @@ Main features:
|
||||
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: localization
|
||||
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
|
||||
@@ -56,13 +56,15 @@ Here is an example of the Alliance Auth web site with some plug-ins apps and ser
|
||||
|
||||
- [Aaron Kable](https://gitlab.com/aaronkable/)
|
||||
- [Ariel Rin](https://gitlab.com/soratidus999/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
- [Col Crunch](https://gitlab.com/colcrunch/)
|
||||
- [Erik Kalkoken](https://gitlab.com/ErikKalkoken/)
|
||||
- [Rounon Dax](https://gitlab.com/ppfeufer)
|
||||
- [snipereagle1](https://gitlab.com/mckernanin)
|
||||
|
||||
### Former Developers
|
||||
|
||||
- [Adarnof](https://gitlab.com/adarnof/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
|
||||
### Beta Testers / Bug Fixers
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '3.0.0a5'
|
||||
__version__ = '3.4.0'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -8,13 +8,13 @@ from uuid import uuid4
|
||||
class AnalyticsIdentifier(models.Model):
|
||||
|
||||
identifier = models.UUIDField(default=uuid4,
|
||||
editable=False)
|
||||
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')
|
||||
AnalyticsIdentifier instance')
|
||||
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -3,16 +3,17 @@ from urllib.parse import parse_qs
|
||||
|
||||
import requests_mock
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test import override_settings
|
||||
|
||||
from allianceauth.analytics.tasks import ANALYTICS_URL
|
||||
from allianceauth.eveonline.tasks import update_character
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.utils.testing import NoSocketsTestCase
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||
@requests_mock.mock()
|
||||
class TestAnalyticsForViews(TestCase):
|
||||
class TestAnalyticsForViews(NoSocketsTestCase):
|
||||
@override_settings(ANALYTICS_DISABLED=False)
|
||||
def test_should_run_analytics(self, requests_mocker):
|
||||
# given
|
||||
@@ -40,7 +41,7 @@ class TestAnalyticsForViews(TestCase):
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
||||
@requests_mock.mock()
|
||||
class TestAnalyticsForTasks(TestCase):
|
||||
class TestAnalyticsForTasks(NoSocketsTestCase):
|
||||
@override_settings(ANALYTICS_DISABLED=False)
|
||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
||||
def test_should_run_analytics_for_successful_task(
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import requests_mock
|
||||
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from allianceauth.analytics.tasks import (
|
||||
analytics_event,
|
||||
send_ga_tracking_celery_event,
|
||||
send_ga_tracking_web_view)
|
||||
from django.test.testcases import TestCase
|
||||
from allianceauth.utils.testing import NoSocketsTestCase
|
||||
|
||||
|
||||
class TestAnalyticsTasks(TestCase):
|
||||
def test_analytics_event(self):
|
||||
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/collect'
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
@requests_mock.Mocker()
|
||||
class TestAnalyticsTasks(NoSocketsTestCase):
|
||||
def test_analytics_event(self, requests_mocker):
|
||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||
analytics_event(
|
||||
category='allianceauth.analytics',
|
||||
action='send_tests',
|
||||
@@ -14,15 +24,19 @@ class TestAnalyticsTasks(TestCase):
|
||||
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
|
||||
def test_send_ga_tracking_web_view_sent(self, requests_mocker):
|
||||
"""This test sends if the event SENDS to google.
|
||||
Not if it was successful.
|
||||
"""
|
||||
# given
|
||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||
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"
|
||||
# when
|
||||
response = send_ga_tracking_web_view(
|
||||
tracking_id,
|
||||
client_id,
|
||||
@@ -30,15 +44,23 @@ class TestAnalyticsTasks(TestCase):
|
||||
title,
|
||||
locale,
|
||||
useragent)
|
||||
# then
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_send_ga_tracking_web_view_success(self):
|
||||
def test_send_ga_tracking_web_view_success(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={"hitParsingResult":[{'valid': True}]}
|
||||
)
|
||||
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"
|
||||
# when
|
||||
json_response = send_ga_tracking_web_view(
|
||||
tracking_id,
|
||||
client_id,
|
||||
@@ -46,15 +68,42 @@ class TestAnalyticsTasks(TestCase):
|
||||
title,
|
||||
locale,
|
||||
useragent).json()
|
||||
# then
|
||||
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
||||
|
||||
def test_send_ga_tracking_web_view_invalid_token(self):
|
||||
def test_send_ga_tracking_web_view_invalid_token(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={
|
||||
"hitParsingResult":[
|
||||
{
|
||||
'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'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
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"
|
||||
# when
|
||||
json_response = send_ga_tracking_web_view(
|
||||
tracking_id,
|
||||
client_id,
|
||||
@@ -62,18 +111,25 @@ class TestAnalyticsTasks(TestCase):
|
||||
title,
|
||||
locale,
|
||||
useragent).json()
|
||||
# then
|
||||
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.")
|
||||
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):
|
||||
def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||
tracking_id = 'UA-186249766-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
category = 'test'
|
||||
action = 'test'
|
||||
label = 'test'
|
||||
value = '1'
|
||||
# when
|
||||
response = send_ga_tracking_celery_event(
|
||||
tracking_id,
|
||||
client_id,
|
||||
@@ -81,15 +137,23 @@ class TestAnalyticsTasks(TestCase):
|
||||
action,
|
||||
label,
|
||||
value)
|
||||
# then
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_send_ga_tracking_celery_event_success(self):
|
||||
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={"hitParsingResult":[{'valid': True}]}
|
||||
)
|
||||
tracking_id = 'UA-186249766-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
category = 'test'
|
||||
action = 'test'
|
||||
label = 'test'
|
||||
value = '1'
|
||||
# when
|
||||
json_response = send_ga_tracking_celery_event(
|
||||
tracking_id,
|
||||
client_id,
|
||||
@@ -97,15 +161,42 @@ class TestAnalyticsTasks(TestCase):
|
||||
action,
|
||||
label,
|
||||
value).json()
|
||||
# then
|
||||
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
||||
|
||||
def test_send_ga_tracking_celery_event_invalid_token(self):
|
||||
def test_send_ga_tracking_celery_event_invalid_token(self, requests_mocker):
|
||||
# given
|
||||
requests_mocker.register_uri(
|
||||
'POST',
|
||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||
json={
|
||||
"hitParsingResult":[
|
||||
{
|
||||
'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'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
tracking_id = 'UA-IntentionallyBadTrackingID-2'
|
||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
||||
category = 'test'
|
||||
action = 'test'
|
||||
label = 'test'
|
||||
value = '1'
|
||||
# when
|
||||
json_response = send_ga_tracking_celery_event(
|
||||
tracking_id,
|
||||
client_id,
|
||||
@@ -113,7 +204,9 @@ class TestAnalyticsTasks(TestCase):
|
||||
action,
|
||||
label,
|
||||
value).json()
|
||||
# then
|
||||
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'}]
|
||||
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."
|
||||
)
|
||||
|
||||
@@ -322,7 +322,7 @@ class UserAdmin(BaseUserAdmin):
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("authentication/css/admin.css",)
|
||||
"all": ("allianceauth/authentication/css/admin.css",)
|
||||
}
|
||||
|
||||
def get_queryset(self, request):
|
||||
@@ -542,7 +542,7 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("authentication/css/admin.css",)
|
||||
"all": ("allianceauth/authentication/css/admin.css",)
|
||||
}
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib import messages
|
||||
|
||||
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||
|
||||
@@ -37,7 +38,13 @@ class StateBackend(ModelBackend):
|
||||
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
||||
if ownership.owner_hash == token.character_owner_hash:
|
||||
logger.debug(f'Authenticating {ownership.user} by ownership of character {token.character_name}')
|
||||
return ownership.user
|
||||
if ownership.user.profile.main_character:
|
||||
if ownership.user.profile.main_character.character_id == token.character_id:
|
||||
return ownership.user
|
||||
else: ## this is an alt, enforce main only.
|
||||
if request:
|
||||
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account.")
|
||||
return None
|
||||
else:
|
||||
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
|
||||
ownership.delete()
|
||||
@@ -57,13 +64,20 @@ class StateBackend(ModelBackend):
|
||||
if records.exists():
|
||||
# we've seen this character owner before. Re-attach to their old user account
|
||||
user = records[0].user
|
||||
if user.profile.main_character:
|
||||
if ownership.user.profile.main_character.character_id != token.character_id:
|
||||
## this is an alt, enforce main only due to trust issues in SSO.
|
||||
if request:
|
||||
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
|
||||
return None
|
||||
|
||||
token.user = user
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
logger.debug(f'Authenticating {user} by matching owner hash record of character {co.character}')
|
||||
if not user.profile.main_character:
|
||||
# set this as their main by default if they have none
|
||||
user.profile.main_character = co.character
|
||||
user.profile.save()
|
||||
|
||||
# set this as their main by default as they have none
|
||||
user.profile.main_character = co.character
|
||||
user.profile.save()
|
||||
return user
|
||||
logger.debug(f'Unable to authenticate character {token.character_name}. Creating new user.')
|
||||
return self.create_user(token)
|
||||
|
||||
@@ -18,13 +18,13 @@ class State(models.Model):
|
||||
priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.")
|
||||
|
||||
member_characters = models.ManyToManyField(EveCharacter, blank=True,
|
||||
help_text="Characters to which this state is available.")
|
||||
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.")
|
||||
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.")
|
||||
help_text="Alliances to whose members this state is available.")
|
||||
member_factions = models.ManyToManyField(EveFactionInfo, blank=True,
|
||||
help_text="Factions to whose members this state is available.")
|
||||
help_text="Factions to whose members this state is available.")
|
||||
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
|
||||
|
||||
objects = StateManager()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
CSS for allianceauth admin site
|
||||
*/
|
||||
|
||||
/* styling for profile pic */
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.column-user_profile_pic {
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* tooltip */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip:hover::after {
|
||||
background-color: rgb(255 255 204);
|
||||
border: 1px rgb(128 128 128) solid;
|
||||
color: rgb(0 0 0);
|
||||
content: attr(data-tooltip);
|
||||
left: 1em;
|
||||
min-width: 200px;
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
top: 1.1em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
CSS for allianceauth admin site
|
||||
*/
|
||||
|
||||
/* styling for profile pic */
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.column-user_profile_pic {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* tooltip */
|
||||
.tooltip {
|
||||
position: relative ;
|
||||
}
|
||||
.tooltip:hover::after {
|
||||
content: attr(data-tooltip) ;
|
||||
position: absolute ;
|
||||
top: 1.1em ;
|
||||
left: 1em ;
|
||||
min-width: 200px ;
|
||||
border: 1px #808080 solid ;
|
||||
padding: 8px ;
|
||||
color: black ;
|
||||
background-color: rgb(255, 255, 204) ;
|
||||
z-index: 1 ;
|
||||
}
|
||||
@@ -1,27 +1,62 @@
|
||||
import datetime as dt
|
||||
from typing import Optional, List
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from redis import Redis
|
||||
from pytz import utc
|
||||
from redis import Redis, RedisError
|
||||
|
||||
from django_redis import get_redis_connection
|
||||
from allianceauth.utils.cache import get_redis_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _RedisStub:
|
||||
"""Stub of a Redis client.
|
||||
|
||||
It's purpose is to prevent EventSeries objects from trying to access Redis
|
||||
when it is not available. e.g. when the Sphinx docs are rendered by readthedocs.org.
|
||||
"""
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def incr(self, *args, **kwargs):
|
||||
return 0
|
||||
|
||||
def zadd(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def zcount(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def zrangebyscore(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class EventSeries:
|
||||
"""API for recording and analysing a series of events."""
|
||||
"""API for recording and analyzing a series of events."""
|
||||
|
||||
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
|
||||
|
||||
def __init__(self, key_id: str, redis: Redis = None) -> None:
|
||||
self._redis = get_redis_connection("default") if not redis else redis
|
||||
if not isinstance(self._redis, Redis):
|
||||
raise TypeError(
|
||||
"This class requires a Redis client, but none was provided "
|
||||
"and the default Django cache backend is not Redis either."
|
||||
self._redis = get_redis_client() if not redis else redis
|
||||
try:
|
||||
if not self._redis.ping():
|
||||
raise RuntimeError()
|
||||
except (AttributeError, RedisError, RuntimeError):
|
||||
logger.exception(
|
||||
"Failed to establish a connection with Redis. "
|
||||
"This EventSeries object is disabled.",
|
||||
)
|
||||
self._redis = _RedisStub()
|
||||
self._key_id = str(key_id)
|
||||
self.clear()
|
||||
|
||||
@property
|
||||
def is_disabled(self):
|
||||
"""True when this object is disabled, e.g. Redis was not available at startup."""
|
||||
return isinstance(self._redis, _RedisStub)
|
||||
|
||||
@property
|
||||
def _key_counter(self):
|
||||
return f"{self._ROOT_KEY}_{self._key_id}_COUNTER"
|
||||
|
||||
@@ -1,13 +1,48 @@
|
||||
import datetime as dt
|
||||
from unittest.mock import patch
|
||||
|
||||
from pytz import utc
|
||||
from redis import RedisError
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import now
|
||||
|
||||
from allianceauth.authentication.task_statistics.event_series import EventSeries
|
||||
from allianceauth.authentication.task_statistics.event_series import (
|
||||
EventSeries,
|
||||
_RedisStub,
|
||||
)
|
||||
|
||||
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
|
||||
|
||||
|
||||
class TestEventSeries(TestCase):
|
||||
def test_should_abort_without_redis_client(self):
|
||||
# when
|
||||
with patch(MODULE_PATH + ".get_redis_client") as mock:
|
||||
mock.return_value = None
|
||||
events = EventSeries("dummy")
|
||||
# then
|
||||
self.assertTrue(events._redis, _RedisStub)
|
||||
self.assertTrue(events.is_disabled)
|
||||
|
||||
def test_should_disable_itself_if_redis_not_available_1(self):
|
||||
# when
|
||||
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
|
||||
mock_get_master_client.return_value.ping.side_effect = RedisError
|
||||
events = EventSeries("dummy")
|
||||
# then
|
||||
self.assertIsInstance(events._redis, _RedisStub)
|
||||
self.assertTrue(events.is_disabled)
|
||||
|
||||
def test_should_disable_itself_if_redis_not_available_2(self):
|
||||
# when
|
||||
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
|
||||
mock_get_master_client.return_value.ping.return_value = False
|
||||
events = EventSeries("dummy")
|
||||
# then
|
||||
self.assertIsInstance(events._redis, _RedisStub)
|
||||
self.assertTrue(events.is_disabled)
|
||||
|
||||
def test_should_add_event(self):
|
||||
# given
|
||||
events = EventSeries("dummy")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||
@@ -15,9 +14,9 @@
|
||||
<div class="panel panel-primary" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% blocktrans with state=request.user.profile.state %}
|
||||
{% blocktranslate with state=request.user.profile.state %}
|
||||
Main Character (State: {{ state }})
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@@ -28,7 +27,7 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.portrait_url_128 }}">
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_128 }}" alt="{{ main.character_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -40,7 +39,7 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.corporation_logo_url_128 }}">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_128 }}" alt="{{ main.corporation_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -53,7 +52,7 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.alliance_logo_url_128 }}">
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_128 }}" alt="{{ main.alliance_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -64,7 +63,7 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.faction_logo_url_128 }}">
|
||||
<img class="ra-avatar" src="{{ main.faction_logo_url_128 }}" alt="{{ main.faction_name }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -76,13 +75,13 @@
|
||||
</div>
|
||||
<div class="table visible-xs-block">
|
||||
<p>
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_64 }}" alt="{{ main.corporation_name }}">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}" alt="{{ main.corporation_name }}">
|
||||
{% if main.alliance_id %}
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}" alt="{{ main.alliance_name }}">
|
||||
{% endif %}
|
||||
{% if main.faction_id %}
|
||||
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}" alt="{{ main.faction_name }}">
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
@@ -104,13 +103,17 @@
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% translate 'Add Character' %}</a>
|
||||
<div class="col-sm-6">
|
||||
<p>
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% translate 'Add Character' %}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% translate "Change Main" %}</a>
|
||||
<div class="col-sm-6">
|
||||
<p>
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% translate "Change Main" %}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +125,7 @@
|
||||
<h3 class="panel-title">{% translate "Group Memberships" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
|
||||
<div style="height: 240px;overflow-y:auto;">
|
||||
<table class="table table-aa">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
@@ -155,11 +158,12 @@
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}" alt="{{ 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.alliance_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name|default:"" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -169,7 +173,7 @@
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align: middle">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
||||
</td>
|
||||
<td class="text-center" style="vertical-align: middle; width: 100%">
|
||||
<strong>{{ char.character_name }}</strong><br>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
|
||||
<div class="col-sm-12">
|
||||
<table class="table table-aa" id="table_tokens" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Scopes" %}</th>
|
||||
<th class="text-right">{% translate "Actions" %}</th>
|
||||
<th>{% translate "Character" %}</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in tokens %}
|
||||
<tr>
|
||||
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td>
|
||||
<td nowrap class="text-right"><a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a> <a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a></td>
|
||||
<td>{{t.character_name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
let grp = 2;
|
||||
var table = $('#table_tokens').DataTable({
|
||||
"columnDefs": [{ orderable: false, targets: [0,1] },{ "visible": false, "targets": grp }],
|
||||
"order": [[grp, 'asc']],
|
||||
"drawCallback": function (settings) {
|
||||
var api = this.api();
|
||||
var rows = api.rows({ page: 'current' }).nodes();
|
||||
var last = null;
|
||||
api.column(grp, { page: 'current' })
|
||||
.data()
|
||||
.each(function (group, i) {
|
||||
if (last !== group) {
|
||||
$(rows).eq(i).before('<tr class="info"><td colspan="3">' + group + '</td></tr>');
|
||||
last = group;
|
||||
}
|
||||
});
|
||||
},
|
||||
"stateSave": true,
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,5 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -7,7 +8,7 @@
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||
|
||||
{% include 'allianceauth/icons.html' %}
|
||||
@@ -21,7 +22,7 @@
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat center center fixed;
|
||||
background: url('{% static 'allianceauth/authentication/img/background.jpg' %}') no-repeat center center fixed;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
@@ -31,6 +32,7 @@
|
||||
.panel-transparent {
|
||||
background: rgba(48, 48, 48, 0.7);
|
||||
color: #ffffff;
|
||||
padding-bottom: 21px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
@@ -47,7 +49,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" style="margin-top:150px">
|
||||
<div class="container" style="margin-top:150px;">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
{{ language.name_local|capfirst }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
|
||||
{% block middle_box_content %}
|
||||
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
|
||||
<img class="img-responsive center-block" src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
|
||||
<img class="img-responsive center-block" src="{% static 'allianceauth/authentication/img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" alt="{% translate 'Login with Eve SSO' %}">
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{% extends 'public/base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
{% if messages %}
|
||||
@@ -7,6 +9,7 @@
|
||||
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="panel panel-default panel-transparent">
|
||||
<div class="panel-body">
|
||||
<div class="col-md-12">
|
||||
@@ -14,10 +17,25 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'public/lang_select.html' %}
|
||||
|
||||
<p class="text-center" style="margin-top: 2rem;">
|
||||
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
|
||||
<a href="https://www.eveonline.com/article/introducing-esi" target="_blank" rel="noopener noreferrer">
|
||||
{% translate "Introducing ESI - A New API For Eve Online" %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p class="text-center">
|
||||
<a href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
|
||||
{% translate "Manage ESI Applications" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_include %}
|
||||
{% include 'bundles/bootstrap-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends 'public/base.html' %}
|
||||
|
||||
{% load static %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans trimmed %}You're receiving this email because you requested a password reset for your
|
||||
user account.{% endblocktrans %}
|
||||
|
||||
{% translate "Please go to the following page and choose a new password:" %}
|
||||
{% block reset_link %}
|
||||
{{domain}}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
{% endblock %}
|
||||
{% translate "Your username, in case you've forgotten:" %} {{ user.get_username }}
|
||||
|
||||
{% translate "Thanks for using our site!" %}
|
||||
|
||||
{% blocktrans %}Your IT Team{% endblocktrans %}
|
||||
|
||||
{% endautoescape %}
|
||||
@@ -1,14 +0,0 @@
|
||||
{% extends 'public/middle_box.html' %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block page_title %}{% translate "Register" %}{% endblock %}
|
||||
{% block middle_box_content %}
|
||||
<form class="form-signin" role="form" action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<br>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Submit" %}</button>
|
||||
<br>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -116,10 +116,17 @@ class TestAuthenticate(TestCase):
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.user)
|
||||
|
||||
""" Alt Login disabled
|
||||
def test_authenticate_alt_character(self):
|
||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.user)
|
||||
"""
|
||||
|
||||
def test_authenticate_alt_character_fail(self):
|
||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, None)
|
||||
|
||||
def test_authenticate_unclaimed_character(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||
@@ -128,6 +135,7 @@ class TestAuthenticate(TestCase):
|
||||
self.assertEqual(user.username, 'Unclaimed_Character')
|
||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||
|
||||
""" Alt Login disabled
|
||||
def test_authenticate_character_record(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||
@@ -135,6 +143,15 @@ class TestAuthenticate(TestCase):
|
||||
self.assertEqual(user, self.old_user)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||
self.assertTrue(user.profile.main_character)
|
||||
"""
|
||||
|
||||
def test_authenticate_character_record_fails(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.old_user)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||
self.assertTrue(user.profile.main_character)
|
||||
|
||||
def test_iterate_username(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id,
|
||||
|
||||
@@ -22,5 +22,20 @@ urlpatterns = [
|
||||
views.add_character,
|
||||
name='add_character'
|
||||
),
|
||||
path(
|
||||
'account/tokens/manage/',
|
||||
views.token_management,
|
||||
name='token_management'
|
||||
),
|
||||
path(
|
||||
'account/tokens/delete/<int:token_id>',
|
||||
views.token_delete,
|
||||
name='token_delete'
|
||||
),
|
||||
path(
|
||||
'account/tokens/refresh/<int:token_id>',
|
||||
views.token_refresh,
|
||||
name='token_refresh'
|
||||
),
|
||||
path('dashboard/', views.dashboard, name='dashboard'),
|
||||
]
|
||||
|
||||
@@ -61,6 +61,44 @@ def dashboard(request):
|
||||
}
|
||||
return render(request, 'authentication/dashboard.html', context)
|
||||
|
||||
@login_required
|
||||
def token_management(request):
|
||||
tokens = request.user.token_set.all()
|
||||
|
||||
context = {
|
||||
'tokens': tokens
|
||||
}
|
||||
return render(request, 'authentication/tokens.html', context)
|
||||
|
||||
@login_required
|
||||
def token_delete(request, token_id=None):
|
||||
try:
|
||||
token = Token.objects.get(id=token_id)
|
||||
if request.user == token.user:
|
||||
token.delete()
|
||||
messages.success(request, "Token Deleted.")
|
||||
else:
|
||||
messages.error(request, "This token does not belong to you.")
|
||||
except Token.DoesNotExist:
|
||||
messages.warning(request, "Token does not exist")
|
||||
return redirect('authentication:token_management')
|
||||
|
||||
@login_required
|
||||
def token_refresh(request, token_id=None):
|
||||
try:
|
||||
token = Token.objects.get(id=token_id)
|
||||
if request.user == token.user:
|
||||
try:
|
||||
token.refresh()
|
||||
messages.success(request, "Token refreshed.")
|
||||
except Exception as e:
|
||||
messages.warning(request, f"Failed to refresh token. {e}")
|
||||
else:
|
||||
messages.error(request, "This token does not belong to you.")
|
||||
except Token.DoesNotExist:
|
||||
messages.warning(request, "Token does not exist")
|
||||
return redirect('authentication:token_management')
|
||||
|
||||
|
||||
@login_required
|
||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
|
||||
@@ -5,5 +5,6 @@ from .views import NightModeRedirectView
|
||||
def auth_settings(request):
|
||||
return {
|
||||
'SITE_NAME': settings.SITE_NAME,
|
||||
'SITE_URL': settings.SITE_URL,
|
||||
'NIGHT_MODE': NightModeRedirectView.night_mode_state(request),
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
|
||||
</td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center col-lg-6">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
@@ -59,7 +59,7 @@
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align:middle">
|
||||
<div class="thumbnail" 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" alt="{{ main.main }}">
|
||||
<div class="caption text-center">
|
||||
{{ main.main }}
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@
|
||||
<tr>
|
||||
<td class="text-center" style="width:5%">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="{{ alt.portrait_url_32 }}" class="img-circle">
|
||||
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
||||
@@ -119,7 +119,7 @@
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
|
||||
<td class="text-center">{{ member }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
@@ -131,7 +131,7 @@
|
||||
{% endfor %}
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||
@@ -160,7 +160,7 @@
|
||||
<tbody>
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<tbody>
|
||||
{% for result in results %}
|
||||
<tr {% if not result.1.registered %}class="danger"{% endif %}>
|
||||
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle"></td>
|
||||
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
|
||||
<td class="text-center">{{ result.1.character_name }}</td>
|
||||
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
|
||||
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a></td>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.0.7 on 2022-08-14 16:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('eveonline', '0016_character_names_are_not_unique'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='eveallianceinfo',
|
||||
name='alliance_name',
|
||||
field=models.CharField(max_length=254, db_index=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='evecorporationinfo',
|
||||
name='corporation_name',
|
||||
field=models.CharField(max_length=254, db_index=True),
|
||||
),
|
||||
]
|
||||
@@ -71,7 +71,7 @@ class EveAllianceInfo(models.Model):
|
||||
"""An alliance in Eve Online."""
|
||||
|
||||
alliance_id = models.PositiveIntegerField(unique=True)
|
||||
alliance_name = models.CharField(max_length=254, unique=True)
|
||||
alliance_name = models.CharField(max_length=254, db_index=True)
|
||||
alliance_ticker = models.CharField(max_length=254)
|
||||
executor_corp_id = models.PositiveIntegerField()
|
||||
|
||||
@@ -139,7 +139,7 @@ class EveCorporationInfo(models.Model):
|
||||
"""A corporation in Eve Online."""
|
||||
|
||||
corporation_id = models.PositiveIntegerField(unique=True)
|
||||
corporation_name = models.CharField(max_length=254, unique=True)
|
||||
corporation_name = models.CharField(max_length=254, db_index=True)
|
||||
corporation_ticker = models.CharField(max_length=254)
|
||||
member_count = models.IntegerField()
|
||||
ceo_id = models.PositiveIntegerField(blank=True, null=True, default=None)
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.conf import settings
|
||||
from esi.clients import esi_client_factory
|
||||
|
||||
from allianceauth import __version__
|
||||
from allianceauth.utils.django import StartupCommand
|
||||
|
||||
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(
|
||||
@@ -175,15 +176,16 @@ class EveProvider:
|
||||
|
||||
class EveSwaggerProvider(EveProvider):
|
||||
def __init__(self, token=None, adapter=None):
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG or StartupCommand().is_management_command:
|
||||
self._client = None
|
||||
logger.info(
|
||||
'DEBUG mode detected: ESI client will be loaded on-demand.'
|
||||
)
|
||||
logger.info('ESI client will be loaded on-demand')
|
||||
else:
|
||||
logger.info('Loading ESI client')
|
||||
try:
|
||||
self._client = esi_client_factory(
|
||||
token=token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
|
||||
token=token,
|
||||
spec_file=SWAGGER_SPEC_PATH,
|
||||
app_info_text=f"allianceauth v{__version__}"
|
||||
)
|
||||
except (HTTPError, RefResolutionError):
|
||||
logger.exception(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="panel-heading">{{ character_name }}</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-2 col-sm-2">
|
||||
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}">
|
||||
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Create Fatlink" %}{% endblock page_title %}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
|
||||
|
||||
@@ -32,7 +30,7 @@
|
||||
<td class="text-center">{{ fat.user }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
{% if char_id %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
@@ -16,11 +14,11 @@
|
||||
{% endif %}
|
||||
</h1>
|
||||
<h2>
|
||||
{% blocktrans count links=n_fats trimmed %}
|
||||
{% blocktranslate count links=n_fats trimmed %}
|
||||
{{ user }} has collected one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has collected {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
@@ -36,11 +34,11 @@
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>
|
||||
{% blocktrans count links=n_created_fats trimmed %}
|
||||
{% blocktranslate count links=n_created_fats trimmed %}
|
||||
{{ user }} has created one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has created {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:personal_statistics_year' previous_year %}" class="btn btn-info">{% translate "Previous year" %}</a>
|
||||
{% if next_year %}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Fatlink Corp Statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
@@ -29,7 +27,7 @@
|
||||
{% for memberStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive">
|
||||
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}">
|
||||
</td>
|
||||
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
|
||||
<td class="text-center">{{ memberStat.n_chars }}</td>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Fatlink statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:statistics_month' previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
@@ -30,9 +28,9 @@
|
||||
{% for corpStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive">
|
||||
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
|
||||
</td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a></td>
|
||||
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
|
||||
<td class="text-center">{{ corpStat.corp.member_count }}</td>
|
||||
<td class="text-center">{{ corpStat.n_fats }}</td>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
|
||||
@@ -35,7 +33,7 @@
|
||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -212,7 +212,14 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
||||
start_of_previous_month = first_day_of_previous_month(year, month)
|
||||
|
||||
if request.user.has_perm('auth.fleetactivitytracking_statistics') and char_id:
|
||||
user = EveCharacter.objects.get(character_id=char_id).user
|
||||
try:
|
||||
user = EveCharacter.objects.get(character_id=char_id).character_ownership.user
|
||||
except EveCharacter.DoesNotExist:
|
||||
messages.error(request, _('Character does not exist'))
|
||||
return redirect('fatlink:view')
|
||||
except AttributeError:
|
||||
messages.error(request, _('User does not exist'))
|
||||
return redirect('fatlink:view')
|
||||
else:
|
||||
user = request.user
|
||||
logger.debug(f"Personal monthly statistics view for user {user} called by {request.user}")
|
||||
@@ -241,59 +248,82 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
||||
|
||||
@login_required
|
||||
@token_required(
|
||||
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
||||
scopes=[
|
||||
'esi-location.read_location.v1',
|
||||
'esi-location.read_ship_type.v1',
|
||||
'esi-universe.read_structures.v1',
|
||||
'esi-location.read_online.v1',
|
||||
]
|
||||
)
|
||||
def click_fatlink_view(request, token, fat_hash=None):
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||
character = EveCharacter.objects.get_character_by_id(token.character_id)
|
||||
character_online = c.Location.get_characters_character_id_online(
|
||||
character_id=token.character_id
|
||||
).result()
|
||||
|
||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
||||
if character_online["online"] is True:
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
|
||||
character = EveCharacter.objects.get_character_by_id(token.character_id)
|
||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
||||
if character:
|
||||
# get data
|
||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
||||
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||
location['solar_system_name'] = \
|
||||
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
|
||||
|
||||
if character:
|
||||
# get data
|
||||
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
||||
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||
location['solar_system_name'] = \
|
||||
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
|
||||
if location['station_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||
elif location['structure_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
||||
'name']
|
||||
if location['station_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||
elif location['structure_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
||||
'name']
|
||||
else:
|
||||
location['station_name'] = "No Station"
|
||||
|
||||
ship['ship_type_name'] = provider.get_itemtype(ship['ship_type_id']).name
|
||||
|
||||
fat = Fat()
|
||||
fat.system = location['solar_system_name']
|
||||
fat.station = location['station_name']
|
||||
fat.shiptype = ship['ship_type_name']
|
||||
fat.fatlink = fatlink
|
||||
fat.character = character
|
||||
fat.user = request.user
|
||||
|
||||
try:
|
||||
fat.full_clean()
|
||||
fat.save()
|
||||
messages.success(request, _('Fleet participation registered.'))
|
||||
except ValidationError as e:
|
||||
err_messages = []
|
||||
|
||||
for errorname, message in e.message_dict.items():
|
||||
err_messages.append(message[0])
|
||||
|
||||
messages.error(request, ' '.join(err_messages))
|
||||
else:
|
||||
location['station_name'] = "No Station"
|
||||
ship['ship_type_name'] = provider.get_itemtype(ship['ship_type_id']).name
|
||||
context = {
|
||||
'character_id': token.character_id,
|
||||
'character_name': token.character_name,
|
||||
'character_portrait_url': EveCharacter.generic_portrait_url(
|
||||
token.character_id, 128
|
||||
),
|
||||
}
|
||||
|
||||
fat = Fat()
|
||||
fat.system = location['solar_system_name']
|
||||
fat.station = location['station_name']
|
||||
fat.shiptype = ship['ship_type_name']
|
||||
fat.fatlink = fatlink
|
||||
fat.character = character
|
||||
fat.user = request.user
|
||||
try:
|
||||
fat.full_clean()
|
||||
fat.save()
|
||||
messages.success(request, _('Fleet participation registered.'))
|
||||
except ValidationError as e:
|
||||
err_messages = []
|
||||
for errorname, message in e.message_dict.items():
|
||||
err_messages.append(message[0])
|
||||
messages.error(request, ' '.join(err_messages))
|
||||
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
|
||||
else:
|
||||
context = {
|
||||
'character_id': token.character_id,
|
||||
'character_name': token.character_name,
|
||||
'character_portrait_url': EveCharacter.generic_portrait_url(
|
||||
token.character_id, 128
|
||||
),
|
||||
}
|
||||
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
|
||||
messages.error(request, _('FAT link has expired.'))
|
||||
else:
|
||||
messages.error(request, _('FAT link has expired.'))
|
||||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
f"Cannot register the fleet participation for {character.character_name}. The character needs to be online."
|
||||
),
|
||||
)
|
||||
|
||||
return redirect('fatlink:view')
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from django.apps import apps
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group as BaseGroup
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.db.models import Count
|
||||
|
||||
from django.contrib.auth.models import Group as BaseGroup, Permission, User
|
||||
from django.db.models import Count, Exists, OuterRef
|
||||
from django.db.models.functions import Lower
|
||||
from django.db.models.signals import (
|
||||
m2m_changed,
|
||||
@@ -15,6 +15,7 @@ from django.dispatch import receiver
|
||||
|
||||
from .forms import GroupAdminForm, ReservedGroupNameAdminForm
|
||||
from .models import AuthGroup, GroupRequest, ReservedGroupName
|
||||
from .tasks import remove_users_not_matching_states_from_group
|
||||
|
||||
if 'eve_autogroups' in apps.app_configs:
|
||||
_has_auto_groups = True
|
||||
@@ -106,14 +107,13 @@ class HasLeaderFilter(admin.SimpleListFilter):
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
form = GroupAdminForm
|
||||
list_select_related = ('authgroup',)
|
||||
ordering = ('name',)
|
||||
list_display = (
|
||||
'name',
|
||||
'_description',
|
||||
'_properties',
|
||||
'_member_count',
|
||||
'has_leader'
|
||||
'has_leader',
|
||||
)
|
||||
list_filter = [
|
||||
'authgroup__internal',
|
||||
@@ -129,31 +129,51 @@ class GroupAdmin(admin.ModelAdmin):
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
if _has_auto_groups:
|
||||
qs = qs.prefetch_related('managedalliancegroup_set', 'managedcorpgroup_set')
|
||||
qs = qs.prefetch_related('authgroup__group_leaders').select_related('authgroup')
|
||||
qs = qs.annotate(
|
||||
member_count=Count('user', distinct=True),
|
||||
has_leader_qs = (
|
||||
AuthGroup.objects.filter(group=OuterRef('pk'), group_leaders__isnull=False)
|
||||
)
|
||||
has_leader_groups_qs = (
|
||||
AuthGroup.objects.filter(
|
||||
group=OuterRef('pk'), group_leader_groups__isnull=False
|
||||
)
|
||||
)
|
||||
qs = (
|
||||
qs.select_related('authgroup')
|
||||
.annotate(member_count=Count('user', distinct=True))
|
||||
.annotate(has_leader=Exists(has_leader_qs))
|
||||
.annotate(has_leader_groups=Exists(has_leader_groups_qs))
|
||||
)
|
||||
if _has_auto_groups:
|
||||
is_autogroup_corp = (
|
||||
Group.objects.filter(
|
||||
pk=OuterRef('pk'), managedcorpgroup__isnull=False
|
||||
)
|
||||
)
|
||||
is_autogroup_alliance = (
|
||||
Group.objects.filter(
|
||||
pk=OuterRef('pk'), managedalliancegroup__isnull=False
|
||||
)
|
||||
)
|
||||
qs = (
|
||||
qs.annotate(is_autogroup_corp=Exists(is_autogroup_corp))
|
||||
.annotate(is_autogroup_alliance=Exists(is_autogroup_alliance))
|
||||
)
|
||||
return qs
|
||||
|
||||
def _description(self, obj):
|
||||
return obj.authgroup.description
|
||||
|
||||
@admin.display(description="Members", ordering="member_count")
|
||||
@admin.display(description='Members', ordering='member_count')
|
||||
def _member_count(self, obj):
|
||||
return obj.member_count
|
||||
|
||||
@admin.display(boolean=True)
|
||||
def has_leader(self, obj):
|
||||
return obj.authgroup.group_leaders.exists() or obj.authgroup.group_leader_groups.exists()
|
||||
return obj.has_leader or obj.has_leader_groups
|
||||
|
||||
def _properties(self, obj):
|
||||
properties = list()
|
||||
if _has_auto_groups and (
|
||||
obj.managedalliancegroup_set.exists()
|
||||
or obj.managedcorpgroup_set.exists()
|
||||
):
|
||||
if _has_auto_groups and (obj.is_autogroup_corp or obj.is_autogroup_alliance):
|
||||
properties.append('Auto Group')
|
||||
elif obj.authgroup.internal:
|
||||
properties.append('Internal')
|
||||
@@ -183,6 +203,8 @@ class GroupAdmin(admin.ModelAdmin):
|
||||
ag_instance = inline_form.save(commit=False)
|
||||
ag_instance.group = form.instance
|
||||
ag_instance.save()
|
||||
if ag_instance.states.exists():
|
||||
remove_users_not_matching_states_from_group.delay(ag_instance.group.pk)
|
||||
formset.save()
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
|
||||
@@ -189,6 +189,15 @@ class AuthGroup(models.Model):
|
||||
| User.objects.filter(groups__in=list(self.group_leader_groups.all()))
|
||||
)
|
||||
|
||||
def remove_users_not_matching_states(self):
|
||||
"""Remove users not matching defined states from related group."""
|
||||
states_qs = self.states.all()
|
||||
if states_qs.exists():
|
||||
states = list(states_qs)
|
||||
non_compliant_users = self.group.user_set.exclude(profile__state__in=states)
|
||||
for user in non_compliant_users:
|
||||
self.group.user_set.remove(user)
|
||||
|
||||
|
||||
class ReservedGroupName(models.Model):
|
||||
"""Name that can not be used for groups.
|
||||
|
||||
10
allianceauth/groupmanagement/tasks.py
Normal file
10
allianceauth/groupmanagement/tasks.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from celery import shared_task
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
|
||||
@shared_task
|
||||
def remove_users_not_matching_states_from_group(group_pk: int) -> None:
|
||||
"""Remove users not matching defined states from related group."""
|
||||
group = Group.objects.get(pk=group_pk)
|
||||
group.authgroup.remove_users_not_matching_states()
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %}
|
||||
@@ -25,13 +24,15 @@
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="log-entries">
|
||||
<thead>
|
||||
<th scope="col">{% translate "Date/Time" %}</th>
|
||||
<th scope="col">{% translate "Requestor" %}</th>
|
||||
<th scope="col">{% translate "Character" %}</th>
|
||||
<th scope="col">{% translate "Corporation" %}</th>
|
||||
<th scope="col">{% translate "Type" %}</th>
|
||||
<th scope="col">{% translate "Action" %}</th>
|
||||
<th scope="col">{% translate "Actor" %}</th>
|
||||
<tr>
|
||||
<th scope="col">{% translate "Date/Time" %}</th>
|
||||
<th scope="col">{% translate "Requestor" %}</th>
|
||||
<th scope="col">{% translate "Character" %}</th>
|
||||
<th scope="col">{% translate "Corporation" %}</th>
|
||||
<th scope="col">{% translate "Type" %}</th>
|
||||
<th scope="col">{% translate "Action" %}</th>
|
||||
<th scope="col">{% translate "Actor" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@@ -74,7 +75,7 @@
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% include 'bundles/moment-js.html' with locale=True %}
|
||||
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
@@ -37,7 +36,7 @@
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
|
||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}">
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ member.main_char.character_name }}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Groups Membership" %}{% endblock page_title %}
|
||||
@@ -61,7 +60,7 @@
|
||||
<i class="glyphicon glyphicon-list-alt"></i>
|
||||
</a>
|
||||
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ SITE_URL }}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
|
||||
<i class="glyphicon glyphicon-copy"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Available Groups" %}{% endblock page_title %}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
@@ -64,7 +63,7 @@
|
||||
{% for acceptrequest in acceptrequests %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
|
||||
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.character_name }}
|
||||
@@ -121,7 +120,7 @@
|
||||
{% for leaverequest in leaverequests %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
|
||||
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
|
||||
{% if leaverequest.main_char %}
|
||||
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ leaverequest.main_char.character_name }}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
from django.test import TestCase, RequestFactory, Client, override_settings
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership, State
|
||||
from allianceauth.eveonline.models import (
|
||||
@@ -236,60 +236,104 @@ class TestGroupAdmin(TestCase):
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_member_count(self):
|
||||
expected = 1
|
||||
obj = self.modeladmin.get_queryset(MockRequest(user=self.user_1))\
|
||||
.get(pk=self.group_1.pk)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_1.pk)
|
||||
# when
|
||||
result = self.modeladmin._member_count(obj)
|
||||
self.assertEqual(result, expected)
|
||||
# then
|
||||
self.assertEqual(result, 1)
|
||||
|
||||
def test_has_leader_user(self):
|
||||
result = self.modeladmin.has_leader(self.group_1)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_1.pk)
|
||||
# when
|
||||
result = self.modeladmin.has_leader(obj)
|
||||
# then
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_has_leader_group(self):
|
||||
result = self.modeladmin.has_leader(self.group_2)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_2.pk)
|
||||
# when
|
||||
result = self.modeladmin.has_leader(obj)
|
||||
# then
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_properties_1(self):
|
||||
expected = ['Default']
|
||||
result = self.modeladmin._properties(self.group_1)
|
||||
self.assertListEqual(result, expected)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_1.pk)
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
self.assertListEqual(result, ['Default'])
|
||||
|
||||
def test_properties_2(self):
|
||||
expected = ['Internal']
|
||||
result = self.modeladmin._properties(self.group_2)
|
||||
self.assertListEqual(result, expected)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_2.pk)
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
self.assertListEqual(result, ['Internal'])
|
||||
|
||||
def test_properties_3(self):
|
||||
expected = ['Hidden']
|
||||
result = self.modeladmin._properties(self.group_3)
|
||||
self.assertListEqual(result, expected)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_3.pk)
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
self.assertListEqual(result, ['Hidden'])
|
||||
|
||||
def test_properties_4(self):
|
||||
expected = ['Open']
|
||||
result = self.modeladmin._properties(self.group_4)
|
||||
self.assertListEqual(result, expected)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_4.pk)
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
self.assertListEqual(result, ['Open'])
|
||||
|
||||
def test_properties_5(self):
|
||||
expected = ['Public']
|
||||
result = self.modeladmin._properties(self.group_5)
|
||||
self.assertListEqual(result, expected)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_5.pk)
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
self.assertListEqual(result, ['Public'])
|
||||
|
||||
def test_properties_6(self):
|
||||
expected = ['Hidden', 'Open', 'Public']
|
||||
result = self.modeladmin._properties(self.group_6)
|
||||
self.assertListEqual(result, expected)
|
||||
# given
|
||||
request = MockRequest(user=self.user_1)
|
||||
obj = self.modeladmin.get_queryset(request).get(pk=self.group_6.pk)
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
self.assertListEqual(result, ['Hidden', 'Open', 'Public'])
|
||||
|
||||
if _has_auto_groups:
|
||||
@patch(MODULE_PATH + '._has_auto_groups', True)
|
||||
def test_properties_7(self):
|
||||
def test_should_show_autogroup_for_corporation(self):
|
||||
# given
|
||||
self._create_autogroups()
|
||||
expected = ['Auto Group']
|
||||
my_group = Group.objects\
|
||||
.filter(managedcorpgroup__isnull=False)\
|
||||
.first()
|
||||
result = self.modeladmin._properties(my_group)
|
||||
self.assertListEqual(result, expected)
|
||||
request = MockRequest(user=self.user_1)
|
||||
queryset = self.modeladmin.get_queryset(request)
|
||||
obj = queryset.filter(managedcorpgroup__isnull=False).first()
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
# then
|
||||
self.assertListEqual(result, ['Auto Group'])
|
||||
|
||||
@patch(MODULE_PATH + '._has_auto_groups', True)
|
||||
def test_should_show_autogroup_for_alliance(self):
|
||||
# given
|
||||
self._create_autogroups()
|
||||
request = MockRequest(user=self.user_1)
|
||||
queryset = self.modeladmin.get_queryset(request)
|
||||
obj = queryset.filter(managedalliancegroup__isnull=False).first()
|
||||
# when
|
||||
result = self.modeladmin._properties(obj)
|
||||
# then
|
||||
self.assertListEqual(result, ['Auto Group'])
|
||||
|
||||
# actions
|
||||
|
||||
@@ -539,6 +583,68 @@ class TestGroupAdminChangeFormSuperuserExclusiveEdits(WebTest):
|
||||
self.assertNotIn(field, form.fields)
|
||||
|
||||
|
||||
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||
class TestGroupAdmin2(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.superuser = User.objects.create_superuser("super")
|
||||
|
||||
def test_should_remove_users_from_state_groups(self):
|
||||
# given
|
||||
user_member = AuthUtils.create_user("Bruce Wayne")
|
||||
character_member = AuthUtils.add_main_character_2(
|
||||
user_member,
|
||||
name="Bruce Wayne",
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name="Wayne Technologies",
|
||||
)
|
||||
user_guest = AuthUtils.create_user("Lex Luthor")
|
||||
AuthUtils.add_main_character_2(
|
||||
user_guest,
|
||||
name="Lex Luthor",
|
||||
character_id=1011,
|
||||
corp_id=2011,
|
||||
corp_name="Luthor Corp",
|
||||
)
|
||||
member_state = AuthUtils.get_member_state()
|
||||
member_state.member_characters.add(character_member)
|
||||
user_member.refresh_from_db()
|
||||
user_guest.refresh_from_db()
|
||||
group = Group.objects.create(name="dummy")
|
||||
user_member.groups.add(group)
|
||||
user_guest.groups.add(group)
|
||||
group.authgroup.states.add(member_state)
|
||||
self.client.force_login(self.superuser)
|
||||
# when
|
||||
response = self.client.post(
|
||||
f"/admin/groupmanagement/group/{group.pk}/change/",
|
||||
data={
|
||||
"name": f"{group.name}",
|
||||
"authgroup-TOTAL_FORMS": "1",
|
||||
"authgroup-INITIAL_FORMS": "1",
|
||||
"authgroup-MIN_NUM_FORMS": "0",
|
||||
"authgroup-MAX_NUM_FORMS": "1",
|
||||
"authgroup-0-description": "",
|
||||
"authgroup-0-states": f"{member_state.pk}",
|
||||
"authgroup-0-internal": "on",
|
||||
"authgroup-0-hidden": "on",
|
||||
"authgroup-0-group": f"{group.pk}",
|
||||
"authgroup-__prefix__-description": "",
|
||||
"authgroup-__prefix__-internal": "on",
|
||||
"authgroup-__prefix__-hidden": "on",
|
||||
"authgroup-__prefix__-group": f"{group.pk}",
|
||||
"_save": "Save"
|
||||
}
|
||||
)
|
||||
# then
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/admin/groupmanagement/group/")
|
||||
self.assertIn(group, user_member.groups.all())
|
||||
self.assertNotIn(group, user_guest.groups.all())
|
||||
|
||||
|
||||
class TestReservedGroupNameAdmin(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -232,6 +232,38 @@ class TestAuthGroup(TestCase):
|
||||
expected = 'Superheros'
|
||||
self.assertEqual(str(group.authgroup), expected)
|
||||
|
||||
def test_should_remove_guests_from_group_when_restricted_to_members_only(self):
|
||||
# given
|
||||
user_member = AuthUtils.create_user("Bruce Wayne")
|
||||
character_member = AuthUtils.add_main_character_2(
|
||||
user_member,
|
||||
name="Bruce Wayne",
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name="Wayne Technologies",
|
||||
)
|
||||
user_guest = AuthUtils.create_user("Lex Luthor")
|
||||
AuthUtils.add_main_character_2(
|
||||
user_guest,
|
||||
name="Lex Luthor",
|
||||
character_id=1011,
|
||||
corp_id=2011,
|
||||
corp_name="Luthor Corp",
|
||||
)
|
||||
member_state = AuthUtils.get_member_state()
|
||||
member_state.member_characters.add(character_member)
|
||||
user_member.refresh_from_db()
|
||||
user_guest.refresh_from_db()
|
||||
group = Group.objects.create(name="dummy")
|
||||
user_member.groups.add(group)
|
||||
user_guest.groups.add(group)
|
||||
group.authgroup.states.add(member_state)
|
||||
# when
|
||||
group.authgroup.remove_users_not_matching_states()
|
||||
# then
|
||||
self.assertIn(group, user_member.groups.all())
|
||||
self.assertNotIn(group, user_guest.groups.all())
|
||||
|
||||
|
||||
class TestAuthGroupRequestApprovers(TestCase):
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -5,7 +5,7 @@ app_name = "groupmanagement"
|
||||
|
||||
urlpatterns = [
|
||||
# groups
|
||||
path("groups", views.groups_view, name="groups"),
|
||||
path("groups/", views.groups_view, name="groups"),
|
||||
path("group/request/join/<int:group_id>/", views.group_request_add, name="request_add"),
|
||||
path(
|
||||
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Choose a Corp" %}{% endblock page_title %}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}
|
||||
@@ -16,11 +15,11 @@
|
||||
<label class="control-label" for="id_{{ question.pk }}">{{ question.title }}</label>
|
||||
<div class=" ">
|
||||
{% if question.help_text %}
|
||||
<div cass="text-center">{{ question.help_text }}</div>
|
||||
<div class="text-center">{{ question.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for choice in question.choices.all %}
|
||||
<input type={% if question.multi_select == False %}"radio"{% else %}"checkbox"{% endif %} name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}">
|
||||
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
|
||||
<input type={% if question.multi_select == False %}"radio"{% else %}"checkbox"{% endif %} name="{{ question.pk }}" id="id_{{ question.pk }}_choice_{{ forloop.counter }}" value="{{ choice.choice_text }}">
|
||||
<label for="id_{{ question.pk }}_choice_{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
|
||||
{% empty %}
|
||||
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "HR Application Management" %}{% endblock page_title %}
|
||||
@@ -178,7 +177,7 @@
|
||||
<h4 class="modal-title" id="myModalLabel">{% translate "Application Search" %}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-signin" role="form" action={% url 'hrapplications:search' %} method="POST">
|
||||
<form class="form-signin" role="form" action="{% url 'hrapplications:search' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ search_form|bootstrap }}
|
||||
<br>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "HR Application Management" %}{% endblock page_title %}
|
||||
@@ -64,7 +63,7 @@
|
||||
<h4 class="modal-title" id="myModalLabel">{% translate "Application Search" %}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-signin" role="form" action={% url 'hrapplications:search' %} method="POST">
|
||||
<form class="form-signin" role="form" action="{% url 'hrapplications:search' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ search_form|bootstrap }}
|
||||
<br>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -49,7 +48,7 @@
|
||||
{% for char in app.characters %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar img-responsive img-circle" src="{{ char.portrait_url_32 }}">
|
||||
<img class="ra-avatar img-responsive img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
|
||||
@@ -219,7 +219,7 @@ def hr_application_search(request):
|
||||
Q(user__character_ownerships__character__corporation_name__icontains=searchstring) |
|
||||
Q(user__character_ownerships__character__alliance_name__icontains=searchstring) |
|
||||
Q(user__username__icontains=searchstring)
|
||||
)
|
||||
).distinct()
|
||||
|
||||
context = {'applications': applications, 'search_form': HRApplicationSearchForm()}
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/uk/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/uk/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2410
allianceauth/locale/uk/LC_MESSAGES/django.po
Normal file
2410
allianceauth/locale/uk/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Notifications" %}{% endblock %}
|
||||
@@ -9,15 +8,15 @@
|
||||
|
||||
<div class="panel panel-default">
|
||||
|
||||
<div class="panel-heading">
|
||||
<ul class="nav nav-pills">
|
||||
<div class="panel-heading clearfix">
|
||||
<ul class="nav nav-pills navbar-left">
|
||||
<li class="active"><a data-toggle="tab" href="#unread">{% translate "Unread" %}<b>({{ unread|length }})</b></a></li>
|
||||
<li><a data-toggle="tab" href="#read">{% translate "Read" %} <b>({{ read|length }})</b></a></li>
|
||||
<div class="pull-right">
|
||||
<a href="{% url 'notifications:mark_all_read' %}" class="btn btn-warning">{% translate "Mark All Read" %}</a>
|
||||
<a href="{% url 'notifications:delete_all_read' %}" class="btn btn-danger">{% translate "Delete All Read" %}</a>
|
||||
</div>
|
||||
</ul>
|
||||
<div class="nav navbar-nav navbar-right" style="margin-right: 0;">
|
||||
<a href="{% url 'notifications:mark_all_read' %}" class="btn btn-warning">{% translate "Mark All Read" %}</a>
|
||||
<a href="{% url 'notifications:delete_all_read' %}" class="btn btn-danger">{% translate "Delete All Read" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "View Notification" %}{% endblock page_title %}
|
||||
|
||||
@@ -34,7 +34,7 @@ class OpTimer(models.Model):
|
||||
fc = models.CharField(max_length=254, default="")
|
||||
post_time = models.DateTimeField(default=timezone.now)
|
||||
eve_character = models.ForeignKey(EveCharacter, null=True,
|
||||
on_delete=models.SET_NULL)
|
||||
on_delete=models.SET_NULL)
|
||||
description = models.TextField(blank=True, default="")
|
||||
type = models.ForeignKey(OpTimerType, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
@@ -40,9 +39,9 @@
|
||||
</div>
|
||||
|
||||
{% include 'bundles/moment-js.html' with locale=True %}
|
||||
<script src="{% static 'js/timers.js' %}"></script>
|
||||
{% include 'bundles/timers-js.html' %}
|
||||
|
||||
<script type="application/javascript">
|
||||
<script>
|
||||
// Data
|
||||
let timers = [
|
||||
{% for op in optimer %}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{{ permission.permission.codename }} - {% translate "Permissions Audit" %}{% endblock page_title %}
|
||||
@@ -47,7 +45,7 @@
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{ type }}: {{ name }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<img src="{{ user.profile.main_character|character_portrait_url:32 }}" class="img-circle">
|
||||
<img src="{{ user.profile.main_character|character_portrait_url:32 }}" class="img-circle" alt="{{ user.profile.main_character.character_name }}">
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ user }}<br></strong>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Permissions Overview" %}{% endblock page_title %}
|
||||
@@ -10,10 +8,10 @@
|
||||
<h1 class="page-header">{% translate "Permissions Overview" %}</h1>
|
||||
<p>
|
||||
{% if request.GET.all != 'yes' %}
|
||||
{% blocktrans %}Showing only applied permissions{% endblocktrans %}
|
||||
{% blocktranslate %}Showing only applied permissions{% endblocktranslate %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=yes" class="btn btn-primary">{% translate "Show All" %}</a>
|
||||
{% else %}
|
||||
{% blocktrans %}Showing all permissions{% endblocktrans %}
|
||||
{% blocktranslate %}Showing all permissions{% endblocktranslate %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=no" class="btn btn-primary">{% translate "Show Applied" %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
@@ -80,7 +78,7 @@
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||
{% include 'bundles/filterdropdown-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
|
||||
@@ -84,17 +84,17 @@ LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
('es', ugettext('Spanish')),
|
||||
('zh-hans', ugettext('Chinese Simplified')),
|
||||
('ru', ugettext('Russian')),
|
||||
('ko', ugettext('Korean')),
|
||||
('fr', ugettext('French')),
|
||||
('ja', ugettext('Japanese')),
|
||||
('it', ugettext('Italian')),
|
||||
("en", "English"),
|
||||
("de", "German"),
|
||||
("es", "Spanish"),
|
||||
("zh-hans", "Chinese Simplified"),
|
||||
("ru", "Russian"),
|
||||
("ko", "Korean"),
|
||||
("fr", "French"),
|
||||
("ja", "Japanese"),
|
||||
("it", "Italian"),
|
||||
("uk", "Ukrainian"),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
|
||||
@@ -13,6 +13,13 @@ STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||
# in page titles and the site header.
|
||||
SITE_NAME = '{{ project_name }}'
|
||||
|
||||
# This is your websites URL, set it accordingly
|
||||
# Make sure this URL is WITHOUT a trailing slash
|
||||
SITE_URL = "https://example.com"
|
||||
|
||||
# Django security
|
||||
CSRF_TRUSTED_ORIGINS = [SITE_URL]
|
||||
|
||||
# Change this to enable/disable debug mode, which displays
|
||||
# useful error messages but can leak sensitive data.
|
||||
DEBUG = False
|
||||
@@ -39,15 +46,16 @@ DATABASES['default'] = {
|
||||
|
||||
# Register an application at https://developers.eveonline.com for Authentication
|
||||
# & API Access and fill out these settings. Be sure to set the callback URL
|
||||
# to https://example.com/sso/callback substituting your domain for example.com
|
||||
# to https://example.com/sso/callback substituting your domain for example.com in
|
||||
# CCP's developer portal
|
||||
# Logging in to auth requires the publicData scope (can be overridden through the
|
||||
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
|
||||
ESI_SSO_CLIENT_ID = ''
|
||||
ESI_SSO_CLIENT_SECRET = ''
|
||||
ESI_SSO_CALLBACK_URL = ''
|
||||
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback"
|
||||
ESI_USER_CONTACT_EMAIL = '' # A server maintainer that CCP can contact in case of issues.
|
||||
|
||||
# By default emails are validated before new users can log in.
|
||||
# By default, emails are validated before new users can log in.
|
||||
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
|
||||
# https://www.sparkpost.com/docs/integrations/django/
|
||||
# https://elasticemail.com/resources/settings/smtp-api/
|
||||
|
||||
@@ -3,11 +3,11 @@ from django.contrib import admin
|
||||
|
||||
from allianceauth import hooks
|
||||
from allianceauth.authentication.admin import (
|
||||
MainAllianceFilter,
|
||||
MainCorporationsFilter,
|
||||
user_main_organization,
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
user_main_organization,
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter
|
||||
)
|
||||
|
||||
from .models import NameFormatConfig
|
||||
@@ -17,7 +17,7 @@ class ServicesUserAdmin(admin.ModelAdmin):
|
||||
"""Parent class for UserAdmin classes for all services"""
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("services/admin.css",)
|
||||
"all": ("allianceauth/services/admin.css",)
|
||||
}
|
||||
|
||||
search_fields = ('user__username',)
|
||||
@@ -36,19 +36,18 @@ class ServicesUserAdmin(admin.ModelAdmin):
|
||||
MainAllianceFilter,
|
||||
'user__date_joined',
|
||||
)
|
||||
list_select_related = (
|
||||
'user', 'user__profile__main_character', 'user__profile__state'
|
||||
)
|
||||
|
||||
@admin.display(ordering='user__profile__state__name')
|
||||
def _state(self, obj):
|
||||
return obj.user.profile.state.name
|
||||
|
||||
_state.short_description = 'state'
|
||||
_state.admin_order_field = 'user__profile__state__name'
|
||||
|
||||
@admin.display(ordering='user__date_joined')
|
||||
def _date_joined(self, obj):
|
||||
return obj.user.date_joined
|
||||
|
||||
_date_joined.short_description = 'date joined'
|
||||
_date_joined.admin_order_field = 'user__date_joined'
|
||||
|
||||
|
||||
class NameFormatConfigForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -62,6 +61,7 @@ class NameFormatConfigForm(forms.ModelForm):
|
||||
self.fields['service_name'] = forms.ChoiceField(choices=SERVICE_CHOICES)
|
||||
|
||||
|
||||
@admin.register(NameFormatConfig)
|
||||
class NameFormatConfigAdmin(admin.ModelAdmin):
|
||||
form = NameFormatConfigForm
|
||||
list_display = ('service_name', 'get_state_display_string')
|
||||
@@ -69,6 +69,3 @@ class NameFormatConfigAdmin(admin.ModelAdmin):
|
||||
def get_state_display_string(self, obj):
|
||||
return ', '.join([state.name for state in obj.states.all()])
|
||||
get_state_display_string.short_description = 'States'
|
||||
|
||||
|
||||
admin.site.register(NameFormatConfig, NameFormatConfigAdmin)
|
||||
|
||||
@@ -2,12 +2,11 @@ import logging
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from . import __title__
|
||||
from ...admin import ServicesUserAdmin
|
||||
from . import __title__
|
||||
from .models import DiscordUser
|
||||
from .utils import LoggerAddTag
|
||||
|
||||
|
||||
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
|
||||
|
||||
|
||||
@@ -18,21 +17,16 @@ class DiscordUserAdmin(ServicesUserAdmin):
|
||||
list_filter = ServicesUserAdmin.list_filter + ('activated',)
|
||||
ordering = ('-activated',)
|
||||
|
||||
def _uid(self, obj):
|
||||
return obj.uid
|
||||
|
||||
_uid.short_description = 'Discord ID (UID)'
|
||||
_uid.admin_order_field = 'uid'
|
||||
|
||||
def _username(self, obj):
|
||||
if obj.username and obj.discriminator:
|
||||
return f'{obj.username}#{obj.discriminator}'
|
||||
else:
|
||||
return ''
|
||||
|
||||
def delete_queryset(self, request, queryset):
|
||||
for user in queryset:
|
||||
user.delete_user()
|
||||
|
||||
_username.short_description = 'Discord Username'
|
||||
_username.admin_order_field = 'username'
|
||||
@admin.display(description='Discord ID (UID)', ordering='uid')
|
||||
def _uid(self, obj):
|
||||
return obj.uid
|
||||
|
||||
@admin.display(description='Discord Username', ordering='username')
|
||||
def _username(self, obj):
|
||||
if obj.username and obj.discriminator:
|
||||
return f'{obj.username}#{obj.discriminator}'
|
||||
return ''
|
||||
|
||||
37
allianceauth/services/modules/discord/api.py
Normal file
37
allianceauth/services/modules/discord/api.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Public interface for community apps who want to interact with the Discord server
|
||||
of the current Alliance Auth instance.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Here is an example for using the api to fetch the current roles from the configured Discord server.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from allianceauth.services.modules.discord.api import create_bot_client, discord_guild_id
|
||||
|
||||
client = create_bot_client() # create a new Discord client
|
||||
guild_id = discord_guild_id() # get the ID of the configured Discord server
|
||||
roles = client.guild_roles(guild_id) # fetch the roles from our Discord server
|
||||
|
||||
.. seealso::
|
||||
The docs for the client class can be found here: :py:class:`~allianceauth.services.modules.discord.discord_client.client.DiscordClient`
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .app_settings import DISCORD_GUILD_ID
|
||||
from .core import create_bot_client, group_to_role, server_name # noqa
|
||||
from .discord_client.models import Role # noqa
|
||||
from .models import DiscordUser # noqa
|
||||
|
||||
__all__ = ["create_bot_client", "group_to_role", "server_name", "DiscordUser", "Role"]
|
||||
|
||||
|
||||
def discord_guild_id() -> Optional[int]:
|
||||
"""Guild ID of configured Discord server.
|
||||
|
||||
Returns:
|
||||
Guild ID or ``None`` if not configured
|
||||
"""
|
||||
return int(DISCORD_GUILD_ID) if DISCORD_GUILD_ID else None
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user