mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-06 15:16:20 +01:00
Compare commits
407 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e416ab8ff2 | ||
|
|
2802ed03a5 | ||
|
|
4af73c76fe | ||
|
|
b6149979aa | ||
|
|
cb20288427 | ||
|
|
db6f4c91dc | ||
|
|
57ac7a5277 | ||
|
|
136438f9c2 | ||
|
|
e2be8b3440 | ||
|
|
04f3473ef3 | ||
|
|
255cb0da8d | ||
|
|
069352fb0f | ||
|
|
66e8ddb684 | ||
|
|
179c26975c | ||
|
|
e17f6e799b | ||
|
|
7cd8294104 | ||
|
|
ede5540335 | ||
|
|
747279b773 | ||
|
|
44f8b1c477 | ||
|
|
7c6ebd9bf6 | ||
|
|
430469b708 | ||
|
|
efbb3cee31 | ||
|
|
21094ed4dd | ||
|
|
5f326efc7e | ||
|
|
b6e34ace35 | ||
|
|
fe4a8965e3 | ||
|
|
23371c233d | ||
|
|
7a3bbf0d7f | ||
|
|
89a1bec9c1 | ||
|
|
1c1e70619a | ||
|
|
0ff4374efa | ||
|
|
18d0e58a48 | ||
|
|
84f44338dc | ||
|
|
2ba0412890 | ||
|
|
2326522b29 | ||
|
|
a7cb6ee434 | ||
|
|
2aeef63565 | ||
|
|
3c9e7335ef | ||
|
|
49067de325 | ||
|
|
471e7e29ae | ||
|
|
3ec5775406 | ||
|
|
e804d2b60d | ||
|
|
742438a95d | ||
|
|
5c60086baa | ||
|
|
e49041bb14 | ||
|
|
f3cbe91883 | ||
|
|
ea439a2176 | ||
|
|
56e1e76f11 | ||
|
|
634e7357be | ||
|
|
08dc88da1a | ||
|
|
3d206e445c | ||
|
|
64686cdad1 | ||
|
|
d7fe09bdf1 | ||
|
|
6da50da92f | ||
|
|
51e4dd986f | ||
|
|
bee6522182 | ||
|
|
1711a9dd33 | ||
|
|
3914626379 | ||
|
|
df276cb32d | ||
|
|
daad7d8b10 | ||
|
|
3bf5bc0fe3 | ||
|
|
96abae553a | ||
|
|
f9cbfb1562 | ||
|
|
8eaa94e179 | ||
|
|
4f876b648b | ||
|
|
cd738137c0 | ||
|
|
5605eb129d | ||
|
|
87ef0f21a3 | ||
|
|
a1c7ce827e | ||
|
|
97466bcdfb | ||
|
|
ff3096b106 | ||
|
|
98f0d77f3f | ||
|
|
92548ba402 | ||
|
|
c46741d311 | ||
|
|
7c7c1abf7c | ||
|
|
fc303b1b0a | ||
|
|
4e220a9679 | ||
|
|
b17b1f7504 | ||
|
|
7081fc0e76 | ||
|
|
68e4574f19 | ||
|
|
e6e0a70012 | ||
|
|
13e38da942 | ||
|
|
468c1de26b | ||
|
|
22ef5ac0e5 | ||
|
|
ef2dc08958 | ||
|
|
6b84ffa16c | ||
|
|
d7a1096413 | ||
|
|
93b94a8bc2 | ||
|
|
9a95716105 | ||
|
|
dbfcf5d87a | ||
|
|
105d7d53b3 | ||
|
|
01cefe1457 | ||
|
|
7fe3db8017 | ||
|
|
e0945fac80 | ||
|
|
40fa190820 | ||
|
|
670580f8f3 | ||
|
|
323a0bcf16 | ||
|
|
6e995edd80 | ||
|
|
8d86e45b7a | ||
|
|
2aa6df4461 | ||
|
|
cf6f989502 | ||
|
|
3e1d8ae334 | ||
|
|
bcfe9484b5 | ||
|
|
5e4d1b9cfd | ||
|
|
3b463e7305 | ||
|
|
eedf5082fa | ||
|
|
2ea5b15175 | ||
|
|
7a9808aad3 | ||
|
|
a1d712694c | ||
|
|
ca11c726a3 | ||
|
|
6e0f7a35bd | ||
|
|
7375b001ca | ||
|
|
0287086633 | ||
|
|
9eb2becbb5 | ||
|
|
12f1444fe7 | ||
|
|
d6372bd093 | ||
|
|
3935a9cdd2 | ||
|
|
49fb6c39d5 | ||
|
|
8821f18b21 | ||
|
|
f4d8ead54e | ||
|
|
7427e13505 | ||
|
|
445683c3d5 | ||
|
|
677c46c48a | ||
|
|
87b9e3f87a | ||
|
|
da2a5aff2f | ||
|
|
65d77743dc | ||
|
|
1c7f8256d0 | ||
|
|
61f0aae5d9 | ||
|
|
4d56030edf | ||
|
|
2dfe194a3b | ||
|
|
ebb40deb7f | ||
|
|
a3a2d3d35b | ||
|
|
d4dee519b8 | ||
|
|
dfcbad3476 | ||
|
|
e03c1307a3 | ||
|
|
3a95b89779 | ||
|
|
4f5b231bdf | ||
|
|
40c0b8d862 | ||
|
|
62c936f1c0 | ||
|
|
2a762df9b3 | ||
|
|
8fb5a488f7 | ||
|
|
dc239d5396 | ||
|
|
2815ebaa07 | ||
|
|
34dd4802a8 | ||
|
|
3ebf11308c | ||
|
|
5cd5180a91 | ||
|
|
ba213db493 | ||
|
|
8362d11714 | ||
|
|
7ba65968ed | ||
|
|
7fdbac20cb | ||
|
|
76efcb5266 | ||
|
|
6aacb8c2e3 | ||
|
|
465fba3a18 | ||
|
|
9ae6addc71 | ||
|
|
76ae9b8849 | ||
|
|
13a8b7678f | ||
|
|
c166fc0ef9 | ||
|
|
9da61588eb | ||
|
|
7425176b3f | ||
|
|
2bb518f7e0 | ||
|
|
84d5693583 | ||
|
|
867e2a1ded | ||
|
|
fd6c6991f5 | ||
|
|
333fe8497d | ||
|
|
8a3fb17147 | ||
|
|
f8baeb19a7 | ||
|
|
10096862e5 | ||
|
|
89193d2fcf | ||
|
|
fd08b987bd | ||
|
|
4e9e22cb4b | ||
|
|
f8fbbb5ba7 | ||
|
|
dd3ef41396 | ||
|
|
d5fda05dc9 | ||
|
|
d815fad0e6 | ||
|
|
e109198782 | ||
|
|
fbd4672454 | ||
|
|
a29bd567c2 | ||
|
|
960aef95ad | ||
|
|
4aae5497bb | ||
|
|
6081cbe900 | ||
|
|
ce0d8342e3 | ||
|
|
006785e592 | ||
|
|
df05070a55 | ||
|
|
e81450baf3 | ||
|
|
24b6c19aca | ||
|
|
9f4bf13cc9 | ||
|
|
2a156302f0 | ||
|
|
c4d3bde106 | ||
|
|
0900806f68 | ||
|
|
a0d32d8c2d | ||
|
|
e16252842a | ||
|
|
950ae34093 | ||
|
|
0ba94c53aa | ||
|
|
96cc55d174 | ||
|
|
a9062c4389 | ||
|
|
51b86f88b9 | ||
|
|
8184461b48 | ||
|
|
ca0cdd6e15 | ||
|
|
036a17ad3b | ||
|
|
2418023ddd | ||
|
|
c602cf0b00 | ||
|
|
b28b51916c | ||
|
|
0db0978d5f | ||
|
|
0531da1128 | ||
|
|
547d047f59 | ||
|
|
5cd92e29d3 | ||
|
|
bb44194cfc | ||
|
|
aa1cb96c8a | ||
|
|
40ae092306 | ||
|
|
b2e70c3702 | ||
|
|
eebeb26001 | ||
|
|
003a67224e | ||
|
|
d3a3810456 | ||
|
|
f55008559e | ||
|
|
dbc19c76c5 | ||
|
|
ced7972962 | ||
|
|
ad508bd880 | ||
|
|
5c128f2c78 | ||
|
|
1caaca86cf | ||
|
|
33ad1413d5 | ||
|
|
cbb5c80b94 | ||
|
|
d2edd288f9 | ||
|
|
21e80f6961 | ||
|
|
a747951d19 | ||
|
|
4cc7135ace | ||
|
|
6a990c11e6 | ||
|
|
3f47cecbfc | ||
|
|
77c126ea2f | ||
|
|
8b7e57494c | ||
|
|
8ff3d854ba | ||
|
|
49ff355d50 | ||
|
|
6f670da1db | ||
|
|
c558f3785f | ||
|
|
04cdd4c64f | ||
|
|
00fcebd8e3 | ||
|
|
995c84481c | ||
|
|
4dd42da993 | ||
|
|
8295cc51a3 | ||
|
|
f7e1d7c47e | ||
|
|
234191218a | ||
|
|
de12b49527 | ||
|
|
021b7b2edb | ||
|
|
20b959f273 | ||
|
|
e16bb2a737 | ||
|
|
0a6a6e0bf9 | ||
|
|
f0fe3929d4 | ||
|
|
9a62c729eb | ||
|
|
eaedcd5bb7 | ||
|
|
d742257c74 | ||
|
|
ad92d7a916 | ||
|
|
af22222bdf | ||
|
|
01f49bd125 | ||
|
|
af5930b9d0 | ||
|
|
95ba19a827 | ||
|
|
c75647301f | ||
|
|
adedf7534f | ||
|
|
d0684862fe | ||
|
|
2440e5d2b2 | ||
|
|
5f51ad4a6a | ||
|
|
0bd3acc230 | ||
|
|
11a32c90a8 | ||
|
|
6f0b853a60 | ||
|
|
583a6d4c7f | ||
|
|
155494afea | ||
|
|
b0aa58b910 | ||
|
|
1adce85422 | ||
|
|
cd47eadcdc | ||
|
|
247058a30f | ||
|
|
e0fa615e90 | ||
|
|
61ec67183c | ||
|
|
4369813478 | ||
|
|
8bb3d35252 | ||
|
|
398a980fb5 | ||
|
|
e14a295ce6 | ||
|
|
e0cd5c6fb9 | ||
|
|
c37ece49d5 | ||
|
|
8adab8bae0 | ||
|
|
a9c87bc25a | ||
|
|
a4901802c0 | ||
|
|
079dcc5d28 | ||
|
|
10c5a8906d | ||
|
|
e6306ea7aa | ||
|
|
20067c1133 | ||
|
|
dba3c651dc | ||
|
|
0b4d2b819b | ||
|
|
7eebf4d953 | ||
|
|
9e45d2eac7 | ||
|
|
e3017f1ec7 | ||
|
|
12709b1b56 | ||
|
|
51ae604efd | ||
|
|
63071ec359 | ||
|
|
0032e4a01f | ||
|
|
25ab78a41e | ||
|
|
3aa0e323d2 | ||
|
|
cadbb7e61c | ||
|
|
115263eb5a | ||
|
|
18fec5f614 | ||
|
|
02ab064ec3 | ||
|
|
ba25f99cb4 | ||
|
|
28fd1b07ea | ||
|
|
5de19c43df | ||
|
|
6a0ddc9a83 | ||
|
|
03be66d11f | ||
|
|
3d92008069 | ||
|
|
59e47c24c2 | ||
|
|
6d942555ff | ||
|
|
7e312bb95f | ||
|
|
c92fee78e2 | ||
|
|
658a8cd6ce | ||
|
|
c1dc130766 | ||
|
|
35f5573b63 | ||
|
|
a9ebecdec6 | ||
|
|
21f0a96422 | ||
|
|
9e47d19337 | ||
|
|
2c5972d0ab | ||
|
|
ee41d62c13 | ||
|
|
346b4014a9 | ||
|
|
9b56a441ed | ||
|
|
068bf1ae7a | ||
|
|
5be686e3ca | ||
|
|
a215b4411c | ||
|
|
e15cfa0fb1 | ||
|
|
46d51699f4 | ||
|
|
ff30a136d5 | ||
|
|
6dcf3304d5 | ||
|
|
beddeea338 | ||
|
|
69723937f7 | ||
|
|
c541f56ee2 | ||
|
|
7e887e5e34 | ||
|
|
072327c79f | ||
|
|
28af3ff11e | ||
|
|
e3b151f2fb | ||
|
|
f87d7dbdf8 | ||
|
|
a04e6ae3d0 | ||
|
|
15042f5e77 | ||
|
|
6e25361d5e | ||
|
|
9e639a0eeb | ||
|
|
257fbdef36 | ||
|
|
df003c8ec5 | ||
|
|
ba22685eb8 | ||
|
|
773288072a | ||
|
|
63afb13d25 | ||
|
|
5dd286bbe7 | ||
|
|
8aaa8172ca | ||
|
|
b68b401146 | ||
|
|
f7821b647f | ||
|
|
a6526d6f78 | ||
|
|
7898594909 | ||
|
|
cfd12ee3cc | ||
|
|
2c9177b19f | ||
|
|
abff26fb6e | ||
|
|
e8c3b5225c | ||
|
|
98fd1dcc4c | ||
|
|
cfe46e4ca5 | ||
|
|
4675193416 | ||
|
|
a84fa1ca69 | ||
|
|
8f6cb0b9bb | ||
|
|
1c8634f1c8 | ||
|
|
2a21599d45 | ||
|
|
e379c01655 | ||
|
|
afa3d2e7cc | ||
|
|
e5ed33aeec | ||
|
|
b12471e775 | ||
|
|
5e70dab11f | ||
|
|
f728c786b3 | ||
|
|
7056912d54 | ||
|
|
7efed950ca | ||
|
|
886acf2005 | ||
|
|
b2dec3bff2 | ||
|
|
f0a402e141 | ||
|
|
2e2afd7923 | ||
|
|
e9ea09bc56 | ||
|
|
186fa1be03 | ||
|
|
37d1d84fc3 | ||
|
|
ee24706e43 | ||
|
|
07e85727ea | ||
|
|
4912f0f8f0 | ||
|
|
24376262f0 | ||
|
|
efe0c6963b | ||
|
|
a4644028ae | ||
|
|
3a77b4a429 | ||
|
|
fa375a551c | ||
|
|
00a93e6fe9 | ||
|
|
656e69d4b2 | ||
|
|
3b55d370d0 | ||
|
|
5c126ffe82 | ||
|
|
99be753836 | ||
|
|
2e78aa5f26 | ||
|
|
567d97f38a | ||
|
|
d6821b3fd6 | ||
|
|
90375246fd | ||
|
|
a2f217ace5 | ||
|
|
25cf2fdcd5 | ||
|
|
4305ae7995 | ||
|
|
4aff4006e3 | ||
|
|
55c188f2d0 | ||
|
|
f36f824a4b | ||
|
|
6fbf33bcdd | ||
|
|
ed3c2c8529 | ||
|
|
05d7fb1f63 | ||
|
|
3b19db2564 | ||
|
|
98aa44c070 | ||
|
|
8d46ee65af | ||
|
|
49780b871d | ||
|
|
2b7d24fc28 | ||
|
|
b8f86a618f | ||
|
|
9921011742 |
@@ -19,5 +19,6 @@ exclude_lines =
|
|||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
def __repr__
|
def __repr__
|
||||||
raise AssertionError
|
raise AssertionError
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -73,3 +73,4 @@ celerybeat-schedule
|
|||||||
.flake8
|
.flake8
|
||||||
.pylintrc
|
.pylintrc
|
||||||
Makefile
|
Makefile
|
||||||
|
alliance_auth.sqlite3
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ before_script:
|
|||||||
pre-commit-check:
|
pre-commit-check:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
stage: pre-commit
|
stage: pre-commit
|
||||||
image: python:3.10-bullseye
|
image: python:3.11-bookworm
|
||||||
variables:
|
# variables:
|
||||||
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
# PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||||
cache:
|
# cache:
|
||||||
paths:
|
# paths:
|
||||||
- ${PRE_COMMIT_HOME}
|
# - ${PRE_COMMIT_HOME}
|
||||||
script:
|
script:
|
||||||
- pip install pre-commit
|
- pip install pre-commit
|
||||||
- pre-commit run --all-files
|
- pre-commit run --all-files
|
||||||
@@ -53,7 +53,7 @@ secret_detection:
|
|||||||
|
|
||||||
test-3.8-core:
|
test-3.8-core:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.8-bullseye
|
image: python:3.8-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py38-core
|
- tox -e py38-core
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -65,7 +65,7 @@ test-3.8-core:
|
|||||||
|
|
||||||
test-3.9-core:
|
test-3.9-core:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.9-bullseye
|
image: python:3.9-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py39-core
|
- tox -e py39-core
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -77,7 +77,7 @@ test-3.9-core:
|
|||||||
|
|
||||||
test-3.10-core:
|
test-3.10-core:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.10-bullseye
|
image: python:3.10-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py310-core
|
- tox -e py310-core
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -89,7 +89,7 @@ test-3.10-core:
|
|||||||
|
|
||||||
test-3.11-core:
|
test-3.11-core:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.11-bullseye
|
image: python:3.11-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py311-core
|
- tox -e py311-core
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -99,22 +99,21 @@ test-3.11-core:
|
|||||||
coverage_format: cobertura
|
coverage_format: cobertura
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
|
|
||||||
test-pvpy-core:
|
test-3.12-core:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: pypy:3.9-bullseye
|
image: python:3.12-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e pypy-all
|
- tox -e py312-core
|
||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
reports:
|
reports:
|
||||||
coverage_report:
|
coverage_report:
|
||||||
coverage_format: cobertura
|
coverage_format: cobertura
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
test-3.8-all:
|
test-3.8-all:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.8-bullseye
|
image: python:3.8-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py38-all
|
- tox -e py38-all
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -126,7 +125,7 @@ test-3.8-all:
|
|||||||
|
|
||||||
test-3.9-all:
|
test-3.9-all:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.9-bullseye
|
image: python:3.9-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py39-all
|
- tox -e py39-all
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -138,7 +137,7 @@ test-3.9-all:
|
|||||||
|
|
||||||
test-3.10-all:
|
test-3.10-all:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.10-bullseye
|
image: python:3.10-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py310-all
|
- tox -e py310-all
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -150,7 +149,7 @@ test-3.10-all:
|
|||||||
|
|
||||||
test-3.11-all:
|
test-3.11-all:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.11-bullseye
|
image: python:3.11-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e py311-all
|
- tox -e py311-all
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -161,22 +160,21 @@ test-3.11-all:
|
|||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
||||||
|
|
||||||
test-pvpy-all:
|
test-3.12-all:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: pypy:3.9-bullseye
|
image: python:3.12-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e pypy-all
|
- tox -e py312-all
|
||||||
artifacts:
|
artifacts:
|
||||||
when: always
|
when: always
|
||||||
reports:
|
reports:
|
||||||
coverage_report:
|
coverage_report:
|
||||||
coverage_format: cobertura
|
coverage_format: cobertura
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
build-test:
|
build-test:
|
||||||
stage: test
|
stage: test
|
||||||
image: python:3.10-bullseye
|
image: python:3.11-bookworm
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- python -m pip install --upgrade pip
|
- python -m pip install --upgrade pip
|
||||||
@@ -195,13 +193,13 @@ build-test:
|
|||||||
|
|
||||||
test-docs:
|
test-docs:
|
||||||
<<: *only-default
|
<<: *only-default
|
||||||
image: python:3.11-bullseye
|
image: python:3.11-bookworm
|
||||||
script:
|
script:
|
||||||
- tox -e docs
|
- tox -e docs
|
||||||
|
|
||||||
deploy_production:
|
deploy_production:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
image: python:3.10-bullseye
|
image: python:3.11-bookworm
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- python -m pip install --upgrade pip
|
- python -m pip install --upgrade pip
|
||||||
@@ -217,10 +215,10 @@ deploy_production:
|
|||||||
|
|
||||||
build-image:
|
build-image:
|
||||||
before_script: []
|
before_script: []
|
||||||
image: docker:20.10.10
|
image: docker:24.0
|
||||||
stage: docker
|
stage: docker
|
||||||
services:
|
services:
|
||||||
- docker:20.10.10-dind
|
- docker:24.0-dind
|
||||||
script: |
|
script: |
|
||||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
|
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
|
||||||
@@ -230,12 +228,10 @@ build-image:
|
|||||||
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
|
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
|
||||||
|
|
||||||
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
|
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
|
||||||
docker tag $IMAGE_TAG $CURRENT_TAG
|
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||||
docker tag $IMAGE_TAG $MINOR_TAG
|
docker buildx create --use --name new-builder
|
||||||
docker tag $IMAGE_TAG $MAJOR_TAG
|
docker buildx build . --tag $IMAGE_TAG --tag $CURRENT_TAG --tag $MINOR_TAG --tag $MAJOR_TAG --tag $LATEST_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
|
||||||
docker tag $IMAGE_TAG $LATEST_TAG
|
|
||||||
docker image push --all-tags $CI_REGISTRY_IMAGE/auth
|
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_TAG
|
- if: $CI_COMMIT_TAG
|
||||||
when: delayed
|
when: delayed
|
||||||
@@ -243,17 +239,19 @@ build-image:
|
|||||||
|
|
||||||
build-image-dev:
|
build-image-dev:
|
||||||
before_script: []
|
before_script: []
|
||||||
image: docker:20.10.10
|
image: docker:24.0
|
||||||
stage: docker
|
stage: docker
|
||||||
services:
|
services:
|
||||||
- docker:20.10.10-dind
|
- docker:24.0-dind
|
||||||
script: |
|
script: |
|
||||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
|
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
|
||||||
|
|
||||||
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
|
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
|
||||||
docker push $IMAGE_TAG
|
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||||
|
docker buildx create --use --name new-builder
|
||||||
|
docker buildx build . --tag $IMAGE_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
|
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
|
||||||
when: manual
|
when: manual
|
||||||
@@ -262,17 +260,19 @@ build-image-dev:
|
|||||||
|
|
||||||
build-image-mr:
|
build-image-mr:
|
||||||
before_script: []
|
before_script: []
|
||||||
image: docker:20.10.10
|
image: docker:24.0
|
||||||
stage: docker
|
stage: docker
|
||||||
services:
|
services:
|
||||||
- docker:20.10.10-dind
|
- docker:24.0-dind
|
||||||
script: |
|
script: |
|
||||||
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
|
||||||
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA
|
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA
|
||||||
|
|
||||||
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
docker build . -t $IMAGE_TAG -f docker/Dockerfile --build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
|
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
|
||||||
docker push $IMAGE_TAG
|
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||||
|
docker buildx create --use --name new-builder
|
||||||
|
docker buildx build . --tag $IMAGE_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
when: manual
|
when: manual
|
||||||
|
|||||||
@@ -4,8 +4,21 @@
|
|||||||
# pre-commit autoupdate
|
# pre-commit autoupdate
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
|
# Code Upgrades
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v3.15.2
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: [--py38-plus]
|
||||||
|
- repo: https://github.com/adamchainz/django-upgrade
|
||||||
|
rev: 1.17.0
|
||||||
|
hooks:
|
||||||
|
- id: django-upgrade
|
||||||
|
args: [--target-version=4.2]
|
||||||
|
|
||||||
|
# Formatting
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.6.0
|
||||||
hooks:
|
hooks:
|
||||||
# Identify invalid files
|
# Identify invalid files
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
@@ -13,14 +26,12 @@ repos:
|
|||||||
- id: check-json
|
- id: check-json
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-xml
|
- id: check-xml
|
||||||
|
|
||||||
# git checks
|
# git checks
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
args: [--maxkb=1000]
|
args: [--maxkb=1000]
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
|
|
||||||
# Python checks
|
# Python checks
|
||||||
# - id: check-docstring-first
|
# - id: check-docstring-first
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
@@ -28,7 +39,6 @@ repos:
|
|||||||
- id: fix-encoding-pragma
|
- id: fix-encoding-pragma
|
||||||
args: [--remove]
|
args: [--remove]
|
||||||
- id: fix-byte-order-marker
|
- id: fix-byte-order-marker
|
||||||
|
|
||||||
# General quality checks
|
# General quality checks
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
args: [--fix=lf]
|
args: [--fix=lf]
|
||||||
@@ -52,9 +62,8 @@ repos:
|
|||||||
\.mo|
|
\.mo|
|
||||||
swagger\.json
|
swagger\.json
|
||||||
)
|
)
|
||||||
|
|
||||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||||
rev: 2.7.2
|
rev: 2.7.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: editorconfig-checker
|
- id: editorconfig-checker
|
||||||
exclude: |
|
exclude: |
|
||||||
@@ -65,15 +74,26 @@ repos:
|
|||||||
\.mo|
|
\.mo|
|
||||||
swagger\.json
|
swagger\.json
|
||||||
)
|
)
|
||||||
|
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||||
- repo: https://github.com/adamchainz/django-upgrade
|
rev: v0.41.0
|
||||||
rev: 1.14.0
|
|
||||||
hooks:
|
hooks:
|
||||||
- id: django-upgrade
|
- id: markdownlint
|
||||||
args: [ --target-version=4.0 ]
|
args:
|
||||||
|
- --disable=MD013
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
# Infrastructure
|
||||||
rev: v3.10.1
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
|
rev: 2.1.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyproject-fmt
|
||||||
args: [ --py38-plus ]
|
name: pyproject.toml formatter
|
||||||
|
description: "Format the pyproject.toml file."
|
||||||
|
args:
|
||||||
|
- --indent=4
|
||||||
|
additional_dependencies:
|
||||||
|
- tox==4.15.0 # https://github.com/tox-dev/tox/releases/latest
|
||||||
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
|
rev: v0.18
|
||||||
|
hooks:
|
||||||
|
- id: validate-pyproject
|
||||||
|
name: Validate pyproject.toml
|
||||||
|
description: "Validate the pyproject.toml file."
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ build:
|
|||||||
- redis
|
- redis
|
||||||
tools:
|
tools:
|
||||||
python: "3.11"
|
python: "3.11"
|
||||||
|
jobs:
|
||||||
|
post_system_dependencies:
|
||||||
|
- redis-server --daemonize yes
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
sphinx:
|
sphinx:
|
||||||
|
|||||||
10
.tx/config
10
.tx/config
@@ -1,10 +0,0 @@
|
|||||||
[main]
|
|
||||||
host = https://app.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
|
|
||||||
@@ -5,7 +5,7 @@ manage online service access.
|
|||||||
# This will make sure the app is always imported when
|
# This will make sure the app is always imported when
|
||||||
# Django starts so that shared_task will use this app.
|
# Django starts so that shared_task will use this app.
|
||||||
|
|
||||||
__version__ = '3.8.1'
|
__version__ = '4.2.0'
|
||||||
__title__ = 'Alliance Auth'
|
__title__ = 'Alliance Auth'
|
||||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||||
NAME = f'{__title__} v{__version__}'
|
NAME = f'{__title__} v{__version__}'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
|
from .models import AnalyticsIdentifier, AnalyticsTokens
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AnalyticsIdentifier)
|
@admin.register(AnalyticsIdentifier)
|
||||||
@@ -13,9 +13,3 @@ class AnalyticsIdentifierAdmin(admin.ModelAdmin):
|
|||||||
class AnalyticsTokensAdmin(admin.ModelAdmin):
|
class AnalyticsTokensAdmin(admin.ModelAdmin):
|
||||||
search_fields = ['name', ]
|
search_fields = ['name', ]
|
||||||
list_display = ('name', 'type',)
|
list_display = ('name', 'type',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(AnalyticsPath)
|
|
||||||
class AnalyticsPathAdmin(admin.ModelAdmin):
|
|
||||||
search_fields = ['ignore_path', ]
|
|
||||||
list_display = ('ignore_path',)
|
|
||||||
|
|||||||
@@ -4,6 +4,3 @@ from django.apps import AppConfig
|
|||||||
class AnalyticsConfig(AppConfig):
|
class AnalyticsConfig(AppConfig):
|
||||||
name = 'allianceauth.analytics'
|
name = 'allianceauth.analytics'
|
||||||
label = 'analytics'
|
label = 'analytics'
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import allianceauth.analytics.signals
|
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
"model": "analytics.AnalyticsTokens",
|
"model": "analytics.AnalyticsTokens",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "AA Team Public Google Analytics (Universal)",
|
"name": "AA Team Public Google Analytics (V4)",
|
||||||
"type": "GA-V4",
|
"type": "GA-V4",
|
||||||
"token": "UA-186249766-2",
|
"token": "G-6LYSMYK8DE",
|
||||||
"send_page_views": "False",
|
"secret": "KLlpjLZ-SRGozS5f5wb_kw",
|
||||||
"send_celery_tasks": "False",
|
|
||||||
"send_stats": "False"
|
"send_stats": "False"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
|
||||||
from .models import AnalyticsTokens, AnalyticsIdentifier
|
|
||||||
from .tasks import send_ga_tracking_web_view
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class AnalyticsMiddleware(MiddlewareMixin):
|
|
||||||
def process_response(self, request, response):
|
|
||||||
"""Django Middleware: Process Page Views and creates Analytics Celery Tasks"""
|
|
||||||
if getattr(settings, "ANALYTICS_DISABLED", False):
|
|
||||||
return response
|
|
||||||
analyticstokens = AnalyticsTokens.objects.all()
|
|
||||||
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
|
|
||||||
try:
|
|
||||||
title = BeautifulSoup(
|
|
||||||
response.content, "html.parser").html.head.title.text
|
|
||||||
except AttributeError:
|
|
||||||
title = ''
|
|
||||||
for token in analyticstokens:
|
|
||||||
# Check if Page View Sending is Disabled
|
|
||||||
if token.send_page_views is False:
|
|
||||||
continue
|
|
||||||
# Check Exclusions
|
|
||||||
ignore = False
|
|
||||||
for ignore_path in token.ignore_paths.values():
|
|
||||||
ignore_path_regex = re.compile(ignore_path["ignore_path"])
|
|
||||||
if re.search(ignore_path_regex, request.path) is not None:
|
|
||||||
ignore = True
|
|
||||||
|
|
||||||
if ignore is True:
|
|
||||||
continue
|
|
||||||
|
|
||||||
tracking_id = token.token
|
|
||||||
locale = request.LANGUAGE_CODE
|
|
||||||
path = request.path
|
|
||||||
try:
|
|
||||||
useragent = request.headers["User-Agent"]
|
|
||||||
except KeyError:
|
|
||||||
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
|
||||||
|
|
||||||
send_ga_tracking_web_view.s(tracking_id=tracking_id,
|
|
||||||
client_id=client_id,
|
|
||||||
page=path,
|
|
||||||
title=title,
|
|
||||||
locale=locale,
|
|
||||||
useragent=useragent).\
|
|
||||||
apply_async(priority=9)
|
|
||||||
return response
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.0.6 on 2022-08-30 05:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('analytics', '0006_more_ignore_paths'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='analyticstokens',
|
||||||
|
name='secret',
|
||||||
|
field=models.CharField(blank=True, max_length=254),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Generated by Django 3.1.4 on 2020-12-30 08:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
|
||||||
|
def add_aa_team_token(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||||
|
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
|
||||||
|
token = Tokens()
|
||||||
|
try:
|
||||||
|
ua_token = Tokens.objects.get(token="UA-186249766-2")
|
||||||
|
original_send_page_views = ua_token.send_page_views
|
||||||
|
original_send_celery_tasks = ua_token.send_celery_tasks
|
||||||
|
original_send_stats = ua_token.send_stats
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
original_send_page_views = True
|
||||||
|
original_send_celery_tasks = True
|
||||||
|
original_send_stats = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_notifications_count = AnalyticsPath.objects.get(ignore_path=r"^\/user_notifications_count\/.*",)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
user_notifications_count = AnalyticsPath.objects.create(ignore_path=r"^\/user_notifications_count\/.*")
|
||||||
|
|
||||||
|
try:
|
||||||
|
admin = AnalyticsPath.objects.get(ignore_path=r"^\/admin\/.*")
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
|
||||||
|
|
||||||
|
try:
|
||||||
|
account_activate = AnalyticsPath.objects.get(ignore_path=r"^\/account\/activate\/.*")
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
account_activate = AnalyticsPath.objects.create(ignore_path=r"^\/account\/activate\/.*")
|
||||||
|
|
||||||
|
token.type = 'GA-V4'
|
||||||
|
token.token = 'G-6LYSMYK8DE'
|
||||||
|
token.secret = 'KLlpjLZ-SRGozS5f5wb_kw'
|
||||||
|
token.send_page_views = original_send_page_views
|
||||||
|
token.send_celery_tasks = original_send_celery_tasks
|
||||||
|
token.send_stats = original_send_stats
|
||||||
|
token.name = 'AA Team Public Google Analytics (V4)'
|
||||||
|
token.save()
|
||||||
|
token.ignore_paths.add(admin, user_notifications_count, account_activate)
|
||||||
|
token.save()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_aa_team_token(apps, schema_editor):
|
||||||
|
# Have to define some code to remove this identifier
|
||||||
|
# In case of migration rollback?
|
||||||
|
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
|
||||||
|
token = Tokens.objects.filter(token="G-6LYSMYK8DE").delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('analytics', '0007_analyticstokens_secret'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(
|
||||||
|
add_aa_team_token, remove_aa_team_token)]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 4.0.10 on 2023-05-08 05:24
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('analytics', '0008_add_AA_GA-4_Team_Token '),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='analyticstokens',
|
||||||
|
name='ignore_paths',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='analyticstokens',
|
||||||
|
name='send_celery_tasks',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='analyticstokens',
|
||||||
|
name='send_page_views',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='AnalyticsPath',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,7 +7,8 @@ from uuid import uuid4
|
|||||||
|
|
||||||
class AnalyticsIdentifier(models.Model):
|
class AnalyticsIdentifier(models.Model):
|
||||||
|
|
||||||
identifier = models.UUIDField(default=uuid4,
|
identifier = models.UUIDField(
|
||||||
|
default=uuid4,
|
||||||
editable=False)
|
editable=False)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@@ -19,10 +20,6 @@ class AnalyticsIdentifier(models.Model):
|
|||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AnalyticsPath(models.Model):
|
|
||||||
ignore_path = models.CharField(max_length=254, default="/example/", help_text="Regex Expression, If matched no Analytics Page View is sent")
|
|
||||||
|
|
||||||
|
|
||||||
class AnalyticsTokens(models.Model):
|
class AnalyticsTokens(models.Model):
|
||||||
|
|
||||||
class Analytics_Type(models.TextChoices):
|
class Analytics_Type(models.TextChoices):
|
||||||
@@ -32,7 +29,5 @@ class AnalyticsTokens(models.Model):
|
|||||||
name = models.CharField(max_length=254)
|
name = models.CharField(max_length=254)
|
||||||
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
|
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
|
||||||
token = models.CharField(max_length=254, blank=False)
|
token = models.CharField(max_length=254, blank=False)
|
||||||
send_page_views = models.BooleanField(default=False)
|
secret = models.CharField(max_length=254, blank=True)
|
||||||
send_celery_tasks = models.BooleanField(default=False)
|
|
||||||
send_stats = models.BooleanField(default=False)
|
send_stats = models.BooleanField(default=False)
|
||||||
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import logging
|
|
||||||
from celery.signals import task_failure, task_success
|
|
||||||
from django.conf import settings
|
|
||||||
from allianceauth.analytics.tasks import analytics_event
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@task_failure.connect
|
|
||||||
def process_failure_signal(
|
|
||||||
exception, traceback,
|
|
||||||
sender, task_id, signal,
|
|
||||||
args, kwargs, einfo, **kw):
|
|
||||||
logger.debug("Celery task_failure signal %s" % sender.__class__.__name__)
|
|
||||||
if getattr(settings, "ANALYTICS_DISABLED", False):
|
|
||||||
return
|
|
||||||
|
|
||||||
category = sender.__module__
|
|
||||||
|
|
||||||
if 'allianceauth.analytics' not in category:
|
|
||||||
if category.endswith(".tasks"):
|
|
||||||
category = category[:-6]
|
|
||||||
|
|
||||||
action = sender.__name__
|
|
||||||
|
|
||||||
label = f"{exception.__class__.__name__}"
|
|
||||||
|
|
||||||
analytics_event(category=category,
|
|
||||||
action=action,
|
|
||||||
label=label)
|
|
||||||
|
|
||||||
|
|
||||||
@task_success.connect
|
|
||||||
def celery_success_signal(sender, result=None, **kw):
|
|
||||||
logger.debug("Celery task_success signal %s" % sender.__class__.__name__)
|
|
||||||
if getattr(settings, "ANALYTICS_DISABLED", False):
|
|
||||||
return
|
|
||||||
|
|
||||||
category = sender.__module__
|
|
||||||
|
|
||||||
if 'allianceauth.analytics' not in category:
|
|
||||||
if category.endswith(".tasks"):
|
|
||||||
category = category[:-6]
|
|
||||||
|
|
||||||
action = sender.__name__
|
|
||||||
label = "Success"
|
|
||||||
|
|
||||||
value = 0
|
|
||||||
if isinstance(result, int):
|
|
||||||
value = result
|
|
||||||
|
|
||||||
analytics_event(category=category,
|
|
||||||
action=action,
|
|
||||||
label=label,
|
|
||||||
value=value)
|
|
||||||
@@ -3,23 +3,24 @@ import logging
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from allianceauth import __version__
|
|
||||||
from .models import AnalyticsTokens, AnalyticsIdentifier
|
from .models import AnalyticsTokens, AnalyticsIdentifier
|
||||||
from .utils import (
|
from .utils import (
|
||||||
install_stat_addons,
|
install_stat_addons,
|
||||||
install_stat_tokens,
|
install_stat_tokens,
|
||||||
install_stat_users)
|
install_stat_users)
|
||||||
|
|
||||||
|
from allianceauth import __version__
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
BASE_URL = "https://www.google-analytics.com/"
|
BASE_URL = "https://www.google-analytics.com"
|
||||||
|
|
||||||
DEBUG_URL = f"{BASE_URL}debug/collect"
|
DEBUG_URL = f"{BASE_URL}/debug/mp/collect"
|
||||||
COLLECTION_URL = f"{BASE_URL}collect"
|
COLLECTION_URL = f"{BASE_URL}/mp/collect"
|
||||||
|
|
||||||
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
|
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
|
||||||
# Force sending of analytics data during in a debug/test environemt
|
# Force sending of analytics data during in a debug/test environment
|
||||||
# Usefull for developers working on this feature.
|
# Useful for developers working on this feature.
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
|
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
|
||||||
"This debug instance will send analytics data!")
|
"This debug instance will send analytics data!")
|
||||||
@@ -31,40 +32,38 @@ if settings.DEBUG is True:
|
|||||||
ANALYTICS_URL = DEBUG_URL
|
ANALYTICS_URL = DEBUG_URL
|
||||||
|
|
||||||
|
|
||||||
def analytics_event(category: str,
|
def analytics_event(namespace: str,
|
||||||
action: str,
|
task: str,
|
||||||
label: str,
|
label: str = "",
|
||||||
value: int = 0,
|
result: str = "",
|
||||||
|
value: int = 1,
|
||||||
event_type: str = 'Celery'):
|
event_type: str = 'Celery'):
|
||||||
"""
|
"""
|
||||||
Send a Google Analytics Event for each token stored
|
Send a Google Analytics Event for each token stored
|
||||||
Includes check for if its enabled/disabled
|
Includes check for if its enabled/disabled
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
`category` (str): Celery Namespace
|
`namespace` (str): Celery Namespace
|
||||||
`action` (str): Task Name
|
`task` (str): Task Name
|
||||||
`label` (str): Optional, Task Success/Exception
|
`label` (str): Optional, additional task label
|
||||||
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
`result` (str): Optional, Task Success/Exception
|
||||||
|
`value` (int): Optional, If bulk, Query size, can be a Boolean
|
||||||
`event_type` (str): Optional, Celery or Stats only, Default to Celery
|
`event_type` (str): Optional, Celery or Stats only, Default to Celery
|
||||||
"""
|
"""
|
||||||
analyticstokens = AnalyticsTokens.objects.all()
|
for token in AnalyticsTokens.objects.filter(type='GA-V4'):
|
||||||
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
|
if event_type == 'Stats':
|
||||||
for token in analyticstokens:
|
|
||||||
if event_type == 'Celery':
|
|
||||||
allowed = token.send_celery_tasks
|
|
||||||
elif event_type == 'Stats':
|
|
||||||
allowed = token.send_stats
|
allowed = token.send_stats
|
||||||
else:
|
else:
|
||||||
allowed = False
|
allowed = False
|
||||||
|
|
||||||
if allowed is True:
|
if allowed is True:
|
||||||
tracking_id = token.token
|
|
||||||
send_ga_tracking_celery_event.s(
|
send_ga_tracking_celery_event.s(
|
||||||
tracking_id=tracking_id,
|
measurement_id=token.token,
|
||||||
client_id=client_id,
|
secret=token.secret,
|
||||||
category=category,
|
namespace=namespace,
|
||||||
action=action,
|
task=task,
|
||||||
label=label,
|
label=label,
|
||||||
|
result=result,
|
||||||
value=value).apply_async(priority=9)
|
value=value).apply_async(priority=9)
|
||||||
|
|
||||||
|
|
||||||
@@ -72,136 +71,131 @@ def analytics_event(category: str,
|
|||||||
def analytics_daily_stats():
|
def analytics_daily_stats():
|
||||||
"""Celery Task: Do not call directly
|
"""Celery Task: Do not call directly
|
||||||
|
|
||||||
Gathers a series of daily statistics and sends analytics events containing them
|
Gathers a series of daily statistics
|
||||||
|
Sends analytics events containing them
|
||||||
"""
|
"""
|
||||||
users = install_stat_users()
|
users = install_stat_users()
|
||||||
tokens = install_stat_tokens()
|
tokens = install_stat_tokens()
|
||||||
addons = install_stat_addons()
|
addons = install_stat_addons()
|
||||||
logger.debug("Running Daily Analytics Upload")
|
logger.debug("Running Daily Analytics Upload")
|
||||||
|
|
||||||
analytics_event(category='allianceauth.analytics',
|
analytics_event(namespace='allianceauth.analytics',
|
||||||
action='send_install_stats',
|
task='send_install_stats',
|
||||||
label='existence',
|
label='existence',
|
||||||
value=1,
|
value=1,
|
||||||
event_type='Stats')
|
event_type='Stats')
|
||||||
analytics_event(category='allianceauth.analytics',
|
analytics_event(namespace='allianceauth.analytics',
|
||||||
action='send_install_stats',
|
task='send_install_stats',
|
||||||
label='users',
|
label='users',
|
||||||
value=users,
|
value=users,
|
||||||
event_type='Stats')
|
event_type='Stats')
|
||||||
analytics_event(category='allianceauth.analytics',
|
analytics_event(namespace='allianceauth.analytics',
|
||||||
action='send_install_stats',
|
task='send_install_stats',
|
||||||
label='tokens',
|
label='tokens',
|
||||||
value=tokens,
|
value=tokens,
|
||||||
event_type='Stats')
|
event_type='Stats')
|
||||||
analytics_event(category='allianceauth.analytics',
|
analytics_event(namespace='allianceauth.analytics',
|
||||||
action='send_install_stats',
|
task='send_install_stats',
|
||||||
label='addons',
|
label='addons',
|
||||||
value=addons,
|
value=addons,
|
||||||
event_type='Stats')
|
event_type='Stats')
|
||||||
|
|
||||||
for appconfig in apps.get_app_configs():
|
for appconfig in apps.get_app_configs():
|
||||||
analytics_event(category='allianceauth.analytics',
|
if appconfig.label in [
|
||||||
action='send_extension_stats',
|
"django_celery_beat",
|
||||||
|
"bootstrapform",
|
||||||
|
"messages",
|
||||||
|
"sessions",
|
||||||
|
"auth",
|
||||||
|
"staticfiles",
|
||||||
|
"users",
|
||||||
|
"addons",
|
||||||
|
"admin",
|
||||||
|
"humanize",
|
||||||
|
"contenttypes",
|
||||||
|
"sortedm2m",
|
||||||
|
"django_bootstrap5",
|
||||||
|
"tokens",
|
||||||
|
"authentication",
|
||||||
|
"services",
|
||||||
|
"framework",
|
||||||
|
"notifications"
|
||||||
|
"eveonline",
|
||||||
|
"navhelper",
|
||||||
|
"analytics",
|
||||||
|
"menu",
|
||||||
|
"theme"
|
||||||
|
]:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
analytics_event(namespace='allianceauth.analytics',
|
||||||
|
task='send_extension_stats',
|
||||||
label=appconfig.label,
|
label=appconfig.label,
|
||||||
value=1,
|
value=1,
|
||||||
event_type='Stats')
|
event_type='Stats')
|
||||||
|
|
||||||
|
|
||||||
@shared_task()
|
|
||||||
def send_ga_tracking_web_view(
|
|
||||||
tracking_id: str,
|
|
||||||
client_id: str,
|
|
||||||
page: str,
|
|
||||||
title: str,
|
|
||||||
locale: str,
|
|
||||||
useragent: str) -> requests.Response:
|
|
||||||
|
|
||||||
"""Celery Task: Do not call directly
|
|
||||||
|
|
||||||
Sends Page View events to GA, Called only via analytics.middleware
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
`tracking_id` (str): Unique Server Identifier
|
|
||||||
`client_id` (str): GA Token
|
|
||||||
`page` (str): Page Path
|
|
||||||
`title` (str): Page Title
|
|
||||||
`locale` (str): Browser Language
|
|
||||||
`useragent` (str): Browser UserAgent
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
requests.Reponse Object
|
|
||||||
"""
|
|
||||||
headers = {"User-Agent": useragent}
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
'v': '1',
|
|
||||||
'tid': tracking_id,
|
|
||||||
'cid': client_id,
|
|
||||||
't': 'pageview',
|
|
||||||
'dp': page,
|
|
||||||
'dt': title,
|
|
||||||
'ul': locale,
|
|
||||||
'ua': useragent,
|
|
||||||
'aip': 1,
|
|
||||||
'an': "allianceauth",
|
|
||||||
'av': __version__
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
ANALYTICS_URL, data=payload,
|
|
||||||
timeout=5, headers=headers)
|
|
||||||
logger.debug(f"Analytics Page View HTTP{response.status_code}")
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task()
|
@shared_task()
|
||||||
def send_ga_tracking_celery_event(
|
def send_ga_tracking_celery_event(
|
||||||
tracking_id: str,
|
measurement_id: str,
|
||||||
client_id: str,
|
secret: str,
|
||||||
category: str,
|
namespace: str,
|
||||||
action: str,
|
task: str,
|
||||||
label: str,
|
label: str = "",
|
||||||
value: int) -> requests.Response:
|
result: str = "",
|
||||||
|
value: int = 1):
|
||||||
"""Celery Task: Do not call directly
|
"""Celery Task: Do not call directly
|
||||||
|
|
||||||
Sends Page View events to GA, Called only via analytics.middleware
|
Sends an events to GA
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
`tracking_id` (str): Unique Server Identifier
|
`measurement_id` (str): GA Token
|
||||||
`client_id` (str): GA Token
|
`secret` (str): GA Authentication Secret
|
||||||
`category` (str): Celery Namespace
|
`namespace` (str): Celery Namespace
|
||||||
`action` (str): Task Name
|
`task` (str): Task Name
|
||||||
`label` (str): Optional, Task Success/Exception
|
`label` (str): Optional, additional task label
|
||||||
|
`result` (str): Optional, Task Success/Exception
|
||||||
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
`value` (int): Optional, If bulk, Query size, can be a binary True/False
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
requests.Reponse Object
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
headers = {
|
parameters = {
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
|
'measurement_id': measurement_id,
|
||||||
|
'api_secret': secret
|
||||||
payload = {
|
|
||||||
'v': '1',
|
|
||||||
'tid': tracking_id,
|
|
||||||
'cid': client_id,
|
|
||||||
't': 'event',
|
|
||||||
'ec': category,
|
|
||||||
'ea': action,
|
|
||||||
'el': label,
|
|
||||||
'ev': value,
|
|
||||||
'aip': 1,
|
|
||||||
'an': "allianceauth",
|
|
||||||
'av': __version__
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
|
||||||
|
"user_properties": {
|
||||||
|
"allianceauth_version": {
|
||||||
|
"value": __version__
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'non_personalized_ads': True,
|
||||||
|
"events": [{
|
||||||
|
"name": "celery_event",
|
||||||
|
"params": {
|
||||||
|
"namespace": namespace,
|
||||||
|
"task": task,
|
||||||
|
'result': result,
|
||||||
|
'label': label,
|
||||||
|
"value": value
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
ANALYTICS_URL, data=payload,
|
ANALYTICS_URL,
|
||||||
timeout=5, headers=headers)
|
params=parameters,
|
||||||
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
|
json=payload,
|
||||||
return response
|
timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
logger.debug(
|
||||||
|
f"Analytics Celery/Stats Event HTTP{response.status_code}")
|
||||||
|
return response.status_code
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
logger.debug(e)
|
||||||
|
return response.status_code
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
logger.debug(e)
|
||||||
|
return "Failed"
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
from unittest.mock import patch
|
|
||||||
from urllib.parse import parse_qs
|
|
||||||
|
|
||||||
import requests_mock
|
|
||||||
|
|
||||||
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(NoSocketsTestCase):
|
|
||||||
@override_settings(ANALYTICS_DISABLED=False)
|
|
||||||
def test_should_run_analytics(self, requests_mocker):
|
|
||||||
# given
|
|
||||||
requests_mocker.post(ANALYTICS_URL)
|
|
||||||
user = AuthUtils.create_user("Bruce Wayne")
|
|
||||||
self.client.force_login(user)
|
|
||||||
# when
|
|
||||||
response = self.client.get("/dashboard/")
|
|
||||||
# then
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertTrue(requests_mocker.called)
|
|
||||||
|
|
||||||
@override_settings(ANALYTICS_DISABLED=True)
|
|
||||||
def test_should_not_run_analytics(self, requests_mocker):
|
|
||||||
# given
|
|
||||||
requests_mocker.post(ANALYTICS_URL)
|
|
||||||
user = AuthUtils.create_user("Bruce Wayne")
|
|
||||||
self.client.force_login(user)
|
|
||||||
# when
|
|
||||||
response = self.client.get("/dashboard/")
|
|
||||||
# then
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertFalse(requests_mocker.called)
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(CELERY_ALWAYS_EAGER=True)
|
|
||||||
@requests_mock.mock()
|
|
||||||
class TestAnalyticsForTasks(NoSocketsTestCase):
|
|
||||||
@override_settings(ANALYTICS_DISABLED=False)
|
|
||||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
|
||||||
def test_should_run_analytics_for_successful_task(
|
|
||||||
self, requests_mocker, mock_update_character
|
|
||||||
):
|
|
||||||
# given
|
|
||||||
requests_mocker.post(ANALYTICS_URL)
|
|
||||||
user = AuthUtils.create_user("Bruce Wayne")
|
|
||||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
|
||||||
# when
|
|
||||||
update_character.delay(character.character_id)
|
|
||||||
# then
|
|
||||||
self.assertTrue(mock_update_character.called)
|
|
||||||
self.assertTrue(requests_mocker.called)
|
|
||||||
payload = parse_qs(requests_mocker.last_request.text)
|
|
||||||
self.assertListEqual(payload["el"], ["Success"])
|
|
||||||
|
|
||||||
@override_settings(ANALYTICS_DISABLED=True)
|
|
||||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
|
||||||
def test_should_not_run_analytics_for_successful_task(
|
|
||||||
self, requests_mocker, mock_update_character
|
|
||||||
):
|
|
||||||
# given
|
|
||||||
requests_mocker.post(ANALYTICS_URL)
|
|
||||||
user = AuthUtils.create_user("Bruce Wayne")
|
|
||||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
|
||||||
# when
|
|
||||||
update_character.delay(character.character_id)
|
|
||||||
# then
|
|
||||||
self.assertTrue(mock_update_character.called)
|
|
||||||
self.assertFalse(requests_mocker.called)
|
|
||||||
|
|
||||||
@override_settings(ANALYTICS_DISABLED=False)
|
|
||||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
|
||||||
def test_should_run_analytics_for_failed_task(
|
|
||||||
self, requests_mocker, mock_update_character
|
|
||||||
):
|
|
||||||
# given
|
|
||||||
requests_mocker.post(ANALYTICS_URL)
|
|
||||||
mock_update_character.side_effect = RuntimeError
|
|
||||||
user = AuthUtils.create_user("Bruce Wayne")
|
|
||||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
|
||||||
# when
|
|
||||||
update_character.delay(character.character_id)
|
|
||||||
# then
|
|
||||||
self.assertTrue(mock_update_character.called)
|
|
||||||
self.assertTrue(requests_mocker.called)
|
|
||||||
payload = parse_qs(requests_mocker.last_request.text)
|
|
||||||
self.assertNotEqual(payload["el"], ["Success"])
|
|
||||||
|
|
||||||
@override_settings(ANALYTICS_DISABLED=True)
|
|
||||||
@patch("allianceauth.eveonline.models.EveCharacter.objects.update_character")
|
|
||||||
def test_should_not_run_analytics_for_failed_task(
|
|
||||||
self, requests_mocker, mock_update_character
|
|
||||||
):
|
|
||||||
# given
|
|
||||||
requests_mocker.post(ANALYTICS_URL)
|
|
||||||
mock_update_character.side_effect = RuntimeError
|
|
||||||
user = AuthUtils.create_user("Bruce Wayne")
|
|
||||||
character = AuthUtils.add_main_character_2(user, "Bruce Wayne", 1001)
|
|
||||||
# when
|
|
||||||
update_character.delay(character.character_id)
|
|
||||||
# then
|
|
||||||
self.assertTrue(mock_update_character.called)
|
|
||||||
self.assertFalse(requests_mocker.called)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
from allianceauth.analytics.middleware import AnalyticsMiddleware
|
|
||||||
from unittest.mock import Mock
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
from django.test.testcases import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestAnalyticsMiddleware(TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.middleware = AnalyticsMiddleware(HttpResponse)
|
|
||||||
self.request = Mock()
|
|
||||||
self.request.headers = {
|
|
||||||
"User-Agent": "AUTOMATED TEST"
|
|
||||||
}
|
|
||||||
self.request.path = '/testURL/'
|
|
||||||
self.request.session = {}
|
|
||||||
self.request.LANGUAGE_CODE = 'en'
|
|
||||||
self.response = Mock()
|
|
||||||
self.response.content = 'hello world'
|
|
||||||
|
|
||||||
def test_middleware(self):
|
|
||||||
response = self.middleware.process_response(self.request, self.response)
|
|
||||||
self.assertEqual(self.response, response)
|
|
||||||
@@ -23,4 +23,5 @@ class TestAnalyticsIdentifier(TestCase):
|
|||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
AnalyticsIdentifier.objects.create(identifier=uuid_2)
|
AnalyticsIdentifier.objects.create(identifier=uuid_2)
|
||||||
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
|
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
|
||||||
self.assertEqual(AnalyticsIdentifier.objects.get(pk=1).identifier, UUID(uuid_1))
|
self.assertEqual(AnalyticsIdentifier.objects.get(
|
||||||
|
pk=1).identifier, UUID(uuid_1))
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ from django.test.utils import override_settings
|
|||||||
|
|
||||||
from allianceauth.analytics.tasks import (
|
from allianceauth.analytics.tasks import (
|
||||||
analytics_event,
|
analytics_event,
|
||||||
send_ga_tracking_celery_event,
|
send_ga_tracking_celery_event)
|
||||||
send_ga_tracking_web_view)
|
|
||||||
from allianceauth.utils.testing import NoSocketsTestCase
|
from allianceauth.utils.testing import NoSocketsTestCase
|
||||||
|
|
||||||
|
|
||||||
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/collect'
|
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/mp/collect'
|
||||||
|
|
||||||
|
|
||||||
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
|
||||||
@@ -18,195 +17,53 @@ class TestAnalyticsTasks(NoSocketsTestCase):
|
|||||||
def test_analytics_event(self, requests_mocker):
|
def test_analytics_event(self, requests_mocker):
|
||||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||||
analytics_event(
|
analytics_event(
|
||||||
category='allianceauth.analytics',
|
namespace='allianceauth.analytics',
|
||||||
action='send_tests',
|
task='send_tests',
|
||||||
label='test',
|
label='test',
|
||||||
value=1,
|
value=1,
|
||||||
|
result="Success",
|
||||||
event_type='Stats')
|
event_type='Stats')
|
||||||
|
|
||||||
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,
|
|
||||||
page,
|
|
||||||
title,
|
|
||||||
locale,
|
|
||||||
useragent)
|
|
||||||
# then
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
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,
|
|
||||||
page,
|
|
||||||
title,
|
|
||||||
locale,
|
|
||||||
useragent).json()
|
|
||||||
# then
|
|
||||||
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
|
||||||
|
|
||||||
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,
|
|
||||||
page,
|
|
||||||
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."
|
|
||||||
)
|
|
||||||
|
|
||||||
# [{'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, requests_mocker):
|
def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
|
||||||
# given
|
# given
|
||||||
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
|
||||||
tracking_id = 'UA-186249766-2'
|
token = 'G-6LYSMYK8DE'
|
||||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
|
||||||
category = 'test'
|
category = 'test'
|
||||||
action = 'test'
|
action = 'test'
|
||||||
label = 'test'
|
label = 'test'
|
||||||
value = '1'
|
value = '1'
|
||||||
# when
|
# when
|
||||||
response = send_ga_tracking_celery_event(
|
task = send_ga_tracking_celery_event(
|
||||||
tracking_id,
|
token,
|
||||||
client_id,
|
secret,
|
||||||
category,
|
category,
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
value)
|
value)
|
||||||
# then
|
# then
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(task, 200)
|
||||||
|
|
||||||
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
|
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
|
||||||
# given
|
# given
|
||||||
requests_mocker.register_uri(
|
requests_mocker.register_uri(
|
||||||
'POST',
|
'POST',
|
||||||
GOOGLE_ANALYTICS_DEBUG_URL,
|
GOOGLE_ANALYTICS_DEBUG_URL,
|
||||||
json={"hitParsingResult":[{'valid': True}]}
|
json={"validationMessages": []}
|
||||||
)
|
)
|
||||||
tracking_id = 'UA-186249766-2'
|
token = 'G-6LYSMYK8DE'
|
||||||
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
|
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
|
||||||
category = 'test'
|
category = 'test'
|
||||||
action = 'test'
|
action = 'test'
|
||||||
label = 'test'
|
label = 'test'
|
||||||
value = '1'
|
value = '1'
|
||||||
# when
|
# when
|
||||||
json_response = send_ga_tracking_celery_event(
|
task = send_ga_tracking_celery_event(
|
||||||
tracking_id,
|
token,
|
||||||
client_id,
|
secret,
|
||||||
category,
|
category,
|
||||||
action,
|
action,
|
||||||
label,
|
label,
|
||||||
value).json()
|
value)
|
||||||
# then
|
# then
|
||||||
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
|
self.assertTrue(task, 200)
|
||||||
|
|
||||||
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,
|
|
||||||
category,
|
|
||||||
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."
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.core.checks import Warning, Error, register
|
||||||
|
|
||||||
|
|
||||||
class AllianceAuthConfig(AppConfig):
|
class AllianceAuthConfig(AppConfig):
|
||||||
name = 'allianceauth'
|
name = 'allianceauth'
|
||||||
|
|
||||||
|
def ready(self) -> None:
|
||||||
|
import allianceauth.checks # noqa
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
Behavior of groups and characters columns can be configured via settings
|
Behavior of groups and characters columns can be configured via settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
inlines = BaseUserAdmin.inlines + [UserProfileInline]
|
inlines = [UserProfileInline]
|
||||||
ordering = ('username', )
|
ordering = ('username', )
|
||||||
list_select_related = ('profile__state', 'profile__main_character')
|
list_select_related = ('profile__state', 'profile__main_character')
|
||||||
show_full_result_count = True
|
show_full_result_count = True
|
||||||
|
|||||||
59
allianceauth/authentication/auth_hooks.py
Normal file
59
allianceauth/authentication/auth_hooks.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from allianceauth.hooks import DashboardItemHook
|
||||||
|
from allianceauth import hooks
|
||||||
|
from .views import dashboard_characters, dashboard_esi_check, dashboard_groups, dashboard_admin
|
||||||
|
|
||||||
|
|
||||||
|
class UserCharactersHook(DashboardItemHook):
|
||||||
|
def __init__(self):
|
||||||
|
DashboardItemHook.__init__(
|
||||||
|
self,
|
||||||
|
dashboard_characters,
|
||||||
|
5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserGroupsHook(DashboardItemHook):
|
||||||
|
def __init__(self):
|
||||||
|
DashboardItemHook.__init__(
|
||||||
|
self,
|
||||||
|
dashboard_groups,
|
||||||
|
5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AdminHook(DashboardItemHook):
|
||||||
|
def __init__(self):
|
||||||
|
DashboardItemHook.__init__(
|
||||||
|
self,
|
||||||
|
dashboard_admin,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ESICheckHook(DashboardItemHook):
|
||||||
|
def __init__(self):
|
||||||
|
DashboardItemHook.__init__(
|
||||||
|
self,
|
||||||
|
dashboard_esi_check,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('dashboard_hook')
|
||||||
|
def register_character_hook():
|
||||||
|
return UserCharactersHook()
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('dashboard_hook')
|
||||||
|
def register_groups_hook():
|
||||||
|
return UserGroupsHook()
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('dashboard_hook')
|
||||||
|
def register_admin_hook():
|
||||||
|
return AdminHook()
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register('dashboard_hook')
|
||||||
|
def register_esi_hook():
|
||||||
|
return ESICheckHook()
|
||||||
12
allianceauth/authentication/constants.py
Normal file
12
allianceauth/authentication/constants.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
# Overide ESI messages in the dashboard widget
|
||||||
|
# when the returned messages are not helpful or out of date
|
||||||
|
ESI_ERROR_MESSAGE_OVERRIDES = {
|
||||||
|
420: _("This software has exceeded the error limit for ESI. "
|
||||||
|
"If you are a user, please contact the maintainer of this software."
|
||||||
|
" If you are a developer/maintainer, please make a greater "
|
||||||
|
"effort in the future to receive valid responses. For tips on how, "
|
||||||
|
"come have a chat with us in ##3rd-party-dev-and-esi on the EVE "
|
||||||
|
"Online Discord. https://www.eveonline.com/discord")
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
from django.urls import include
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Callable, Iterable, Optional
|
from typing import Callable, Iterable, Optional
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class UserSettingsMiddleware(MiddlewareMixin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
|
# AA v3 NIGHT_MODE
|
||||||
# Set our Night mode flag from the DB
|
# Set our Night mode flag from the DB
|
||||||
# Null = hasnt been set by the user ever, dont act.
|
# Null = hasnt been set by the user ever, dont act.
|
||||||
#
|
#
|
||||||
@@ -42,4 +43,13 @@ class UserSettingsMiddleware(MiddlewareMixin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
|
# AA v4 Themes
|
||||||
|
# Null = has not been set by the user ever, dont act
|
||||||
|
# DEFAULT_THEME or DEFAULT_THEME_DARK will be used in get_theme()
|
||||||
|
try:
|
||||||
|
if request.user.profile.theme is not None:
|
||||||
|
request.session["THEME"] = request.user.profile.theme
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.0.10 on 2023-10-07 07:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0021_alter_userprofile_language'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='theme',
|
||||||
|
field=models.CharField(blank=True, help_text='Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps', max_length=200, null=True, verbose_name='Theme'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.13 on 2024-05-12 09:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('authentication', '0022_userprofile_theme'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='language',
|
||||||
|
field=models.CharField(blank=True, choices=[('en', 'English'), ('de', 'German'), ('es', 'Spanish'), ('zh-hans', 'Chinese Simplified'), ('ru', 'Russian'), ('ko', 'Korean'), ('fr', 'French'), ('ja', 'Japanese'), ('it', 'Italian'), ('uk', 'Ukrainian'), ('pl', 'Polish')], default='', max_length=10, verbose_name='Language'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -78,6 +78,7 @@ class UserProfile(models.Model):
|
|||||||
JAPANESE = 'ja', _('Japanese')
|
JAPANESE = 'ja', _('Japanese')
|
||||||
ITALIAN = 'it', _('Italian')
|
ITALIAN = 'it', _('Italian')
|
||||||
UKRAINIAN = 'uk', _('Ukrainian')
|
UKRAINIAN = 'uk', _('Ukrainian')
|
||||||
|
POLISH = 'pl', _("Polish")
|
||||||
|
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
User,
|
User,
|
||||||
@@ -101,6 +102,13 @@ class UserProfile(models.Model):
|
|||||||
_("Night Mode"),
|
_("Night Mode"),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
theme = models.CharField(
|
||||||
|
_("Theme"),
|
||||||
|
max_length=200,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text="Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps"
|
||||||
|
)
|
||||||
|
|
||||||
def assign_state(self, state=None, commit=True):
|
def assign_state(self, state=None, commit=True):
|
||||||
if not state:
|
if not state:
|
||||||
|
|||||||
@@ -1,190 +1,15 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Dashboard" %}
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="page-header text-center">{% translate "Dashboard" %}</h1>
|
|
||||||
{% if user.is_staff %}
|
|
||||||
{% include 'allianceauth/admin-status/include.html' %}
|
|
||||||
{% endif %}
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<div class="row vertical-flexbox-row2">
|
|
||||||
<div class="col-sm-6 text-center">
|
|
||||||
<div class="panel panel-primary" style="height:100%">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title">
|
|
||||||
{% blocktranslate with state=request.user.profile.state %}
|
|
||||||
Main Character (State: {{ state }})
|
|
||||||
{% endblocktranslate %}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{% if request.user.profile.main_character %}
|
|
||||||
{% with request.user.profile.main_character as main %}
|
|
||||||
<div class="hidden-xs">
|
|
||||||
<div class="col-lg-4 col-sm-2">
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">
|
|
||||||
<img class="ra-avatar" src="{{ main.portrait_url_128 }}" alt="{{ main.character_name }}">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">{{ main.character_name }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4 col-sm-2">
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">
|
|
||||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_128 }}" alt="{{ main.corporation_name }}">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">{{ main.corporation_name }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-4 col-sm-2">
|
|
||||||
{% if main.alliance_id %}
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">
|
|
||||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_128 }}" alt="{{ main.alliance_name }}">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">{{ main.alliance_name }}</td>
|
|
||||||
<tr>
|
|
||||||
</table>
|
|
||||||
{% elif main.faction_id %}
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">
|
|
||||||
<img class="ra-avatar" src="{{ main.faction_logo_url_128 }}" alt="{{ main.faction_name }}">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">{{ main.faction_name }}</td>
|
|
||||||
<tr>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="table visible-xs-block">
|
|
||||||
<p>
|
|
||||||
<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 }}" alt="{{ main.alliance_name }}">
|
|
||||||
{% endif %}
|
|
||||||
{% if main.faction_id %}
|
|
||||||
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}" alt="{{ main.faction_name }}">
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{{ main.character_name }}</strong><br>
|
|
||||||
{{ main.corporation_name }}<br>
|
|
||||||
{% if main.alliance_id %}
|
|
||||||
{{ main.alliance_name }}<br>
|
|
||||||
{% endif %}
|
|
||||||
{% if main.faction_id %}
|
|
||||||
{{ main.faction_name }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
{% translate "No main character set." %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">
|
{% for dash in views %}
|
||||||
<p>
|
{{ dash | safe }}
|
||||||
<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">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6 text-center">
|
|
||||||
<div class="panel panel-success" style="height:100%">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title">{% translate "Group Memberships" %}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div style="height: 240px;overflow-y:auto;">
|
|
||||||
<table class="table table-aa">
|
|
||||||
{% for group in groups %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ group.name }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title text-center" style="text-align: center">
|
|
||||||
{% translate 'Characters' %}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<table class="table table-aa hidden-xs">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="text-center"></th>
|
|
||||||
<th class="text-center">{% translate 'Name' %}</th>
|
|
||||||
<th class="text-center">{% translate 'Corp' %}</th>
|
|
||||||
<th class="text-center">{% translate 'Alliance' %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for char in characters %}
|
|
||||||
<tr>
|
|
||||||
<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|default:"" }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<table class="table table-aa visible-xs-block" style="width: 100%">
|
|
||||||
<tbody>
|
|
||||||
{% for char in characters %}
|
|
||||||
<tr>
|
|
||||||
<td class="text-center" style="vertical-align: middle">
|
|
||||||
<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>
|
|
||||||
{{ char.corporation_name }}<br>
|
|
||||||
{{ char.alliance_name|default:"" }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'allianceauth/base.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block page_title %}Dashboard{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
<h1>Dashboard Dummy</h1>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div id="aa-dashboard-panel-characters" class="col-12 col-xl-8 mb-3">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
{% translate "Characters" as widget_title %}
|
||||||
|
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="height: 300px; overflow-y:auto;">
|
||||||
|
<div class="d-flex">
|
||||||
|
<a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}">
|
||||||
|
<span class="d-md-inline m-2">{% translate 'Add Character' %}</span>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Change Main' %}">
|
||||||
|
<span class="d-md-inline m-2">{% translate 'Change Main' %}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center"></th>
|
||||||
|
<th class="text-center">{% translate "Name" %}</th>
|
||||||
|
<th class="text-center">{% translate "Corp" %}</th>
|
||||||
|
<th class="text-center">{% translate "Alliance" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for char in characters %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">
|
||||||
|
<img class="ra-avatar rounded-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|default_if_none:"" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<div id="aa-dashboard-panel-membership" class="col-12 col-xl-4 mb-3">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
{% translate "Membership" as widget_title %}
|
||||||
|
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="height: 300px; overflow-y:auto;">
|
||||||
|
<h5 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h5>
|
||||||
|
<table class="table">
|
||||||
|
{% for group in groups %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ group.name }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,62 +1,85 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
{% block page_title %}
|
||||||
|
{% translate "Token Management" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Token Management" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
|
<div>
|
||||||
<div class="col-sm-12">
|
<p class="mb-3">
|
||||||
<table class="table table-aa" id="table_tokens" style="width:100%">
|
{% 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 %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table w-100" id="table_tokens">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Scopes" %}</th>
|
<th>{% translate "Scopes" %}</th>
|
||||||
<th class="text-right">{% translate "Actions" %}</th>
|
<th class="text-end">{% translate "Actions" %}</th>
|
||||||
<th>{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for t in tokens %}
|
{% for t in tokens %}
|
||||||
<tr>
|
<tr>
|
||||||
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td>
|
<td style="white-space:initial;">
|
||||||
<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>
|
{% for s in t.scopes.all %}
|
||||||
|
<span class="badge bg-secondary">{{ s.name }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td nowrap class="text-end">
|
||||||
|
<a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fa-solid fa-trash-can"></i></a>
|
||||||
|
<a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fa-solid fa-rotate"></i></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td>{{ t.character_name }}</td>
|
<td>{{ t.character_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include "bundles/datatables-js-bs5.html" %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
<script>
|
||||||
{% include 'bundles/datatables-css.html' %}
|
$(document).ready(() => {
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_script %}
|
|
||||||
$(document).ready(function(){
|
|
||||||
let grp = 2;
|
let grp = 2;
|
||||||
var table = $('#table_tokens').DataTable({
|
|
||||||
"columnDefs": [{ orderable: false, targets: [0,1] },{ "visible": false, "targets": grp }],
|
const table = $('#table_tokens').DataTable({
|
||||||
"order": [[grp, 'asc']],
|
'columnDefs': [{orderable: false, targets: [0, 1]}, {
|
||||||
"drawCallback": function (settings) {
|
'visible': false,
|
||||||
|
'targets': grp
|
||||||
|
}],
|
||||||
|
'order': [[grp, 'asc']],
|
||||||
|
'drawCallback': function (settings) {
|
||||||
var api = this.api();
|
var api = this.api();
|
||||||
var rows = api.rows({page: 'current'}).nodes();
|
var rows = api.rows({page: 'current'}).nodes();
|
||||||
var last = null;
|
var last = null;
|
||||||
api.column(grp, {page: 'current'})
|
api.column(grp, {page: 'current'})
|
||||||
.data()
|
.data()
|
||||||
.each(function (group, i) {
|
.each((group, i) => {
|
||||||
if (last !== group) {
|
if (last !== group) {
|
||||||
$(rows).eq(i).before('<tr class="info"><td colspan="3">' + group + '</td></tr>');
|
$(rows).eq(i).before(`<tr class="h5 table-primary"><td colspan="3">${group}</td></tr>`);
|
||||||
|
|
||||||
last = group;
|
last = group;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
"stateSave": true,
|
'stateSave': true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
{% endblock %}
|
</script>
|
||||||
|
{% endblock extra_javascript %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include "bundles/datatables-css-bs5.html" %}
|
||||||
|
{% endblock extra_css %}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% load theme_tags %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="author" content="">
|
<meta name="author" content="">
|
||||||
|
<!-- TODO Bundle all the site specific stuff up into its own template for easy override -->
|
||||||
<meta property="og:title" content="{{ SITE_NAME }}">
|
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||||
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/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.">
|
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||||
@@ -15,8 +17,9 @@
|
|||||||
|
|
||||||
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
|
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
|
||||||
|
|
||||||
{% include 'bundles/bootstrap-css.html' %}
|
{% theme_css %}
|
||||||
{% include 'bundles/fontawesome.html' %}
|
{% include 'bundles/fontawesome.html' %}
|
||||||
|
|
||||||
{% block extra_include %}
|
{% block extra_include %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -29,25 +32,23 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-transparent {
|
.card-login {
|
||||||
background: rgba(48, 48, 48, 0.7);
|
background: rgba(48 48 48 / 0.7);
|
||||||
color: #ffffff;
|
color: rgb(255 255 255);
|
||||||
padding-bottom: 21px;
|
padding-bottom: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-body {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#lang-select {
|
#lang-select {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
{% block extra_style %}
|
{% block extra_style %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container" style="margin-top:150px;">
|
<div class="container" style="margin-top:150px;">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<form action="{% url 'set_language' %}" method="post">
|
<form action="{% url 'set_language' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
|
||||||
|
<select class="form-select" onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||||
{% get_language_info_list for LANGUAGES as languages %}
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
|
|
||||||
{% for language in languages %}
|
{% for language in languages %}
|
||||||
<option lang="{{ language.code }}" value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
<option lang="{{ language.code }}" value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||||
{{ language.name_local|capfirst }} ({{ language.code }})
|
{{ language.name_local|capfirst }} ({{ language.code }})
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
|
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="panel panel-default panel-transparent">
|
<div class="card card-login border-secondary p-3">
|
||||||
<div class="panel-body">
|
<div class="card-body">
|
||||||
<div class="col-md-12">
|
<div class="text-center">
|
||||||
{% block middle_box_content %}
|
{% block middle_box_content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
@@ -20,22 +21,23 @@
|
|||||||
|
|
||||||
{% include 'public/lang_select.html' %}
|
{% include 'public/lang_select.html' %}
|
||||||
|
|
||||||
<p class="text-center" style="margin-top: 2rem;">
|
<p class="text-center mt-3">
|
||||||
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
|
{% 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">
|
<a class="text-reset" href="https://www.eveonline.com/news/view/introducing-esi" target="_blank" rel="noopener noreferrer">
|
||||||
{% translate "Introducing ESI - A New API For Eve Online" %}
|
{% translate "Introducing ESI - A New API For EVE Online" %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<a href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
|
<a class="text-reset" href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
|
||||||
{% translate "Manage ESI Applications" %}
|
{% translate "Manage ESI Applications" %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_include %}
|
{% block extra_include %}
|
||||||
{% include 'bundles/bootstrap-js.html' %}
|
{% include 'bundles/bootstrap-js-bs5.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
{% extends 'public/base.html' %}
|
{% extends 'public/base.html' %}
|
||||||
|
|
||||||
{% load bootstrap %}
|
{% load django_bootstrap5 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Registration" %}{% endblock %}
|
{% block page_title %}{% translate "Registration" %}{% endblock %}
|
||||||
|
|
||||||
{% block extra_include %}
|
{% block extra_include %}
|
||||||
{% include 'bundles/bootstrap-css.html' %}
|
{% include 'bundles/bootstrap-css-bs5.html' %}
|
||||||
{% include 'bundles/fontawesome.html' %}
|
{% include 'bundles/fontawesome.html' %}
|
||||||
{% include 'bundles/bootstrap-js.html' %}
|
{% include 'bundles/bootstrap-js-bs5.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<div class="row justify-content-center">
|
||||||
<div class="panel panel-default panel-transparent">
|
<div class="col-md-6">
|
||||||
<div class="panel-body">
|
<div class="card card-login border-secondary p-3">
|
||||||
|
<div class="card-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|bootstrap }}
|
{% bootstrap_form form %}
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Register" %}</button>
|
|
||||||
|
<button class="btn btn-primary btn-block" type="submit">{% translate "Register" %}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% include 'public/lang_select.html' %}
|
{% include 'public/lang_select.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{% extends 'public/middle_box.html' %}
|
{% extends 'public/middle_box.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block middle_box_content %}
|
{% block middle_box_content %}
|
||||||
<div class="alert alert-danger">{% translate 'Invalid or expired activation link.' %}</div>
|
<div class="alert alert-danger">{% translate 'Invalid or expired activation link.' %}</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
|
import requests_mock
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
from allianceauth.authentication.views import task_counts
|
from allianceauth.authentication.views import task_counts, esi_check
|
||||||
from allianceauth.tests.auth_utils import AuthUtils
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
from allianceauth.authentication.constants import ESI_ERROR_MESSAGE_OVERRIDES
|
||||||
|
|
||||||
MODULE_PATH = "allianceauth.authentication.views"
|
MODULE_PATH = "allianceauth.authentication.views"
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ class TestRunningTasksCount(TestCase):
|
|||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.factory = RequestFactory()
|
cls.factory = RequestFactory()
|
||||||
cls.user = AuthUtils.create_user("bruce_wayne")
|
cls.user = AuthUtils.create_user("bruce_wayne")
|
||||||
|
cls.user.is_superuser = True
|
||||||
|
cls.user.save()
|
||||||
|
|
||||||
def test_should_return_data(
|
def test_should_return_data(
|
||||||
self, mock_active_tasks_count, mock_queued_tasks_count
|
self, mock_active_tasks_count, mock_queued_tasks_count
|
||||||
@@ -35,5 +39,164 @@ class TestRunningTasksCount(TestCase):
|
|||||||
# then
|
# then
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
jsonresponse_to_dict(response), {"tasks_running": 2, "tasks_queued": 3}
|
jsonresponse_to_dict(response), {
|
||||||
|
"tasks_running": 2, "tasks_queued": 3}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_su_only(
|
||||||
|
self, mock_active_tasks_count, mock_queued_tasks_count
|
||||||
|
):
|
||||||
|
self.user.is_superuser = False
|
||||||
|
self.user.save()
|
||||||
|
self.user.refresh_from_db()
|
||||||
|
# given
|
||||||
|
mock_active_tasks_count.return_value = 2
|
||||||
|
mock_queued_tasks_count.return_value = 3
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = task_counts(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEsiCheck(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
super().setUpClass()
|
||||||
|
cls.factory = RequestFactory()
|
||||||
|
cls.user = AuthUtils.create_user("bruce_wayne")
|
||||||
|
cls.user.is_superuser = True
|
||||||
|
cls.user.save()
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_401_data_returns_200(
|
||||||
|
self, m
|
||||||
|
):
|
||||||
|
error_json = {
|
||||||
|
"error": "You have been banned from using ESI. Please contact Technical Support. (support@eveonline.com)"
|
||||||
|
}
|
||||||
|
status_code = 401
|
||||||
|
m.get(
|
||||||
|
"https://esi.evetech.net/latest/status/?datasource=tranquility",
|
||||||
|
text=json.dumps(error_json),
|
||||||
|
status_code=status_code
|
||||||
|
)
|
||||||
|
# given
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = esi_check(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertDictEqual(
|
||||||
|
jsonresponse_to_dict(response), {
|
||||||
|
"status": status_code,
|
||||||
|
"data": error_json
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_504_data_returns_200(
|
||||||
|
self, m
|
||||||
|
):
|
||||||
|
error_json = {
|
||||||
|
"error": "Gateway timeout message",
|
||||||
|
"timeout": 5000
|
||||||
|
}
|
||||||
|
status_code = 504
|
||||||
|
m.get(
|
||||||
|
"https://esi.evetech.net/latest/status/?datasource=tranquility",
|
||||||
|
text=json.dumps(error_json),
|
||||||
|
status_code=status_code
|
||||||
|
)
|
||||||
|
# given
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = esi_check(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertDictEqual(
|
||||||
|
jsonresponse_to_dict(response), {
|
||||||
|
"status": status_code,
|
||||||
|
"data": error_json
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_420_data_override(
|
||||||
|
self, m
|
||||||
|
):
|
||||||
|
error_json = {
|
||||||
|
"error": "message from CCP",
|
||||||
|
}
|
||||||
|
status_code = 420
|
||||||
|
m.get(
|
||||||
|
"https://esi.evetech.net/latest/status/?datasource=tranquility",
|
||||||
|
text=json.dumps(error_json),
|
||||||
|
status_code=status_code
|
||||||
|
)
|
||||||
|
# given
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = esi_check(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotEqual(
|
||||||
|
jsonresponse_to_dict(response)["data"],
|
||||||
|
error_json
|
||||||
|
)
|
||||||
|
self.assertDictEqual(
|
||||||
|
jsonresponse_to_dict(response), {
|
||||||
|
"status": status_code,
|
||||||
|
"data": {
|
||||||
|
"error": ESI_ERROR_MESSAGE_OVERRIDES.get(status_code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@requests_mock.Mocker()
|
||||||
|
def test_200_data_returns_200(
|
||||||
|
self, m
|
||||||
|
):
|
||||||
|
good_json = {
|
||||||
|
"players": 5,
|
||||||
|
"server_version": "69420",
|
||||||
|
"start_time": "2030-01-01T23:59:59Z"
|
||||||
|
}
|
||||||
|
status_code = 200
|
||||||
|
|
||||||
|
m.get(
|
||||||
|
"https://esi.evetech.net/latest/status/?datasource=tranquility",
|
||||||
|
text=json.dumps(good_json),
|
||||||
|
status_code=status_code
|
||||||
|
)
|
||||||
|
# given
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = esi_check(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertDictEqual(
|
||||||
|
jsonresponse_to_dict(response), {
|
||||||
|
"status": status_code,
|
||||||
|
"data": good_json
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_su_only(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
self.user.is_superuser = False
|
||||||
|
self.user.save()
|
||||||
|
self.user.refresh_from_db()
|
||||||
|
# given
|
||||||
|
request = self.factory.get("/")
|
||||||
|
request.user = self.user
|
||||||
|
# when
|
||||||
|
response = esi_check(request)
|
||||||
|
# then
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|||||||
@@ -38,5 +38,7 @@ urlpatterns = [
|
|||||||
name='token_refresh'
|
name='token_refresh'
|
||||||
),
|
),
|
||||||
path('dashboard/', views.dashboard, name='dashboard'),
|
path('dashboard/', views.dashboard, name='dashboard'),
|
||||||
|
path('dashboard_bs3/', views.dashboard_bs3, name='dashboard_bs3'),
|
||||||
path('task-counts/', views.task_counts, name='task_counts'),
|
path('task-counts/', views.task_counts, name='task_counts'),
|
||||||
|
path('esi-check/', views.esi_check, name='esi_check'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
from django_registration.backends.activation.views import (
|
from django_registration.backends.activation.views import (
|
||||||
REGISTRATION_SALT, ActivationView as BaseActivationView,
|
REGISTRATION_SALT, ActivationView as BaseActivationView,
|
||||||
RegistrationView as BaseRegistrationView,
|
RegistrationView as BaseRegistrationView,
|
||||||
@@ -9,7 +10,7 @@ from django_registration.signals import user_registered
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
@@ -22,14 +23,16 @@ from esi.decorators import token_required
|
|||||||
from esi.models import Token
|
from esi.models import Token
|
||||||
|
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.hooks import get_hooks
|
||||||
|
|
||||||
|
from .constants import ESI_ERROR_MESSAGE_OVERRIDES
|
||||||
from .core.celery_workers import active_tasks_count, queued_tasks_count
|
from .core.celery_workers import active_tasks_count, queued_tasks_count
|
||||||
from .forms import RegistrationForm
|
from .forms import RegistrationForm
|
||||||
from .models import CharacterOwnership
|
from .models import CharacterOwnership
|
||||||
|
|
||||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||||
_has_auto_groups = True
|
_has_auto_groups = True
|
||||||
from allianceauth.eveonline.autogroups.models import *
|
from allianceauth.eveonline.autogroups.models import * # noqa: F401, F403
|
||||||
else:
|
else:
|
||||||
_has_auto_groups = False
|
_has_auto_groups = False
|
||||||
|
|
||||||
@@ -42,23 +45,58 @@ def index(request):
|
|||||||
return redirect('authentication:dashboard')
|
return redirect('authentication:dashboard')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
def dashboard_groups(request):
|
||||||
def dashboard(request):
|
|
||||||
groups = request.user.groups.all()
|
groups = request.user.groups.all()
|
||||||
if _has_auto_groups:
|
if _has_auto_groups:
|
||||||
groups = groups\
|
groups = groups\
|
||||||
.filter(managedalliancegroup__isnull=True)\
|
.filter(managedalliancegroup__isnull=True)\
|
||||||
.filter(managedcorpgroup__isnull=True)
|
.filter(managedcorpgroup__isnull=True)
|
||||||
groups = groups.order_by('name')
|
groups = groups.order_by('name')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'groups': groups,
|
||||||
|
}
|
||||||
|
return render_to_string('authentication/dashboard_groups.html', context=context, request=request)
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_characters(request):
|
||||||
characters = EveCharacter.objects\
|
characters = EveCharacter.objects\
|
||||||
.filter(character_ownership__user=request.user)\
|
.filter(character_ownership__user=request.user)\
|
||||||
.select_related()\
|
.select_related()\
|
||||||
.order_by('character_name')
|
.order_by('character_name')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'groups': groups,
|
|
||||||
'characters': characters
|
'characters': characters
|
||||||
}
|
}
|
||||||
|
return render_to_string('authentication/dashboard_characters.html', context=context, request=request)
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_admin(request):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return render_to_string('allianceauth/admin-status/include.html', request=request)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_esi_check(request):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return render_to_string('allianceauth/admin-status/esi_check.html', request=request)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def dashboard(request):
|
||||||
|
_dash_items = list()
|
||||||
|
hooks = get_hooks('dashboard_hook')
|
||||||
|
items = [fn() for fn in hooks]
|
||||||
|
items.sort(key=lambda i: i.order)
|
||||||
|
for item in items:
|
||||||
|
_dash_items.append(item.render(request))
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'views': _dash_items,
|
||||||
|
}
|
||||||
return render(request, 'authentication/dashboard.html', context)
|
return render(request, 'authentication/dashboard.html', context)
|
||||||
|
|
||||||
|
|
||||||
@@ -106,23 +144,30 @@ def token_refresh(request, token_id=None):
|
|||||||
@login_required
|
@login_required
|
||||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||||
def main_character_change(request, token):
|
def main_character_change(request, token):
|
||||||
logger.debug(f"main_character_change called by user {request.user} for character {token.character_name}")
|
logger.debug(
|
||||||
|
f"main_character_change called by user {request.user} for character {token.character_name}")
|
||||||
try:
|
try:
|
||||||
co = CharacterOwnership.objects.get(character__character_id=token.character_id, user=request.user)
|
co = CharacterOwnership.objects.get(
|
||||||
|
character__character_id=token.character_id, user=request.user)
|
||||||
except CharacterOwnership.DoesNotExist:
|
except CharacterOwnership.DoesNotExist:
|
||||||
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
|
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
|
||||||
co = CharacterOwnership.objects.create_by_token(token)
|
co = CharacterOwnership.objects.create_by_token(token)
|
||||||
else:
|
else:
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
|
_('Cannot change main character to %(char)s: character owned by a different account.') % (
|
||||||
|
{'char': token.character_name})
|
||||||
)
|
)
|
||||||
co = None
|
co = None
|
||||||
if co:
|
if co:
|
||||||
request.user.profile.main_character = co.character
|
request.user.profile.main_character = co.character
|
||||||
request.user.profile.save(update_fields=['main_character'])
|
request.user.profile.save(update_fields=['main_character'])
|
||||||
messages.success(request, _('Changed main character to %(char)s') % {"char": co.character})
|
messages.success(request, _('Changed main character to %s') % co.character)
|
||||||
logger.info('Changed user %(user)s main character to %(char)s' % ({'user': request.user, 'char': co.character}))
|
logger.info(
|
||||||
|
'Changed user {user} main character to {char}'.format(
|
||||||
|
user=request.user, char=co.character
|
||||||
|
)
|
||||||
|
)
|
||||||
return redirect("authentication:dashboard")
|
return redirect("authentication:dashboard")
|
||||||
|
|
||||||
|
|
||||||
@@ -130,9 +175,11 @@ def main_character_change(request, token):
|
|||||||
def add_character(request, token):
|
def add_character(request, token):
|
||||||
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
|
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
|
||||||
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
|
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
|
||||||
messages.success(request, _('Added %(name)s to your account.' % ({'name': token.character_name})))
|
messages.success(request, _(
|
||||||
|
'Added %(name)s to your account.' % ({'name': token.character_name})))
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
|
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % (
|
||||||
|
{'name': token.character_name})))
|
||||||
return redirect('authentication:dashboard')
|
return redirect('authentication:dashboard')
|
||||||
|
|
||||||
|
|
||||||
@@ -175,8 +222,10 @@ def sso_login(request, token):
|
|||||||
token.delete()
|
token.delete()
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
_('Unable to authenticate as the selected character. '
|
_(
|
||||||
'Please log in with the main character associated with this account.')
|
'Unable to authenticate as the selected character. '
|
||||||
|
'Please log in with the main character associated with this account.'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return redirect(settings.LOGIN_URL)
|
return redirect(settings.LOGIN_URL)
|
||||||
|
|
||||||
@@ -249,7 +298,8 @@ class RegistrationView(BaseRegistrationView):
|
|||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def register(self, form):
|
def register(self, form):
|
||||||
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
user = User.objects.get(
|
||||||
|
pk=self.request.session.get('registration_uid'))
|
||||||
user.email = form.cleaned_data['email']
|
user.email = form.cleaned_data['email']
|
||||||
user_registered.send(self.__class__, user=user, request=self.request)
|
user_registered.send(self.__class__, user=user, request=self.request)
|
||||||
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||||
@@ -266,7 +316,8 @@ class RegistrationView(BaseRegistrationView):
|
|||||||
|
|
||||||
def get_email_context(self, activation_key):
|
def get_email_context(self, activation_key):
|
||||||
context = super().get_email_context(activation_key)
|
context = super().get_email_context(activation_key)
|
||||||
context['url'] = context['site'].domain + reverse('registration_activate', args=[activation_key])
|
context['url'] = context['site'].domain + \
|
||||||
|
reverse('registration_activate', args=[activation_key])
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -299,20 +350,24 @@ class ActivationView(BaseActivationView):
|
|||||||
|
|
||||||
|
|
||||||
def registration_complete(request):
|
def registration_complete(request):
|
||||||
messages.success(request, _('Sent confirmation email. Please follow the link to confirm your email address.'))
|
messages.success(request, _(
|
||||||
|
'Sent confirmation email. Please follow the link to confirm your email address.'))
|
||||||
return redirect('authentication:login')
|
return redirect('authentication:login')
|
||||||
|
|
||||||
|
|
||||||
def activation_complete(request):
|
def activation_complete(request):
|
||||||
messages.success(request, _('Confirmed your email address. Please login to continue.'))
|
messages.success(request, _(
|
||||||
|
'Confirmed your email address. Please login to continue.'))
|
||||||
return redirect('authentication:dashboard')
|
return redirect('authentication:dashboard')
|
||||||
|
|
||||||
|
|
||||||
def registration_closed(request):
|
def registration_closed(request):
|
||||||
messages.error(request, _('Registration of new accounts is not allowed at this time.'))
|
messages.error(request, _(
|
||||||
|
'Registration of new accounts is not allowed at this time.'))
|
||||||
return redirect('authentication:login')
|
return redirect('authentication:login')
|
||||||
|
|
||||||
|
|
||||||
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
def task_counts(request) -> JsonResponse:
|
def task_counts(request) -> JsonResponse:
|
||||||
"""Return task counts as JSON for an AJAX call."""
|
"""Return task counts as JSON for an AJAX call."""
|
||||||
data = {
|
data = {
|
||||||
@@ -320,3 +375,31 @@ def task_counts(request) -> JsonResponse:
|
|||||||
"tasks_queued": queued_tasks_count()
|
"tasks_queued": queued_tasks_count()
|
||||||
}
|
}
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_override_esi_error_message(response):
|
||||||
|
if response.status_code in ESI_ERROR_MESSAGE_OVERRIDES:
|
||||||
|
return {"error": ESI_ERROR_MESSAGE_OVERRIDES.get(response.status_code)}
|
||||||
|
else:
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
def esi_check(request) -> JsonResponse:
|
||||||
|
"""Return if ESI ok With error messages and codes as JSON"""
|
||||||
|
_r = requests.get("https://esi.evetech.net/latest/status/?datasource=tranquility")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"status": _r.status_code,
|
||||||
|
"data": check_for_override_esi_error_message(_r)
|
||||||
|
}
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def dashboard_bs3(request):
|
||||||
|
"""Render dashboard view with BS3 theme.
|
||||||
|
|
||||||
|
This is an internal view used for testing BS3 backward compatibility in AA4 only.
|
||||||
|
"""
|
||||||
|
return render(request, 'authentication/dashboard_bs3.html')
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ class StartProject(BaseStartProject):
|
|||||||
parser.add_argument('--python', help='The path to the python executable.')
|
parser.add_argument('--python', help='The path to the python executable.')
|
||||||
parser.add_argument('--celery', help='The path to the celery executable.')
|
parser.add_argument('--celery', help='The path to the celery executable.')
|
||||||
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
|
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
|
||||||
|
parser.add_argument('--memmon', help='The path to the memmon executable.')
|
||||||
|
|
||||||
|
|
||||||
def create_project(parser, options, args):
|
def create_project(parser, options, args):
|
||||||
# Validate args
|
# Validate args
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
parser.error("Please specify a name for your Alliance Auth installation.")
|
parser.error("Please specify a name for your Alliance Auth installation.")
|
||||||
elif len(args) > 3:
|
elif len(args) > 4:
|
||||||
parser.error("Too many arguments.")
|
parser.error("Too many arguments.")
|
||||||
|
|
||||||
# First find the path to Alliance Auth
|
# First find the path to Alliance Auth
|
||||||
@@ -32,6 +33,7 @@ def create_project(parser, options, args):
|
|||||||
'python': shutil.which('python'),
|
'python': shutil.which('python'),
|
||||||
'gunicorn': shutil.which('gunicorn'),
|
'gunicorn': shutil.which('gunicorn'),
|
||||||
'celery': shutil.which('celery'),
|
'celery': shutil.which('celery'),
|
||||||
|
'memmon': shutil.which('memmon'),
|
||||||
'extensions': ['py', 'conf', 'json'],
|
'extensions': ['py', 'conf', 'json'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
171
allianceauth/checks.py
Normal file
171
allianceauth/checks.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
from typing import List
|
||||||
|
from django import db
|
||||||
|
from django.core.checks import CheckMessage, Error, register, Warning
|
||||||
|
from allianceauth.utils.cache import get_redis_client
|
||||||
|
from django.utils import timezone
|
||||||
|
from packaging.version import InvalidVersion, Version as Pep440Version
|
||||||
|
from celery import current_app
|
||||||
|
from django.conf import settings
|
||||||
|
from sqlite3.dbapi2 import sqlite_version_info
|
||||||
|
|
||||||
|
"""
|
||||||
|
A = System Packages
|
||||||
|
B = Configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def django_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
if hasattr(settings, "SITE_URL"):
|
||||||
|
if settings.SITE_URL[-1] == "/":
|
||||||
|
errors.append(Warning("'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth.", hint="", id="allianceauth.checks.B005"))
|
||||||
|
else:
|
||||||
|
errors.append(Error("No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.", hint="", id="allianceauth.checks.B006"))
|
||||||
|
|
||||||
|
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
|
||||||
|
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
|
||||||
|
errors.append(Warning("'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.", hint="", id="allianceauth.checks.B007"))
|
||||||
|
else:
|
||||||
|
errors.append(Error("No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified", hint="", id="allianceauth.checks.B008"))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
try:
|
||||||
|
redis_version = Pep440Version(get_redis_client().info()['redis_version'])
|
||||||
|
except InvalidVersion:
|
||||||
|
errors.append(Warning("Unable to confirm Redis Version"))
|
||||||
|
return errors
|
||||||
|
|
||||||
|
if redis_version.major == 7 and redis_version.minor == 2 and timezone.now() > timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/", id="allianceauth.checks.A001"))
|
||||||
|
elif redis_version.major == 7 and redis_version.minor == 0:
|
||||||
|
errors.append(Warning(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/", id="allianceauth.checks.A002"))
|
||||||
|
elif redis_version.major == 6 and redis_version.minor == 2:
|
||||||
|
errors.append(Warning(f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/", id="allianceauth.checks.A018"))
|
||||||
|
elif redis_version.major in [6, 5]:
|
||||||
|
errors.append(Error(f"Redis {redis_version.public} EOL", hint="https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/", id="allianceauth.checks.A003"))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
|
||||||
|
for connection in db.connections.all():
|
||||||
|
if connection.vendor == "mysql":
|
||||||
|
try:
|
||||||
|
mysql_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
|
||||||
|
except InvalidVersion:
|
||||||
|
errors.append(Warning("Unable to confirm MySQL Version"))
|
||||||
|
return errors
|
||||||
|
|
||||||
|
# MySQL 8
|
||||||
|
if mysql_version.major == 8 and mysql_version.minor == 4 and timezone.now() > timezone.datetime(year=2032, month=4, day=30, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MySQL {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A004"))
|
||||||
|
elif mysql_version.major == 8 and mysql_version.minor == 3:
|
||||||
|
errors.append(Warning(f"MySQL {mysql_version.public} Non LTS", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A005"))
|
||||||
|
elif mysql_version.major == 8 and mysql_version.minor == 2:
|
||||||
|
errors.append(Warning(f"MySQL {mysql_version.public} Non LTS", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A006"))
|
||||||
|
elif mysql_version.major == 8 and mysql_version.minor == 1:
|
||||||
|
errors.append(Error(f"MySQL {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A007"))
|
||||||
|
elif mysql_version.major == 8 and mysql_version.minor == 0 and timezone.now() > timezone.datetime(year=2026, month=4, day=30, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MySQL {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A008"))
|
||||||
|
elif mysql_version.major < 8: # This will also catch Mariadb 5.x
|
||||||
|
errors.append(Error(f"MySQL or MariaDB {mysql_version.public} EOL", hint="https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/", id="allianceauth.checks.A009"))
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
|
||||||
|
for connection in db.connections.all():
|
||||||
|
if connection.vendor == "mysql": # Still to find a way to determine MySQL vs MariaDB
|
||||||
|
try:
|
||||||
|
mariadb_version = Pep440Version(".".join(str(i) for i in connection.mysql_version))
|
||||||
|
except InvalidVersion:
|
||||||
|
errors.append(Warning("Unable to confirm MariaDB Version"))
|
||||||
|
return errors
|
||||||
|
|
||||||
|
# MariaDB 11
|
||||||
|
if mariadb_version.major == 11 and mariadb_version.minor == 4 and timezone.now() > timezone.datetime(year=2029, month=5, day=19, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A010"))
|
||||||
|
elif mariadb_version.major == 11 and mariadb_version.minor == 2:
|
||||||
|
errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A018"))
|
||||||
|
if timezone.now() > timezone.datetime(year=2024, month=11, day=21, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A011"))
|
||||||
|
elif mariadb_version.major == 11 and mariadb_version.minor == 1:
|
||||||
|
errors.append(Warning(f"MariaDB {mariadb_version.public} Non LTS", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A019"))
|
||||||
|
if timezone.now() > timezone.datetime(year=2024, month=8, day=21, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A012"))
|
||||||
|
elif mariadb_version.major == 11 and mariadb_version.minor in [0, 3]: # Demote versions down here once EOL
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config.", id="allianceauth.checks.A013"))
|
||||||
|
|
||||||
|
# MariaDB 10
|
||||||
|
elif mariadb_version.major == 10 and mariadb_version.minor == 11 and timezone.now() > timezone.datetime(year=2028, month=2, day=10, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config.", id="allianceauth.checks.A014"))
|
||||||
|
elif mariadb_version.major == 10 and mariadb_version.minor == 6 and timezone.now() > timezone.datetime(year=2026, month=7, day=6, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A0015"))
|
||||||
|
elif mariadb_version.major == 10 and mariadb_version.minor == 5 and timezone.now() > timezone.datetime(year=2025, month=6, day=24, tzinfo=timezone.utc):
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A016"))
|
||||||
|
elif mariadb_version.major == 10 and mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]: # Demote versions down here once EOL
|
||||||
|
errors.append(Error(f"MariaDB {mariadb_version.public} EOL", hint="https://mariadb.org/download/?t=repo-config", id="allianceauth.checks.A017"))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
for connection in db.connections.all():
|
||||||
|
if connection.vendor == "sqlite":
|
||||||
|
try:
|
||||||
|
sqlite_version = Pep440Version(".".join(str(i) for i in sqlite_version_info))
|
||||||
|
except InvalidVersion:
|
||||||
|
errors.append(Warning("Unable to confirm SQLite Version"))
|
||||||
|
return errors
|
||||||
|
if sqlite_version.major == 3 and sqlite_version.minor < 27:
|
||||||
|
errors.append(Error(f"SQLite {sqlite_version.public} Unsupported by Django", hint="https://pkgs.org/download/sqlite3", id="allianceauth.checks.A020"))
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
for connection in db.connections.all():
|
||||||
|
if connection.vendor == "mysql":
|
||||||
|
if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
|
||||||
|
errors.append(Error("SQL Charset is not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001"))
|
||||||
|
# This hasn't actually been set on AA yet
|
||||||
|
# if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4_unicode_ci":
|
||||||
|
# errors.append(Error("SQL Collation is not set correctly", hint="", id="allianceauth.checks.B002"))
|
||||||
|
# if connection.vendor == "sqlite":
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@register()
|
||||||
|
def celery_settings(app_configs, **kwargs) -> List[CheckMessage]:
|
||||||
|
errors: List[CheckMessage] = []
|
||||||
|
if current_app.conf.broker_transport_options != {'priority_steps': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'queue_order_strategy': 'priority'}:
|
||||||
|
errors.append(Error("Celery Priorities are not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003"))
|
||||||
|
|
||||||
|
if current_app.conf.broker_connection_retry_on_startup != True:
|
||||||
|
errors.append(Error("Celery broker_connection_retry_on_startup not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004"))
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
# IDEAS
|
||||||
|
|
||||||
|
# Any other celery things weve manually changed over the years
|
||||||
|
# I'd be happy to add Community App checks, old versions the owners dont want to support etc.
|
||||||
|
|
||||||
|
|
||||||
|
# Check Default Collation on DB
|
||||||
|
# Check Charset Collation on all tables
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.menu.hooks import MenuItemHook
|
||||||
|
from allianceauth.services.hooks import UrlHook
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.corputils import urls
|
from allianceauth.corputils import urls
|
||||||
@@ -9,7 +10,7 @@ class CorpStats(MenuItemHook):
|
|||||||
MenuItemHook.__init__(
|
MenuItemHook.__init__(
|
||||||
self,
|
self,
|
||||||
_('Corporation Stats'),
|
_('Corporation Stats'),
|
||||||
'fas fa-share-alt fa-fw',
|
'fa-solid fa-share-nodes',
|
||||||
'corputils:view',
|
'corputils:view',
|
||||||
navactive=['corputils:']
|
navactive=['corputils:']
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,37 +1,63 @@
|
|||||||
{% extends 'allianceauth/base.html' %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block page_title %}{% translate "Corporation Member Data" %}{% endblock %}
|
|
||||||
{% block content %}
|
{% block page_title %}
|
||||||
<div class="col-lg-12">
|
{% translate "Corporation Member Data" %}
|
||||||
<h1 class="page-header text-center">{% translate "Corporation Member Data" %}</h1>
|
{% endblock page_title %}
|
||||||
<div class="col-lg-10 col-lg-offset-1 container">
|
|
||||||
<nav class="navbar navbar-default">
|
{% block header_nav_brand %}
|
||||||
<div class="container-fluid">
|
{% translate "Corporation Member Data" %}
|
||||||
<ul class="nav navbar-nav">
|
{% endblock header_nav_brand %}
|
||||||
<li class="dropdown">
|
|
||||||
<a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a>
|
{% block header_nav_collapse_left %}
|
||||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">
|
||||||
|
{% translate "Corporations" %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="dropdown-menu">
|
||||||
{% for corpstat in available %}
|
{% for corpstat in available %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
|
<a class="dropdown-item" href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">
|
||||||
|
{{ corpstat.corp.corporation_name }}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{% if perms.corputils.add_corpstats %}
|
{% if perms.corputils.add_corpstats %}
|
||||||
|
{% if available.count >= 1 %}
|
||||||
|
<li> </li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
|
<a class="dropdown-item" href="{% url 'corputils:add' %}">
|
||||||
|
{% translate "Add corporation" %}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header_nav_collapse_right %}
|
||||||
|
<li class="nav-item">
|
||||||
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
|
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate "Search all corporations..." %}{% endif %}">
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="search_string"
|
||||||
|
placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate 'Search all corporations...' %}{% endif %}"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</li>
|
||||||
</nav>
|
|
||||||
{% block member_data %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
{% block member_data %}
|
||||||
|
{% endblock member_data %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|||||||
@@ -1,93 +1,143 @@
|
|||||||
{% extends 'corputils/base.html' %}
|
{% extends 'corputils/base.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
|
||||||
{% block member_data %}
|
{% block member_data %}
|
||||||
{% if corpstats %}
|
{% if corpstats %}
|
||||||
<div class="row">
|
<div>
|
||||||
<div class="col-lg-12 text-center">
|
<table class="table text-center">
|
||||||
<table class="table">
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
|
<td>
|
||||||
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
|
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{% if corpstats.corp.alliance %}
|
{% if corpstats.corp.alliance %}
|
||||||
<td class="text-center col-lg-6">
|
<td>
|
||||||
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
|
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
|
<td><p class="h4">{{ corpstats.corp.corporation_name }}</p></td>
|
||||||
|
|
||||||
{% if corpstats.corp.alliance %}
|
{% if corpstats.corp.alliance %}
|
||||||
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
|
<td><p class="h4">{{ corpstats.corp.alliance.alliance_name }}</p></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="card card-default mt-4">
|
||||||
<div class="col-lg-12">
|
<div class="card-header clearfix" role="tablist">
|
||||||
<div class="panel panel-default">
|
<ul class="nav nav-pills float-start">
|
||||||
<div class="panel-heading">
|
<li class="nav-item" role="presentation">
|
||||||
<ul class="nav nav-pills pull-left">
|
<a
|
||||||
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li>
|
class="nav-link active"
|
||||||
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li>
|
id="mains"
|
||||||
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-mains"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="tab-mains"
|
||||||
|
aria-selected="true"
|
||||||
|
>
|
||||||
|
{% translate 'Mains' %} ({{ total_mains }})
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
id="members"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-members"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="tab-members"
|
||||||
|
aria-selected="false"
|
||||||
|
>
|
||||||
|
{% translate 'Members' %} ({{ corpstats.member_count }})
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
id="unregistered"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-unregistered"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="tab-unregistered"
|
||||||
|
aria-selected="false"
|
||||||
|
>
|
||||||
|
{% translate 'Unregistered' %} ({{ unregistered.count }})
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="pull-right hidden-xs">
|
|
||||||
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}
|
<div class="float-end d-none d-sm-block">
|
||||||
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
|
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}
|
||||||
<span class="glyphicon glyphicon-refresh"></span>
|
|
||||||
|
<a
|
||||||
|
class="btn btn-success btn-sm ms-2"
|
||||||
|
type="button"
|
||||||
|
href="{% url 'corputils:update' corpstats.corp.corporation_id %}"
|
||||||
|
title="{% translate 'Update Now' %}"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-rotate"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
|
||||||
|
<div class="card-body">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane fade in active" id="mains">
|
<div class="tab-pane fade show active" id="tab-mains" role="tabpanel" aria-labelledby="tab-mains">
|
||||||
{% if mains %}
|
{% if mains %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover" id="table-mains">
|
<table class="table table-hover" id="table-mains">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
|
<th>{% translate "Main character" %}</th>
|
||||||
<th></th>
|
<th>{% translate "Registered characters" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for id, main in mains.items %}
|
{% for id, main in mains.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center" style="vertical-align:middle">
|
<td class="text-center" style="vertical-align: middle;">
|
||||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||||
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
|
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
|
||||||
<div class="caption text-center">
|
<div class="caption">
|
||||||
{{ main.main }}
|
{{ main.main }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
{% for alt in main.alts %}
|
{% for alt in main.alts|dictsort:"character_name" %}
|
||||||
{% if forloop.first %}
|
{% if forloop.first %}
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th class="text-center">{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
<th class="text-center">{% translate "Corporation" %}</th>
|
<th>{% translate "Corporation" %}</th>
|
||||||
<th class="text-center">{% translate "Alliance" %}</th>
|
<th>{% translate "Alliance" %}</th>
|
||||||
<th class="text-center"></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center" style="width:5%">
|
<td style="width: 5%;">
|
||||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||||
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
|
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
<td style="width: 30%;">{{ alt.character_name }}</td>
|
||||||
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
|
<td style="width: 30%;">{{ alt.corporation_name }}</td>
|
||||||
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
|
<td style="width: 30%;">{{ alt.alliance_name|default_if_none:"" }}</td>
|
||||||
<td class="text-center" style="width:5%">
|
<td style="width: 5%;">
|
||||||
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
|
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
|
||||||
{% translate "Killboard" %}
|
{% translate "Killboard" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -102,43 +152,46 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="members">
|
|
||||||
|
<div class="tab-pane fade" id="tab-members" role="tabpanel" aria-labelledby="tab-members">
|
||||||
{% if members %}
|
{% if members %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover" id="table-members">
|
<table class="table table-hover" id="table-members">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th class="text-center">{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
<th class="text-center"></th>
|
<th></th>
|
||||||
<th class="text-center">{% translate "Main Character" %}</th>
|
<th>{% translate "Main Character" %}</th>
|
||||||
<th class="text-center">{% translate "Main Corporation" %}</th>
|
<th>{% translate "Main Corporation" %}</th>
|
||||||
<th class="text-center">{% translate "Main Alliance" %}</th>
|
<th>{% translate "Main Alliance" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for member in members %}
|
{% for member in members %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
|
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
|
||||||
<td class="text-center">{{ member }}</td>
|
<td>{{ member }}</td>
|
||||||
<td class="text-center">
|
<td>
|
||||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
|
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
<td>{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
||||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
<td>{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
||||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
|
<td>{{ member.character_ownership.user.profile.main_character.alliance_name|default_if_none:"" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for member in unregistered %}
|
{% for member in unregistered %}
|
||||||
<tr class="danger">
|
<tr class="table-danger">
|
||||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||||
<td class="text-center">{{ member.character_name }}</td>
|
<td>{{ member.character_name }}</td>
|
||||||
<td class="text-center">
|
<td>
|
||||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
|
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center"></td>
|
<td></td>
|
||||||
<td class="text-center"></td>
|
<td></td>
|
||||||
<td class="text-center"></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -146,24 +199,26 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="unregistered">
|
|
||||||
|
<div class="tab-pane fade" id="tab-unregistered" role="tabpanel" aria-labelledby="tab-unregistered">
|
||||||
{% if unregistered %}
|
{% if unregistered %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover" id="table-unregistered">
|
<table class="table table-hover" id="table-unregistered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th class="text-center">{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
<th class="text-center"></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for member in unregistered %}
|
{% for member in unregistered %}
|
||||||
<tr class="danger">
|
<tr class="table-danger">
|
||||||
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
|
||||||
<td class="text-center">{{ member.character_name }}</td>
|
<td>{{ member.character_name }}</td>
|
||||||
<td class="text-center">
|
<td>
|
||||||
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
|
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">
|
||||||
{% translate "Killboard" %}
|
{% translate "Killboard" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -177,18 +232,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js-bs5.html' %}
|
||||||
{% endblock %}
|
|
||||||
{% block extra_css %}
|
<script>
|
||||||
{% include 'bundles/datatables-css.html' %}
|
$(document).ready(() => {
|
||||||
{% endblock %}
|
|
||||||
{% block extra_script %}
|
|
||||||
$(document).ready(function(){
|
|
||||||
$('#table-mains').DataTable({
|
$('#table-mains').DataTable({
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{ "sortable": false, "targets": [1] },
|
{ "sortable": false, "targets": [1] },
|
||||||
@@ -196,6 +247,7 @@
|
|||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"stateDuration": 0
|
"stateDuration": 0
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#table-members').DataTable({
|
$('#table-members').DataTable({
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{ "searchable": false, "targets": [0, 2] },
|
{ "searchable": false, "targets": [0, 2] },
|
||||||
@@ -205,6 +257,7 @@
|
|||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"stateDuration": 0
|
"stateDuration": 0
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#table-unregistered').DataTable({
|
$('#table-unregistered').DataTable({
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{ "searchable": false, "targets": [0, 2] },
|
{ "searchable": false, "targets": [0, 2] },
|
||||||
@@ -214,6 +267,10 @@
|
|||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"stateDuration": 0
|
"stateDuration": 0
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include 'bundles/datatables-css-bs5.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,33 +1,36 @@
|
|||||||
{% extends "corputils/base.html" %}
|
{% extends "corputils/base.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block member_data %}
|
{% block member_data %}
|
||||||
<div class="panel panel-default">
|
<div class="card card-default">
|
||||||
<div class="panel-heading clearfix">
|
<div class="card-header clearfix">
|
||||||
<div class="panel-title pull-left">{% translate "Search Results" %}</div>
|
<div class="card-title">{% translate "Search Results" %}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
|
||||||
|
<div class="card-body mt-2">
|
||||||
<table class="table table-hover" id="table-search">
|
<table class="table table-hover" id="table-search">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center"></th>
|
<th></th>
|
||||||
<th class="text-center">{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
<th class="text-center">{% translate "Corporation" %}</th>
|
<th>{% translate "Corporation" %}</th>
|
||||||
<th class="text-center">{% translate "zKillboard" %}</th>
|
<th>{% translate "zKillboard" %}</th>
|
||||||
<th class="text-center">{% translate "Main Character" %}</th>
|
<th>{% translate "Main Character" %}</th>
|
||||||
<th class="text-center">{% translate "Main Corporation" %}</th>
|
<th>{% translate "Main Corporation" %}</th>
|
||||||
<th class="text-center">{% translate "Main Alliance" %}</th>
|
<th>{% translate "Main Alliance" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for result in results %}
|
{% for result in results %}
|
||||||
<tr {% if not result.1.registered %}class="danger"{% endif %}>
|
<tr {% if not result.1.registered %}class="danger"{% endif %}>
|
||||||
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
|
<td><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>{{ result.1.character_name }}</td>
|
||||||
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
|
<td >{{ 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>
|
<td><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
|
||||||
<td class="text-center">{{ result.1.main_character.character_name }}</td>
|
<td>{{ result.1.main_character.character_name }}</td>
|
||||||
<td class="text-center">{{ result.1.main_character.corporation_name }}</td>
|
<td>{{ result.1.main_character.corporation_name }}</td>
|
||||||
<td class="text-center">{{ result.1.main_character.alliance_name }}</td>
|
<td>{{ result.1.main_character.alliance_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -35,17 +38,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js-bs5.html' %}
|
||||||
{% endblock %}
|
|
||||||
{% block extra_css %}
|
<script>
|
||||||
{% include 'bundles/datatables-css.html' %}
|
$(document).ready(() => {
|
||||||
{% endblock %}
|
|
||||||
{% block extra_script %}
|
|
||||||
$(document).ready(function(){
|
|
||||||
$('#table-search').DataTable({
|
$('#table-search').DataTable({
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"stateDuration": 0
|
"stateDuration": 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include 'bundles/datatables-css-bs5.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class AutogroupsConfigTestCase(TestCase):
|
|||||||
group_qs = Group.objects.filter(pk=group.pk)
|
group_qs = Group.objects.filter(pk=group.pk)
|
||||||
|
|
||||||
self.assertIn(group, self.member.groups.all())
|
self.assertIn(group, self.member.groups.all())
|
||||||
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
|
self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
|
||||||
|
|
||||||
def test_update_alliance_group_membership_no_main_character(self):
|
def test_update_alliance_group_membership_no_main_character(self):
|
||||||
obj = AutogroupsConfig.objects.create()
|
obj = AutogroupsConfig.objects.create()
|
||||||
@@ -172,7 +172,7 @@ class AutogroupsConfigTestCase(TestCase):
|
|||||||
group_qs = Group.objects.filter(pk=group.pk)
|
group_qs = Group.objects.filter(pk=group.pk)
|
||||||
|
|
||||||
self.assertIn(group, self.member.groups.all())
|
self.assertIn(group, self.member.groups.all())
|
||||||
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
|
self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
|
||||||
|
|
||||||
def test_update_corp_group_membership_no_state(self):
|
def test_update_corp_group_membership_no_state(self):
|
||||||
obj = AutogroupsConfig.objects.create(corp_groups=True)
|
obj = AutogroupsConfig.objects.create(corp_groups=True)
|
||||||
|
|||||||
@@ -8,21 +8,19 @@ Needs to be called with a context containing three objects:
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
{% extends 'allianceauth/base.html' %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
{% load evelinks %}
|
{% load evelinks %}
|
||||||
|
|
||||||
{% block page_title %}Evelinks examples{% endblock %}
|
{% block page_title %}Evelinks Examples{% endblock page_title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
{% include "framework/header/page-header.html" with title="Evelinks templatetags examples" %}
|
||||||
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<h1 class="page-header text-center">Evelinks templatetags examples</h1>
|
|
||||||
<div class="col-lg-12 container">
|
<div class="col-lg-12 container">
|
||||||
|
|
||||||
<h2>profile URLs</h2>
|
<h2>profile URLs</h2>
|
||||||
|
|
||||||
<div class="rows">
|
<div class="rows">
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<h3>evewho</h3>
|
<h3>evewho</h3>
|
||||||
<p><a href="{{ my_character|evewho_character_url }}">character from character object</a></p>
|
<p><a href="{{ my_character|evewho_character_url }}">character from character object</a></p>
|
||||||
@@ -53,12 +51,10 @@ Needs to be called with a context containing three objects:
|
|||||||
<p><a href="{{ 30002813|zkillboard_solar_system_url }}">solar sytem from ID</a></p>
|
<p><a href="{{ 30002813|zkillboard_solar_system_url }}">solar sytem from ID</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>image URLs</h2>
|
<h2>image URLs</h2>
|
||||||
|
|
||||||
<div class="rows">
|
<div class="rows">
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128 }}"></p>
|
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128 }}"></p>
|
||||||
<p>character from character object: <img src="{{ my_character|character_portrait_url:128 }}"></p>
|
<p>character from character object: <img src="{{ my_character|character_portrait_url:128 }}"></p>
|
||||||
@@ -75,10 +71,7 @@ Needs to be called with a context containing three objects:
|
|||||||
<p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128 }}"></p>
|
<p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128 }}"></p>
|
||||||
<p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128 }}"></p>
|
<p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128 }}"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
from allianceauth.menu.hooks import MenuItemHook
|
||||||
from . import urls
|
from . import urls
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.services.hooks import UrlHook
|
||||||
|
|
||||||
|
|
||||||
@hooks.register('menu_item_hook')
|
@hooks.register('menu_item_hook')
|
||||||
def register_menu():
|
def register_menu():
|
||||||
return MenuItemHook(_('Fleet Activity Tracking'), 'fas fa-users fa-fw', 'fatlink:view',
|
return MenuItemHook(_('Fleet Activity Tracking'), 'fa-solid fa-users', 'fatlink:view',
|
||||||
navactive=['fatlink:'])
|
navactive=['fatlink:'])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
class FatlinkForm(forms.Form):
|
class FatlinkForm(forms.Form):
|
||||||
fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
|
fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
|
||||||
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1, max_value=2147483647, help_text=_('minutes'))
|
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1, max_value=2147483647, help_text=_('Duration of the fat-link in minutes'))
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from django.utils.timezone import utc
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -15,6 +14,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='fatlink',
|
model_name='fatlink',
|
||||||
name='fatdatetime',
|
name='fatdatetime',
|
||||||
field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=utc)),
|
field=models.DateTimeField(default=datetime.datetime(2016, 9, 5, 22, 20, 2, 999041, tzinfo=datetime.timezone.utc)),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Migration to AA Framework API method
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import allianceauth.framework.api.user
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("fleetactivitytracking", "0006_auto_20180803_0430"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="fatlink",
|
||||||
|
name="creator",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=models.SET(allianceauth.framework.api.user.get_sentinel_user),
|
||||||
|
to=settings.AUTH_USER_MODEL
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -3,10 +3,7 @@ from django.db import models
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from allianceauth.eveonline.models import EveCharacter
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.framework.api.user import get_sentinel_user
|
||||||
|
|
||||||
def get_sentinel_user():
|
|
||||||
return User.objects.get_or_create(username='deleted')[0]
|
|
||||||
|
|
||||||
|
|
||||||
class Fatlink(models.Model):
|
class Fatlink(models.Model):
|
||||||
|
|||||||
@@ -1,22 +1,43 @@
|
|||||||
{% extends 'allianceauth/base.html' %}
|
{% extends 'allianceauth/base-bs5.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block page_title %}{% translate "Fleet Participation" %}{% endblock %}
|
|
||||||
|
{% block page_title %}
|
||||||
|
{% translate "Fleet Participation" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1>
|
{% translate "Character not found!" as page_header %}
|
||||||
<div class="col-lg-12 container" id="example">
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
|
<div class="col-lg-12 container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="panel panel-default">
|
<div class="card card-default">
|
||||||
<div class="panel-heading">{{ character_name }}</div>
|
<div class="card-header">
|
||||||
<div class="panel-body">
|
<div class="card-title mb-0">
|
||||||
|
{{ character_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<div class="col-lg-2 col-sm-2">
|
<div class="col-lg-2 col-sm-2">
|
||||||
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
|
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-10 col-sm-2">
|
<div class="col-lg-10 col-sm-2">
|
||||||
<div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div>
|
<div class="alert alert-danger" role="alert">
|
||||||
{% translate "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% translate "Add it here" %}</a> {% translate "before attempting to click fleet attendance links." %}
|
{% translate "Character not registered!" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% translate "This character is not associated with an auth account." %}
|
||||||
|
<a href="{% url 'authentication:add_character' %}">{% translate "Add it here" %}</a>
|
||||||
|
{% translate "before attempting to click fleet attendance links." %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
{% translate "Create Fatlink" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
{% translate "Create Fatlink" as page_header %}
|
||||||
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if badrequest %}
|
||||||
|
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for message in errormessages %}
|
||||||
|
<div class="alert alert-danger" role="alert">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="card card-primary border-0">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title mb-0">
|
||||||
|
{% translate "Fatlink details" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<form role="form" action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
|
||||||
|
<div class="form-group mt-3 clearfix">
|
||||||
|
{% translate "Create fatlink" as button_text %}
|
||||||
|
{% bootstrap_button button_class="btn btn-primary" content=button_text name="submit_fat" id="submit_fat" %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Create Fatlink" %}{% endblock page_title %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<h1 class="page-header text-center">{% translate "Create Fleet Operation" %}</h1>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
|
||||||
{% if badrequest %}
|
|
||||||
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% for message in errormessages %}
|
|
||||||
<div class="alert alert-danger" role="alert">{{ message }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
<div class="col-md-4 col-md-offset-4">
|
|
||||||
<div class="row">
|
|
||||||
<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" name="submit_fat">{% translate "Create fatlink" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
@@ -1,21 +1,35 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
|
|
||||||
|
{% block page_title %}
|
||||||
|
{% translate "Fatlink view" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "Edit fatlink" %} "{{ fatlink }}"
|
<h1 class="page-header text-center mb-3">
|
||||||
<div class="text-right">
|
{% translate "Edit fatlink" %} "{{ fatlink }}"
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
<form>
|
<form>
|
||||||
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
|
<button type="submit" onclick="return confirm('{% translate "Are you sure?" %}')" class="btn btn-danger" name="deletefat" value="True">
|
||||||
{% translate "Delete fat" %}
|
{% translate "Delete fat" %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
<div class="panel panel-default">
|
<div class="card card-default">
|
||||||
<div class="panel-heading">{% translate "Registered characters" %}</div>
|
<div class="card-header">
|
||||||
<div class="panel-body">
|
<div class="card-title mb-0">{% translate "Registered characters" %}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<table class="table table-responsive table-hover">
|
<table class="table table-responsive table-hover">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "User" %}</th>
|
<th class="text-center">{% translate "User" %}</th>
|
||||||
@@ -25,21 +39,23 @@
|
|||||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
<th class="text-center">{% translate "Eve Time" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for fat in registered_fats %}
|
{% for fat in registered_fats %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ fat.user }}</td>
|
<td class="text-center">{{ fat.user }}</td>
|
||||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||||
|
<td class="text-center">
|
||||||
{% if fat.station != "No Station" %}
|
{% if fat.station != "No Station" %}
|
||||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
{% translate "Docked in" %}
|
||||||
{% else %}
|
|
||||||
<td class="text-center">{{ fat.system }}</td>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{{ fat.system }}
|
||||||
|
</td>
|
||||||
<td class="text-center">{{ fat.shiptype }}</td>
|
<td class="text-center">{{ fat.shiptype }}</td>
|
||||||
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
|
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<form>
|
<form>
|
||||||
<button type="submit" class="btn btn-warning" name="removechar" value="{{ fat.character.character_id }}">
|
<button type="submit" class="btn btn-warning" name="removechar" value="{{ fat.character.character_id }}">
|
||||||
<span class="glyphicon glyphicon-remove"></span>
|
<i class="fa-solid fa-trash-can fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,30 +1,50 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Personal fatlink statistics" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
<h1 class="page-header text-center mb-3">
|
||||||
|
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||||
|
</h1>
|
||||||
|
|
||||||
{% if char_id %}
|
{% if char_id %}
|
||||||
<div class="text-right">
|
<div class="text-end mb-3">
|
||||||
<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>
|
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">
|
||||||
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% translate "Next month" %}</a>
|
{% translate "Previous month" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">
|
||||||
|
{% translate "Next month" %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
|
||||||
<h2>
|
<div class="card card-default mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title mb-0">
|
||||||
{% blocktranslate count links=n_fats trimmed %}
|
{% blocktranslate count links=n_fats trimmed %}
|
||||||
{{ user }} has collected one link this month.
|
{{ user }} has collected one link this month.
|
||||||
{% plural %}
|
{% plural %}
|
||||||
{{ user }} has collected {{ links }} links this month.
|
{{ user }} has collected {{ links }} links this month.
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
</h2>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<table class="table table-responsive">
|
<table class="table table-responsive">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-2 text-center">{% translate "Ship" %}</th>
|
<th class="col-md-2 text-center">{% translate "Ship" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Times used" %}</th>
|
<th class="col-md-2 text-center">{% translate "Times used" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for ship, n_fats in shipStats %}
|
{% for ship, n_fats in shipStats %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ ship }}</td>
|
<td class="text-center">{{ ship }}</td>
|
||||||
@@ -32,15 +52,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if created_fats %}
|
{% if created_fats %}
|
||||||
<h2>
|
<div class="card card-default">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title mb-0">
|
||||||
{% blocktranslate count links=n_created_fats trimmed %}
|
{% blocktranslate count links=n_created_fats trimmed %}
|
||||||
{{ user }} has created one link this month.
|
{{ user }} has created one link this month.
|
||||||
{% plural %}
|
{% plural %}
|
||||||
{{ user }} has created {{ links }} links this month.
|
{{ user }} has created {{ links }} links this month.
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
</h2>
|
</div>
|
||||||
{% if created_fats %}
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "Fleet" %}</th>
|
<th class="text-center">{% translate "Fleet" %}</th>
|
||||||
@@ -49,23 +76,29 @@
|
|||||||
<th class="text-center">{% translate "Duration" %}</th>
|
<th class="text-center">{% translate "Duration" %}</th>
|
||||||
<th class="text-center">{% translate "Edit" %}</th>
|
<th class="text-center">{% translate "Edit" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for link in created_fats %}
|
{% for link in created_fats %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
<td class="text-center">
|
||||||
|
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">
|
||||||
|
{{ link.fleet }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td class="text-center">{{ link.creator.username }}</td>
|
<td class="text-center">{{ link.creator.username }}</td>
|
||||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||||
<td class="text-center">{{ link.duration }}</td>
|
<td class="text-center">{{ link.duration }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'fatlink:modify' link.hash %}">
|
<a href="{% url 'fatlink:modify' link.hash %}">
|
||||||
<button type="button" class="btn btn-info"><span
|
<button type="button" class="btn btn-info">
|
||||||
class="glyphicon glyphicon-edit"></span></button>
|
<i class="fa-solid fa-pen-to-square fa-fw"></i>
|
||||||
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -1,30 +1,40 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Personal fatlink statistics" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
|
<h1 class="page-header text-center mb-3">
|
||||||
<div class="text-right">
|
{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
|
||||||
<a href="{% url 'fatlink:personal_statistics_year' previous_year %}" class="btn btn-info">{% translate "Previous year" %}</a>
|
</h1>
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
|
<a href="{% url "fatlink:personal_statistics_year" previous_year %}" class="btn btn-info"><i class="fa-solid fa-chevron-left"></i> {% translate "Previous year" %}</a>
|
||||||
|
|
||||||
{% if next_year %}
|
{% if next_year %}
|
||||||
<a href="{% url 'fatlink:personal_statistics_year' next_year %}" class="btn btn-info">{% translate "Next year" %}</a>
|
<a href="{% url "fatlink:personal_statistics_year" next_year %}" class="btn btn-info">{% translate "Next year" %} <i class="fa-solid fa-chevron-right"></i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
<div class="col-lg-2 col-lg-offset-5">
|
<div class="col-lg-2 offset-lg-5">
|
||||||
<table class="table table-responsive">
|
<table class="table table-responsive">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-2 text-center">{% translate "Month" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Month" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Fats" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for monthnr, month, n_fats in monthlystats %}
|
{% for monthnr, month, n_fats in monthlystats %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'fatlink:personal_statistics_month' year monthnr %}">
|
<a href="{% url 'fatlink:personal_statistics_month' year monthnr %}">{{ month }}</a>
|
||||||
{{ month }}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ n_fats }}</td>
|
<td class="text-center">{{ n_fats }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,27 +1,40 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Fatlink Corp Statistics" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Fatlink Corp Statistics" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
<h1 class="page-header text-center mb-3">
|
||||||
<div class="text-right">
|
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||||
<a href="{% url 'fatlink:statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
</h1>
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
|
<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 %}
|
{% if next_month %}
|
||||||
<a href="{% url 'fatlink:statistics_corp_month' corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
<a href="{% url "fatlink:statistics_corp_month" corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
{% if fatStats %}
|
{% if fatStats %}
|
||||||
<table class="table table-responsive">
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-1"></th>
|
<th scope="col" class="col-md-1"></th>
|
||||||
<th class="col-md-2 text-center">{% translate "Main Character" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Main Character" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Characters" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Characters" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Fats" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Average fats" %}
|
<th scope="col" class="col-md-2 text-center">
|
||||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
|
{% translate "Average fats" %}
|
||||||
|
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for memberStat in fatStats %}
|
{% for memberStat in fatStats %}
|
||||||
@@ -36,11 +49,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block extra_script %}
|
|
||||||
$(document).ready(function () {
|
{% block extra_javascript %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(() => {
|
||||||
$("[rel=tooltip]").tooltip();
|
$("[rel=tooltip]").tooltip();
|
||||||
});
|
});
|
||||||
{% endblock extra_script %}
|
</script>
|
||||||
|
{% endblock extra_javascript %}
|
||||||
|
|||||||
@@ -1,28 +1,41 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Fatlink statistics" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Fatlink Statistics" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
<h1 class="page-header text-center mb-3">
|
||||||
<div class="text-right">
|
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||||
<a href="{% url 'fatlink:statistics_month' previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
</h1>
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
|
<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 %}
|
{% if next_month %}
|
||||||
<a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
<a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
{% if fatStats %}
|
{% if fatStats %}
|
||||||
<table class="table table-responsive">
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-1"></th>
|
<th scope="col" class="col-md-1"></th>
|
||||||
<th class="col-md-2 text-center">{% translate "Ticker" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Ticker" %}</th>
|
||||||
<th class="col-md-5 text-center">{% translate "Corp" %}</th>
|
<th scope="col" class="col-md-5 text-center">{% translate "Corp" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Members" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Members" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Fats" %}</th>
|
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
|
||||||
<th class="col-md-2 text-center">{% translate "Average fats" %}
|
<th scope="col" class="col-md-2 text-center">
|
||||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
|
{% translate "Average fats" %}
|
||||||
|
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for corpStat in fatStats %}
|
{% for corpStat in fatStats %}
|
||||||
@@ -30,7 +43,9 @@
|
|||||||
<td>
|
<td>
|
||||||
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
|
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
|
||||||
</td>
|
</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">
|
||||||
|
<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.corporation_name }}</td>
|
||||||
<td class="text-center">{{ corpStat.corp.member_count }}</td>
|
<td class="text-center">{{ corpStat.corp.member_count }}</td>
|
||||||
<td class="text-center">{{ corpStat.n_fats }}</td>
|
<td class="text-center">{{ corpStat.n_fats }}</td>
|
||||||
@@ -38,11 +53,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
{% block extra_script %}
|
|
||||||
$(document).ready(function () {
|
{% block extra_javascript %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(() => {
|
||||||
$("[rel=tooltip]").tooltip();
|
$("[rel=tooltip]").tooltip();
|
||||||
});
|
});
|
||||||
{% endblock extra_script %}
|
</script>
|
||||||
|
{% endblock extra_javascript %}
|
||||||
|
|||||||
@@ -1,39 +1,55 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Fatlink view" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Fleet Activity Tracking" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "Participation data" %}</h1>
|
{% translate "Participation data" as page_header %}
|
||||||
<table class="table">
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-11">
|
<th class="col-md-10">
|
||||||
<h4><b>{% translate "Most recent clicked fatlinks" %}</b>
|
<h4>
|
||||||
|
<b>{% translate "Most recent clicked fatlinks" %}</b>
|
||||||
</h4>
|
</h4>
|
||||||
</th>
|
</th>
|
||||||
<th class="col-md-1">
|
<th class="col-md-2 align-self-end">
|
||||||
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info">
|
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info">
|
||||||
|
<i class="fa-solid fa-circle-info fa-fw"></i>
|
||||||
{% translate "Personal statistics" %}
|
{% translate "Personal statistics" %}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if fats %}
|
{% if fats %}
|
||||||
<table class="table table-responsive">
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "Fleet" %}</th>
|
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
|
||||||
<th class="text-center">{% translate "Character" %}</th>
|
<th scope="col" class="text-center">{% translate "Character" %}</th>
|
||||||
<th class="text-center">{% translate "System" %}</th>
|
<th scope="col" class="text-center">{% translate "System" %}</th>
|
||||||
<th class="text-center">{% translate "Ship" %}</th>
|
<th scope="col" class="text-center">{% translate "Ship" %}</th>
|
||||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for fat in fats %}
|
{% for fat in fats %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||||
{% if fat.station != "No Station" %}
|
{% if fat.station != "No Station" %}
|
||||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
<td class="text-center">{% translate "Docked in" %} {{ fat.system }}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="text-center">{{ fat.system }}</td>
|
<td class="text-center">{{ fat.system }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -42,57 +58,66 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
|
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.auth.fleetactivitytracking %}
|
{% if perms.auth.fleetactivitytracking %}
|
||||||
<table class="table">
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-md-10">
|
<th class="col-md-8">
|
||||||
<h4><b>{% translate "Most recent fatlinks" %}</b>
|
<h4>
|
||||||
|
<b>{% translate "Most recent fatlinks" %}</b>
|
||||||
</h4>
|
</h4>
|
||||||
</th>
|
</th>
|
||||||
<th class="col-md-1">
|
<th class="col-md-2 align-self-end">
|
||||||
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info">
|
<a href="{% url 'fatlink:statistics' %}" class="btn btn-info"><i class="fa-solid fa-eye fa-fw"></i> {% translate "View statistics" %}</a>
|
||||||
{% translate "View statistics" %}
|
|
||||||
</a>
|
|
||||||
</th>
|
</th>
|
||||||
<th class="col-md-1">
|
<th class="col-md-2 align-self-end">
|
||||||
<a href="{% url 'fatlink:create' %}" class="btn btn-success">
|
<a href="{% url 'fatlink:create' %}" class="btn btn-success"><i class="fa-solid fa-plus fa-fw"></i> {% translate "Create fatlink" %}</a>
|
||||||
{% translate "Create fatlink" %}
|
|
||||||
</a>
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if fatlinks %}
|
{% if fatlinks %}
|
||||||
<table class="table">
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "Name" %}</th>
|
<th scope="col" class="text-center">{% translate "Name" %}</th>
|
||||||
<th class="text-center">{% translate "Creator" %}</th>
|
<th scope="col" class="text-center">{% translate "Creator" %}</th>
|
||||||
<th class="text-center">{% translate "Fleet" %}</th>
|
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
|
||||||
<th class="text-center">{% translate "Eve Time" %}</th>
|
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
|
||||||
<th class="text-center">{% translate "Duration" %}</th>
|
<th scope="col" class="text-center">{% translate "Duration" %}</th>
|
||||||
<th class="text-center">{% translate "Edit" %}</th>
|
<th scope="col" class="text-center">{% translate "Edit" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for link in fatlinks %}
|
{% for link in fatlinks %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="label label-primary">{{ link.fleet }}</a></td>
|
<td class="text-center">
|
||||||
|
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a>
|
||||||
|
</td>
|
||||||
<td class="text-center">{{ link.creator.username }}</td>
|
<td class="text-center">{{ link.creator.username }}</td>
|
||||||
<td class="text-center">{{ link.fleet }}</td>
|
<td class="text-center">{{ link.fleet }}</td>
|
||||||
<td class="text-center">{{ link.fatdatetime }}</td>
|
<td class="text-center">{{ link.fatdatetime }}</td>
|
||||||
<td class="text-center">{{ link.duration }}</td>
|
<td class="text-center">
|
||||||
|
{{ link.duration }}
|
||||||
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
|
<a href="{% url 'fatlink:modify' link.hash %}" class="btn btn-info">
|
||||||
<span class="glyphicon glyphicon-edit"></span>
|
<i class="fa-solid fa-pen-to-square fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning text-center">{% translate "No created fatlinks on record." %}</div>
|
<div class="alert alert-warning text-center">
|
||||||
|
{% translate "No created fatlinks on record." %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -352,11 +352,11 @@ def create_fatlink_view(request):
|
|||||||
for errorname, message in e.message_dict.items():
|
for errorname, message in e.message_dict.items():
|
||||||
messages.append(message[0].decode())
|
messages.append(message[0].decode())
|
||||||
context = {'form': form, 'errormessages': messages}
|
context = {'form': form, 'errormessages': messages}
|
||||||
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context)
|
return render(request, 'fleetactivitytracking/fatlinkcreate.html', context=context)
|
||||||
else:
|
else:
|
||||||
form = FatlinkForm()
|
form = FatlinkForm()
|
||||||
context = {'form': form, 'badrequest': True}
|
context = {'form': form, 'badrequest': True}
|
||||||
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context)
|
return render(request, 'fleetactivitytracking/fatlinkcreate.html', context=context)
|
||||||
return redirect('fatlink:view')
|
return redirect('fatlink:view')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -365,7 +365,7 @@ def create_fatlink_view(request):
|
|||||||
|
|
||||||
context = {'form': form}
|
context = {'form': form}
|
||||||
|
|
||||||
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context)
|
return render(request, 'fleetactivitytracking/fatlinkcreate.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
3
allianceauth/framework/__init__.py
Normal file
3
allianceauth/framework/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Alliance Auth Framework
|
||||||
|
"""
|
||||||
57
allianceauth/framework/api/evecharacter.py
Normal file
57
allianceauth/framework/api/evecharacter.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"""
|
||||||
|
Alliance Auth Evecharacter API
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from allianceauth.authentication.models import CharacterOwnership
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
from allianceauth.framework.api.user import get_sentinel_user
|
||||||
|
|
||||||
|
|
||||||
|
def get_main_character_from_evecharacter(
|
||||||
|
character: EveCharacter,
|
||||||
|
) -> Optional[EveCharacter]:
|
||||||
|
"""
|
||||||
|
Get the main character for a given EveCharacter or None when no main character is set
|
||||||
|
|
||||||
|
:param character:
|
||||||
|
:type character:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
userprofile = character.character_ownership.user.profile
|
||||||
|
except (
|
||||||
|
AttributeError,
|
||||||
|
EveCharacter.character_ownership.RelatedObjectDoesNotExist,
|
||||||
|
CharacterOwnership.user.RelatedObjectDoesNotExist,
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return userprofile.main_character
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_from_evecharacter(character: EveCharacter) -> User:
|
||||||
|
"""
|
||||||
|
Get the user for an EveCharacter or the sentinel user when no user is found
|
||||||
|
|
||||||
|
:param character:
|
||||||
|
:type character:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
userprofile = character.character_ownership.user.profile
|
||||||
|
except (
|
||||||
|
AttributeError,
|
||||||
|
EveCharacter.character_ownership.RelatedObjectDoesNotExist,
|
||||||
|
CharacterOwnership.user.RelatedObjectDoesNotExist,
|
||||||
|
):
|
||||||
|
return get_sentinel_user()
|
||||||
|
|
||||||
|
return userprofile.user
|
||||||
90
allianceauth/framework/api/user.py
Normal file
90
allianceauth/framework/api/user.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""
|
||||||
|
Alliance Auth User API
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from allianceauth.authentication.models import CharacterOwnership
|
||||||
|
from allianceauth.eveonline.models import EveCharacter
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_characters_from_user(user: User) -> list:
|
||||||
|
"""
|
||||||
|
Get all characters from a user or an empty list
|
||||||
|
when no characters are found for the user or the user is None
|
||||||
|
|
||||||
|
:param user:
|
||||||
|
:type user:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
characters = [
|
||||||
|
char.character for char in CharacterOwnership.objects.filter(user=user)
|
||||||
|
]
|
||||||
|
except AttributeError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return characters
|
||||||
|
|
||||||
|
|
||||||
|
def get_main_character_from_user(user: User) -> Optional[EveCharacter]:
|
||||||
|
"""
|
||||||
|
Get the main character from a user
|
||||||
|
|
||||||
|
:param user:
|
||||||
|
:type user:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
main_character = user.profile.main_character
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return main_character
|
||||||
|
|
||||||
|
|
||||||
|
def get_main_character_name_from_user(user: User) -> str:
|
||||||
|
"""
|
||||||
|
Get the main character name from a user
|
||||||
|
|
||||||
|
:param user:
|
||||||
|
:type user:
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
sentinel_user = get_sentinel_user()
|
||||||
|
|
||||||
|
return sentinel_user.username
|
||||||
|
|
||||||
|
main_character = get_main_character_from_user(user=user)
|
||||||
|
|
||||||
|
try:
|
||||||
|
username = main_character.character_name
|
||||||
|
except AttributeError:
|
||||||
|
return str(user)
|
||||||
|
|
||||||
|
return username
|
||||||
|
|
||||||
|
|
||||||
|
def get_sentinel_user() -> User:
|
||||||
|
"""
|
||||||
|
Get the sentinel user or create one
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
return User.objects.get_or_create(username="deleted")[0]
|
||||||
14
allianceauth/framework/apps.py
Normal file
14
allianceauth/framework/apps.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
Framework App Config
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class FrameworkConfig(AppConfig):
|
||||||
|
"""
|
||||||
|
Framework App Config
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "allianceauth.framework"
|
||||||
|
label = "framework"
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Alliance Auth CSS Framework
|
||||||
|
*
|
||||||
|
* This provides some CSS classes together with a couple of Bootstrap fixes
|
||||||
|
* to be used throughout Alliance Auth and its Community Apps
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Bootstrap fixes
|
||||||
|
------------------------------------------------------------------------------------- */
|
||||||
|
@media all {
|
||||||
|
.table {
|
||||||
|
--bs-table-bg: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Side Navigation
|
||||||
|
------------------------------------------------------------------------------------- */
|
||||||
|
@media all {
|
||||||
|
#sidebar > div {
|
||||||
|
width: 325px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu items in general */
|
||||||
|
#sidebar-menu li > a,
|
||||||
|
#sidebar-menu li > ul > li > a {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parent items with chevron and possible badge */
|
||||||
|
#sidebar-menu li:has(span.badge) > a[data-bs-toggle="collapse"] {
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Child items with possible badge */
|
||||||
|
#sidebar-menu li > ul > li > a {
|
||||||
|
max-width: 189px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chevron icons */
|
||||||
|
#sidebar-menu [data-bs-toggle="collapse"] > i.fa-chevron-down,
|
||||||
|
#sidebar-menu [data-bs-toggle="collapse"].collapsed > i.fa-chevron-right {
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-menu [data-bs-toggle="collapse"] > i.fa-chevron-right,
|
||||||
|
#sidebar-menu [data-bs-toggle="collapse"].collapsed > i.fa-chevron-down {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cursor classes
|
||||||
|
------------------------------------------------------------------------------------- */
|
||||||
|
@media all {
|
||||||
|
.cursor-auto {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-default {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-wait {
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-text {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-move {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-help {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-not-allowed {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-inherit {
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-zoom-in {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-zoom-out {
|
||||||
|
cursor: zoom-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callouts
|
||||||
|
*
|
||||||
|
* Not quite alerts, but custom and helpful notes for folks.
|
||||||
|
* Requires a base and modifier class.
|
||||||
|
------------------------------------------------------------------------------------- */
|
||||||
|
@media all {
|
||||||
|
/* Common styles for all types */
|
||||||
|
.aa-callout {
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-left-width: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aa-callout.aa-callout-sm {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aa-callout.aa-callout-lg {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Last item bottom margin should be 0 */
|
||||||
|
.aa-callout :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variations */
|
||||||
|
.aa-callout.aa-callout-danger {
|
||||||
|
border-left-color: var(--bs-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aa-callout.aa-callout-info {
|
||||||
|
border-left-color: var(--bs-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aa-callout.aa-callout-success {
|
||||||
|
border-left-color: var(--bs-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aa-callout.aa-callout-warning {
|
||||||
|
border-left-color: var(--bs-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{#Usage:#}
|
||||||
|
{# {% include "framework/dashboard/widget-title.html" with title="Foobar" %}#}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<h4 class="ms-auto me-auto mb-3">
|
||||||
|
{{ title }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{#Usage:#}
|
||||||
|
{# {% include "framework/header/page-header.html" with title="Foobar" subtitle="Barfoo" %}#}
|
||||||
|
|
||||||
|
{% if title %}
|
||||||
|
<h1 class="page-header text-center mb-3">
|
||||||
|
{{ title }}
|
||||||
|
|
||||||
|
{% if subtitle %}
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">{{ subtitle }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
{% endif %}
|
||||||
3
allianceauth/framework/tests/__init__.py
Normal file
3
allianceauth/framework/tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Initializing our tests
|
||||||
|
"""
|
||||||
179
allianceauth/framework/tests/test_api_user.py
Normal file
179
allianceauth/framework/tests/test_api_user.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
Test sentinel user
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Alliance Auth
|
||||||
|
from allianceauth.framework.api.user import (
|
||||||
|
get_sentinel_user,
|
||||||
|
get_main_character_from_user,
|
||||||
|
get_main_character_name_from_user
|
||||||
|
)
|
||||||
|
from allianceauth.tests.auth_utils import AuthUtils
|
||||||
|
|
||||||
|
|
||||||
|
class TestSentinelUser(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the sentinel user
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_should_create_user_when_it_does_not_exist(self) -> None:
|
||||||
|
"""
|
||||||
|
Test should create a sentinel user when it doesn't exist
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
# when
|
||||||
|
user = get_sentinel_user()
|
||||||
|
|
||||||
|
# then
|
||||||
|
self.assertEqual(first=user.username, second="deleted")
|
||||||
|
|
||||||
|
def test_should_return_user_when_it_does(self) -> None:
|
||||||
|
"""
|
||||||
|
Test should return sentinel user when it exists
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
# given
|
||||||
|
User.objects.create_user(username="deleted")
|
||||||
|
|
||||||
|
# when
|
||||||
|
user = get_sentinel_user()
|
||||||
|
|
||||||
|
# then
|
||||||
|
self.assertEqual(first=user.username, second="deleted")
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetMainForUser(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for get_main_character_from_user
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
"""
|
||||||
|
Set up groups and users
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
cls.character_name = "William T. Riker"
|
||||||
|
cls.character_name_2 = "Christopher Pike"
|
||||||
|
|
||||||
|
cls.username = re.sub(pattern=r"[^\w\d@\.\+-]", repl="_", string=cls.character_name)
|
||||||
|
cls.username_2 = re.sub(
|
||||||
|
pattern=r"[^\w\d@\.\+-]", repl="_", string=cls.character_name_2
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.user = AuthUtils.create_user(username=cls.username)
|
||||||
|
cls.user_without_main = AuthUtils.create_user(
|
||||||
|
username=cls.username_2, disconnect_signals=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.character = AuthUtils.add_main_character_2(
|
||||||
|
user=cls.user, name=cls.character_name, character_id=1001
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_main_character_from_user_should_return_character_name(self):
|
||||||
|
"""
|
||||||
|
Test should return the main character name for a regular user
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
character = get_main_character_from_user(user=self.user)
|
||||||
|
|
||||||
|
self.assertEqual(first=character, second=self.character)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_main_character_from_user_should_return_none_for_no_main_character(self):
|
||||||
|
"""
|
||||||
|
Test should return None for User without a main character
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
character = get_main_character_from_user(user=self.user_without_main)
|
||||||
|
|
||||||
|
self.assertIsNone(obj=character)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_main_character_from_user_should_none(self):
|
||||||
|
"""
|
||||||
|
Test should return None when user is None
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = None
|
||||||
|
|
||||||
|
character = get_main_character_from_user(user=user)
|
||||||
|
|
||||||
|
self.assertIsNone(obj=character)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_main_character_name_from_user_should_return_character_name(self):
|
||||||
|
"""
|
||||||
|
Test should return the main character name for a regular user
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
character_name = get_main_character_name_from_user(user=self.user)
|
||||||
|
|
||||||
|
self.assertEqual(first=character_name, second=self.character_name)
|
||||||
|
|
||||||
|
def test_get_main_character_name_from_user_should_return_user_name(self):
|
||||||
|
"""
|
||||||
|
Test should return just the username for a user without a main character
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
character_name = get_main_character_name_from_user(user=self.user_without_main)
|
||||||
|
|
||||||
|
self.assertEqual(first=character_name, second=self.username_2)
|
||||||
|
|
||||||
|
def test_get_main_character_name_from_user_should_return_sentinel_user(self):
|
||||||
|
"""
|
||||||
|
Test should return "deleted" as username (Sentinel User)
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = get_sentinel_user()
|
||||||
|
|
||||||
|
character_name = get_main_character_name_from_user(user=user)
|
||||||
|
|
||||||
|
self.assertEqual(first=character_name, second="deleted")
|
||||||
|
|
||||||
|
def test_get_main_character_name_from_user_should_return_sentinel_user_for_none(self):
|
||||||
|
"""
|
||||||
|
Test should return "deleted" (Sentinel User) if user is None
|
||||||
|
|
||||||
|
:return:
|
||||||
|
:rtype:
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = None
|
||||||
|
|
||||||
|
character_name = get_main_character_name_from_user(user=user)
|
||||||
|
|
||||||
|
self.assertEqual(first=character_name, second="deleted")
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from allianceauth.menu.hooks import MenuItemHook
|
||||||
|
|
||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.services.hooks import UrlHook
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
|
|
||||||
from . import urls
|
from . import urls
|
||||||
@@ -15,7 +16,7 @@ class GroupManagementMenuItem(MenuItemHook):
|
|||||||
MenuItemHook.__init__(
|
MenuItemHook.__init__(
|
||||||
self,
|
self,
|
||||||
text=_("Group Management"),
|
text=_("Group Management"),
|
||||||
classes="fas fa-users-cog fa-fw",
|
classes="fa-solid fa-users-gear",
|
||||||
url_name="groupmanagement:management",
|
url_name="groupmanagement:management",
|
||||||
order=50,
|
order=50,
|
||||||
navactive=[
|
navactive=[
|
||||||
@@ -33,11 +34,43 @@ class GroupManagementMenuItem(MenuItemHook):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
<li class="d-flex m-2 p-2 pt-0 pb-0 mt-0 mb-0">
|
||||||
|
<i class="fa-solid fa-users fa-fw align-self-center me-2"></i>
|
||||||
|
<a class="nav-link flex-fill align-self-center {% navactive request 'groupmanagement:groups' %}" href="{% url 'groupmanagement:groups' %}">
|
||||||
|
{% translate "Groups" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class GroupsMenuItem(MenuItemHook):
|
||||||
|
def __init__(self):
|
||||||
|
MenuItemHook.__init__(
|
||||||
|
self,
|
||||||
|
text=_("Groups"),
|
||||||
|
classes="fa-solid fa-user",
|
||||||
|
url_name="groupmanagement:groups",
|
||||||
|
order=25,
|
||||||
|
navactive=[
|
||||||
|
"groupmanagement:groups", # group list view
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, request):
|
||||||
|
return MenuItemHook.render(self, request)
|
||||||
|
|
||||||
|
|
||||||
@hooks.register("menu_item_hook")
|
@hooks.register("menu_item_hook")
|
||||||
def register_menu():
|
def register_manager_menu():
|
||||||
return GroupManagementMenuItem()
|
return GroupManagementMenuItem()
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register("menu_item_hook")
|
||||||
|
def register_groups_menu():
|
||||||
|
return GroupsMenuItem()
|
||||||
|
|
||||||
|
|
||||||
@hooks.register("url_hook")
|
@hooks.register("url_hook")
|
||||||
def register_urls():
|
def register_urls():
|
||||||
return UrlHook(urls, "group", r"^groups/")
|
return UrlHook(urls, "group", r"^groups/")
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %}
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load navactive %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
{{ group }} {% translate "Audit Log" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Audit Log" %} - {{ group.name }}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
|
{% block header_nav_collapse_left %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Back" %}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
|
||||||
<br>
|
|
||||||
{% include 'groupmanagement/menu.html' %}
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
{{ group }} - {% translate "Audit Log" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>
|
|
||||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
|
||||||
{% translate "Back" %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if entries %}
|
{% if entries %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped" id="log-entries">
|
<table class="table table-striped" id="log-entries">
|
||||||
@@ -67,39 +66,31 @@
|
|||||||
{% translate "No entries found for this group." %}
|
{% translate "No entries found for this group." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js-bs5.html' %}
|
||||||
{% include 'bundles/moment-js.html' with locale=True %}
|
{% include 'bundles/moment-js.html' with locale=True %}
|
||||||
{% include 'bundles/filterdropdown-js.html' %}
|
{% include 'bundles/filterdropdown-js.html' %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
<script>
|
||||||
{% include 'bundles/datatables-css.html' %}
|
$.fn.dataTable.moment = (format, locale) => {
|
||||||
{% endblock %}
|
const types = $.fn.dataTable.ext.type;
|
||||||
|
|
||||||
{% block extra_script %}
|
|
||||||
$.fn.dataTable.moment = function(format, locale) {
|
|
||||||
let types = $.fn.dataTable.ext.type;
|
|
||||||
|
|
||||||
// Add type detection
|
// Add type detection
|
||||||
types.detect.unshift(function(d) {
|
types.detect.unshift((d) => {
|
||||||
return moment(d, format, locale, true).isValid() ?
|
return moment(d, format, locale, true).isValid() ?
|
||||||
'moment-'+format :
|
'moment-'+format :
|
||||||
null;
|
null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add sorting method - use an integer for the sorting
|
// Add sorting method - use an integer for the sorting
|
||||||
types.order[ 'moment-'+format+'-pre' ] = function(d) {
|
types.order[ 'moment-'+format+'-pre' ] = (d) => {
|
||||||
return moment(d, format, locale, true).unix();
|
return moment(d, format, locale, true).unix();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(() => {
|
||||||
$.fn.dataTable.moment('YYYY-MMM-D, HH:mm');
|
$.fn.dataTable.moment('YYYY-MMM-D, HH:mm');
|
||||||
|
|
||||||
$('#log-entries').DataTable({
|
$('#log-entries').DataTable({
|
||||||
@@ -126,10 +117,16 @@
|
|||||||
idx: 6
|
idx: 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
bootstrap: true
|
bootstrap: true,
|
||||||
|
bootstrap_version: 5
|
||||||
},
|
},
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"stateDuration": 0
|
"stateDuration": 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include 'bundles/datatables-css-bs5.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,29 +1,28 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load evelinks %}
|
{% load evelinks %}
|
||||||
|
{% load navactive %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Group Members" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Group Members" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Group Members" %} - {{ group.name }}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
|
{% block header_nav_collapse_left %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Back" %}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
|
||||||
<br>
|
|
||||||
{% include 'groupmanagement/menu.html' %}
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
{{ group.name }} - {% translate 'Members' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>
|
|
||||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
|
||||||
{% translate "Back" %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if group.user_set %}
|
{% if group.user_set %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-aa" id="tab_group_members">
|
<table class="table" id="tab_group_members">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
@@ -36,7 +35,8 @@
|
|||||||
{% for member in members %}
|
{% for member in members %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}">
|
<img src="{{ member.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ member.main_char.character_name }}">
|
||||||
|
|
||||||
{% if member.main_char %}
|
{% if member.main_char %}
|
||||||
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
|
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
|
||||||
{{ member.main_char.character_name }}
|
{{ member.main_char.character_name }}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if member.is_leader %}
|
{% if member.is_leader %}
|
||||||
<i class="fas fa-star" title="{% translate "Group leader" %}" style="margin-left: 1rem;"></i>
|
<sup><i class="fa-solid fa-star" title="{% translate "Group leader" %}"></i></sup>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -61,9 +61,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-right">
|
<td class="text-end">
|
||||||
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
|
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% translate "Remove from group" %}">
|
||||||
<i class="glyphicon glyphicon-remove"></i>
|
<i class="fa-solid fa-xmark"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="text-muted">
|
<p class="text-muted">
|
||||||
<i class="fas fa-star"></i>: {% translate "Group leader" %}
|
<i class="fa-solid fa-star"></i>: {% translate "Group leader" %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -80,22 +80,14 @@
|
|||||||
{% translate "No group members to list." %}
|
{% translate "No group members to list." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/datatables-js.html' %}
|
{% include 'bundles/datatables-js-bs5.html' %}
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
<script>
|
||||||
{% include 'bundles/datatables-css.html' %}
|
$(document).ready(() => {
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_script %}
|
|
||||||
$(document).ready(function(){
|
|
||||||
$('#tab_group_members').DataTable({
|
$('#tab_group_members').DataTable({
|
||||||
order: [[0, "asc"]],
|
order: [[0, "asc"]],
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
@@ -108,4 +100,9 @@
|
|||||||
"stateDuration": 0
|
"stateDuration": 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include 'bundles/datatables-css-bs5.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load navactive %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Groups Membership" %}{% endblock page_title %}
|
{% block page_title %}{% translate "Groups Membership" %}{% endblock page_title %}
|
||||||
|
{% block header_nav_brand %}{% translate "Groups Membership" %}{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block extra_css %}{% endblock extra_css %}
|
{% block extra_css %}{% endblock extra_css %}
|
||||||
|
|
||||||
|
{% block header_nav_collapse_left %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Join/Leave Requests" %}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock header_nav_collapse_left %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
|
||||||
<br>
|
|
||||||
{% include 'groupmanagement/menu.html' %}
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
{% translate "Groups" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body">
|
|
||||||
{% if groups %}
|
{% if groups %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-aa">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Name" %}</th>
|
<th>{% translate "Name" %}</th>
|
||||||
<th>{% translate "Description" %}</th>
|
<th>{% translate "Description" %}</th>
|
||||||
<th>{% translate "Status" %}</th>
|
<th>{% translate "Status" %}</th>
|
||||||
<th style="white-space: nowrap;">{% translate "Member Count" %}</th>
|
<th style="white-space: nowrap;" class="text-center">{% translate "Member Count" %}</th>
|
||||||
<th style="min-width: 170px;"></th>
|
<th style="min-width: 170px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody class="align-middle">
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -39,29 +39,30 @@
|
|||||||
|
|
||||||
<td>
|
<td>
|
||||||
{% if group.authgroup.hidden %}
|
{% if group.authgroup.hidden %}
|
||||||
<span class="label label-info">{% translate "Hidden" %}</span>
|
<span class="badge bg-info">{% translate "Hidden" %}</span>
|
||||||
{% elif group.authgroup.open %}
|
{% endif %}
|
||||||
<span class="label label-success">{% translate "Open" %}</span>
|
{% if group.authgroup.open %}
|
||||||
|
<span class="badge bg-success">{% translate "Open" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-default">{% translate "Requestable" %}</span>
|
<span class="badge bg-secondary">{% translate "Requestable" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-right">
|
<td class="text-center">
|
||||||
{{ group.num_members }}
|
{{ group.num_members }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="text-right">
|
<td class="text-end">
|
||||||
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
|
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% translate "View Members" %}">
|
||||||
<i class="glyphicon glyphicon-eye-open"></i>
|
<i class="fa-regular fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
|
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% translate "Audit Members" %}">
|
||||||
<i class="glyphicon glyphicon-list-alt"></i>
|
<i class="fa-regular fa-rectangle-list"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<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" %}">
|
<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" %}">
|
||||||
<i class="glyphicon glyphicon-copy"></i>
|
<i class="fa-regular fa-clipboard"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -74,15 +75,12 @@
|
|||||||
{% translate "No groups to list." %}
|
{% translate "No groups to list." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block extra_javascript %}
|
{% block extra_javascript %}
|
||||||
{% include 'bundles/clipboard-js.html' %}
|
{% include "bundles/clipboard-js.html" %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
new ClipboardJS('#clipboard-copy');
|
new ClipboardJS('#clipboard-copy');
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock extra_javascript %}
|
||||||
|
|||||||
@@ -1,29 +1,69 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Available Groups" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
{% block extra_css %}{% endblock extra_css %}
|
{% translate "Available Groups" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "Available Groups" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
|
{% if manager_perms %}
|
||||||
|
{% block header_nav_collapse_left %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}
|
||||||
|
{% if req_count %}
|
||||||
|
<span class="badge bg-secondary">{{ req_count }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
|
||||||
<h1 class="page-header text-center">{% translate "Available Groups" %}</h1>
|
|
||||||
{% if groups %}
|
{% if groups %}
|
||||||
<table class="table table-aa">
|
<table class="table" id="groupsTable" >
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Name" %}</th>
|
<th>{% translate "Name" %}</th>
|
||||||
<th>{% translate "Description" %}</th>
|
<th>{% translate "Description" %}</th>
|
||||||
|
<th>
|
||||||
|
{% translate "Leaders" %}<br>
|
||||||
|
<span class="my-1 me-1 fw-lighter badge bg-primary">{% translate "User" %}</span>
|
||||||
|
<span class="my-1 me-1 fw-lighter badge bg-secondary">{% translate "Group" %}</span>
|
||||||
|
</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody class>
|
||||||
{% for g in groups %}
|
{% for g in groups %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ g.group.name }}</td>
|
<td>{{ g.group.name }}</td>
|
||||||
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
|
<td>
|
||||||
<td class="text-right">
|
{% if g.group.authgroup.description %}
|
||||||
{% if g.group in user.groups.all %}
|
{{ g.group.authgroup.description|linebreaks|urlize }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td style="max-width: 30%;">
|
||||||
|
{% if g.group.authgroup.group_leaders.all.count %}
|
||||||
|
{% for leader in g.group.authgroup.group_leaders.all %}
|
||||||
|
{% if leader.profile.main_character %}
|
||||||
|
<span class="my-1 me-1 badge bg-primary">{{leader.profile.main_character}}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if g.group.authgroup.group_leaders.all.count %}
|
||||||
|
{% for group in g.group.authgroup.group_leader_groups.all %}
|
||||||
|
<span class="my-1 me-1 badge bg-secondary">{{group.name}}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
{% if g.group in user_groups %}
|
||||||
{% if not g.request %}
|
{% if not g.request %}
|
||||||
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
|
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
|
||||||
{% translate "Leave" %}
|
{% translate "Leave" %}
|
||||||
@@ -58,5 +98,18 @@
|
|||||||
{% translate "No groups available." %}
|
{% translate "No groups available." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block extra_javascript %}
|
||||||
|
{% include 'bundles/datatables-js-bs5.html' %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(() => {
|
||||||
|
$('#groupsTable').DataTable();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{% include 'bundles/datatables-css-bs5.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,55 +1,55 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load evelinks %}
|
{% load evelinks %}
|
||||||
|
{% load navactive %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Groups Management" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Groups Management" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block header_nav_brand %}
|
||||||
<style>
|
{% translate "Groups Management" %}
|
||||||
.nav-tabs > li.active > a {
|
{% endblock header_nav_brand %}
|
||||||
background-color: rgb(236, 240, 241) !important;
|
|
||||||
color: rgb(44, 62, 80);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock extra_css %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block header_nav_collapse_left %}
|
||||||
<div class="col-lg-12">
|
|
||||||
<br>
|
|
||||||
{% include 'groupmanagement/menu.html' %}
|
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<a data-toggle="tab" href="#add">
|
<a class="nav-link active" id="add-tab" data-bs-toggle="tab" data-bs-target="#add" type="button" role="tab" aria-controls="add" aria-selected="true">
|
||||||
{% translate "Join Requests" %}
|
{% translate "Join Requests" %}
|
||||||
|
|
||||||
{% if acceptrequests %}
|
{% if acceptrequests %}
|
||||||
<span class="badge">{{ acceptrequests|length }}</span>
|
<span class="badge bg-secondary">{{ acceptrequests|length }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if not show_leave_tab %}
|
{% if not show_leave_tab %}
|
||||||
<li>
|
<li>
|
||||||
<a data-toggle="tab" href="#leave">
|
<a class="nav-link" id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave" type="button" role="tab" aria-controls="leave" aria-selected="false">
|
||||||
{% translate "Leave Requests" %}
|
{% translate "Leave Requests" %}
|
||||||
|
|
||||||
{% if leaverequests %}
|
{% if leaverequests %}
|
||||||
<span class="badge">{{ leaverequests|length }}</span>
|
<span class="badge bg-secondary">{{ leaverequests|length }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="panel panel-default panel-tabs-aa">
|
<li class="nav-item ">
|
||||||
<div class="panel-body">
|
<a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">
|
||||||
|
{% translate "Group Membership" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endblock header_nav_collapse_left %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
||||||
<div id="add" class="tab-pane active">
|
<div id="add" class="tab-pane active">
|
||||||
{% if acceptrequests %}
|
{% if acceptrequests %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-aa">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
@@ -59,11 +59,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody class="align-middle">
|
||||||
{% for acceptrequest in acceptrequests %}
|
{% for acceptrequest in acceptrequests %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
|
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ acceptrequest.main_char.character_name }}">
|
||||||
|
|
||||||
{% if acceptrequest.main_char %}
|
{% if acceptrequest.main_char %}
|
||||||
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
|
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
|
||||||
{{ acceptrequest.main_char.character_name }}
|
{{ acceptrequest.main_char.character_name }}
|
||||||
@@ -72,22 +73,25 @@
|
|||||||
{{ acceptrequest.user.username }}
|
{{ acceptrequest.user.username }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{% if acceptrequest.main_char %}
|
{% if acceptrequest.main_char %}
|
||||||
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
|
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||||
{{ acceptrequest.main_char.corporation_name }}
|
{{ acceptrequest.main_char.corporation_name }}
|
||||||
</a><br>
|
</a>
|
||||||
|
<br>
|
||||||
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
|
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% translate "(unknown)" %}
|
{% translate "(unknown)" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{{ acceptrequest.group.name }}</td>
|
<td>{{ acceptrequest.group.name }}</td>
|
||||||
<td class="text-right">
|
|
||||||
|
<td class="text-end">
|
||||||
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
||||||
{% translate "Accept" %}
|
{% translate "Accept" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
||||||
{% translate "Reject" %}
|
{% translate "Reject" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -98,7 +102,9 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning text-center">{% translate "No group add requests." %}</div>
|
<div class="aa-callout aa-callout-warning text-center">
|
||||||
|
{% translate "No group add requests." %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -106,7 +112,7 @@
|
|||||||
<div id="leave" class="tab-pane">
|
<div id="leave" class="tab-pane">
|
||||||
{% if leaverequests %}
|
{% if leaverequests %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-aa">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% translate "Character" %}</th>
|
<th>{% translate "Character" %}</th>
|
||||||
@@ -116,11 +122,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody class="align-middle">
|
||||||
{% for leaverequest in leaverequests %}
|
{% for leaverequest in leaverequests %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
|
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="rounded-circle" style="margin-right: 1rem;" alt="{{ leaverequest.main_char.character_name }}">
|
||||||
|
|
||||||
{% if leaverequest.main_char %}
|
{% if leaverequest.main_char %}
|
||||||
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
|
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
|
||||||
{{ leaverequest.main_char.character_name }}
|
{{ leaverequest.main_char.character_name }}
|
||||||
@@ -129,18 +136,22 @@
|
|||||||
{{ leaverequest.user.username }}
|
{{ leaverequest.user.username }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{% if leaverequest.main_char %}
|
{% if leaverequest.main_char %}
|
||||||
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
|
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||||
{{ leaverequest.main_char.corporation_name }}
|
{{ leaverequest.main_char.corporation_name }}
|
||||||
</a><br>
|
</a>
|
||||||
|
<br>
|
||||||
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
|
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% translate "(unknown)" %}
|
{% translate "(unknown)" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{{ leaverequest.group.name }}</td>
|
<td>{{ leaverequest.group.name }}</td>
|
||||||
<td class="text-right">
|
|
||||||
|
<td class="text-end">
|
||||||
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
|
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
|
||||||
{% translate "Accept" %}
|
{% translate "Accept" %}
|
||||||
</a>
|
</a>
|
||||||
@@ -155,12 +166,9 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning text-center">{% translate "No group leave requests." %}</div>
|
<div class="aa-callout aa-callout-warning text-center">{% translate "No group leave requests." %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load navactive %}
|
{% load navactive %}
|
||||||
|
|
||||||
<nav class="navbar navbar-default">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
|
||||||
<span class="sr-only">{% translate "Toggle navigation" %}</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</button>
|
|
||||||
<a class="navbar-brand" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<li class="nav-item ">
|
||||||
<ul class="nav navbar-nav">
|
<a class="nav-link {% navactive request 'groupmanagement:management' %}" href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
|
||||||
<li class="{% navactive request 'groupmanagement:management' %}">
|
|
||||||
<a href="{% url 'groupmanagement:management' %}">{% translate "Group Requests" %}</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li class="{% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}">
|
<li class="nav-item ">
|
||||||
<a href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
|
<a class="nav-link {% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}" href="{% url 'groupmanagement:membership' %}">{% translate "Group Membership" %}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class TestViews(TestCase):
|
|||||||
content = response_content_to_str(response)
|
content = response_content_to_str(response)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('<a data-toggle="tab" href="#leave">', content)
|
self.assertIn('id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
|
||||||
self.assertIn('<div id="leave" class="tab-pane">', content)
|
self.assertIn('<div id="leave" class="tab-pane">', content)
|
||||||
|
|
||||||
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
|
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
|
||||||
@@ -83,7 +83,7 @@ class TestViews(TestCase):
|
|||||||
content = response_content_to_str(response)
|
content = response_content_to_str(response)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertNotIn('<a data-toggle="tab" href="#leave">', content)
|
self.assertNotIn('id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
|
||||||
self.assertNotIn('<div id="leave" class="tab-pane">', content)
|
self.assertNotIn('<div id="leave" class="tab-pane">', content)
|
||||||
|
|
||||||
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
|
@override_settings(GROUPMANAGEMENT_AUTO_LEAVE=True)
|
||||||
@@ -99,5 +99,5 @@ class TestViews(TestCase):
|
|||||||
# then
|
# then
|
||||||
content = response_content_to_str(response)
|
content = response_content_to_str(response)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertIn('<a data-toggle="tab" href="#leave">', content)
|
self.assertIn('<a class="nav-link" id="leave-tab" data-bs-toggle="tab" data-bs-target="#leave"', content)
|
||||||
self.assertIn('<div id="leave" class="tab-pane">', content)
|
self.assertIn('<div id="leave" class="tab-pane">', content)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def group_membership_audit(request, group_id):
|
|||||||
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404("Group does not exist")
|
raise Http404("Group does not exist")
|
||||||
render_items = {'group': group.name}
|
render_items = {'group': group}
|
||||||
entries = RequestLog.objects.filter(group=group).order_by('-date')
|
entries = RequestLog.objects.filter(group=group).order_by('-date')
|
||||||
render_items['entries'] = entries
|
render_items['entries'] = entries
|
||||||
|
|
||||||
@@ -314,8 +314,10 @@ def groups_view(request):
|
|||||||
groups_qs = GroupManager.get_joinable_groups_for_user(
|
groups_qs = GroupManager.get_joinable_groups_for_user(
|
||||||
request.user, include_hidden=False
|
request.user, include_hidden=False
|
||||||
)
|
)
|
||||||
groups_qs = groups_qs.order_by('name')
|
groups_qs = groups_qs.order_by('name').select_related("authgroup").prefetch_related('authgroup__group_leaders', 'authgroup__group_leaders__profile__main_character', 'authgroup__group_leader_groups')
|
||||||
groups = []
|
groups = []
|
||||||
|
|
||||||
|
## TODO see about making this faster
|
||||||
for group in groups_qs:
|
for group in groups_qs:
|
||||||
group_request = GroupRequest.objects\
|
group_request = GroupRequest.objects\
|
||||||
.filter(user=request.user)\
|
.filter(user=request.user)\
|
||||||
@@ -325,7 +327,14 @@ def groups_view(request):
|
|||||||
'request': group_request[0] if group_request else None
|
'request': group_request[0] if group_request else None
|
||||||
})
|
})
|
||||||
|
|
||||||
context = {'groups': groups}
|
count = 0
|
||||||
|
perms = GroupManager.can_manage_groups(request.user)
|
||||||
|
if perms:
|
||||||
|
count = GroupManager.pending_requests_count_for_user(request.user)
|
||||||
|
|
||||||
|
user_groups_list = list(request.user.groups.all())
|
||||||
|
context = {'groups': groups, "manager_perms": perms, "req_count":count, "user_groups": user_groups_list}
|
||||||
|
|
||||||
return render(request, 'groupmanagement/groups.html', context=context)
|
return render(request, 'groupmanagement/groups.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ def get_app_modules():
|
|||||||
|
|
||||||
|
|
||||||
def get_app_submodules(module_name):
|
def get_app_submodules(module_name):
|
||||||
"""
|
"""pyt
|
||||||
Get a specific sub module of the app
|
Get a specific sub module of the app
|
||||||
:param module_name: module name to get
|
:param module_name: module name to get
|
||||||
:return: name, module tuple
|
:return: name, module tuple
|
||||||
@@ -122,3 +122,17 @@ def get_hooks(name):
|
|||||||
"""
|
"""
|
||||||
register_all_hooks()
|
register_all_hooks()
|
||||||
return _hooks.get(name, [])
|
return _hooks.get(name, [])
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardItemHook:
|
||||||
|
def __init__(self, view_function, order:int=10):
|
||||||
|
self.view_function = view_function
|
||||||
|
self.order = order
|
||||||
|
|
||||||
|
def render(self, request):
|
||||||
|
try:
|
||||||
|
logger.debug(f"Rendering {self.view_function} to dashboard")
|
||||||
|
return self.view_function(request)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Rendering {self.view_function} failed!")
|
||||||
|
return ""
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from allianceauth import hooks
|
from allianceauth import hooks
|
||||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
from allianceauth.menu.hooks import MenuItemHook
|
||||||
|
from allianceauth.services.hooks import UrlHook
|
||||||
|
|
||||||
from . import urls
|
from . import urls
|
||||||
from .models import Application
|
from .models import Application
|
||||||
@@ -12,7 +13,7 @@ class ApplicationsMenu(MenuItemHook):
|
|||||||
MenuItemHook.__init__(
|
MenuItemHook.__init__(
|
||||||
self,
|
self,
|
||||||
_('Applications'),
|
_('Applications'),
|
||||||
'far fa-file fa-fw',
|
'fa-regular fa-file',
|
||||||
'hrapplications:index',
|
'hrapplications:index',
|
||||||
navactive=['hrapplications:'])
|
navactive=['hrapplications:'])
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Choose a Corp" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Choose a Corp" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "Choose a Corp" %}</h1>
|
{% translate "Choose a Corp" as page_header %}
|
||||||
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
{% if choices %}
|
{% if choices %}
|
||||||
<div class="panel panel-primary">
|
<div class="card card-primary">
|
||||||
<div class="panel-heading">{% translate "Available Corps" %}</div>
|
<div class="card-header">
|
||||||
|
<div class="card-title mb-0">{% translate "Available Corps" %}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<table class="table table-responsive">
|
<table class="table table-responsive">
|
||||||
{% for choice in choices %}
|
{% for choice in choices %}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -18,6 +32,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-danger">{% translate "No corps are accepting applications at this time." %}</div>
|
<div class="alert alert-danger">{% translate "No corps are accepting applications at this time." %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,35 +1,66 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}
|
{% block page_title %}
|
||||||
|
{% translate "Apply To" %} {{ corp.corporation_name }}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "Apply To" %} {{ corp.corporation_name }}</h1>
|
<h1 class="page-header text-center mb-3">
|
||||||
<div class="container-fluid">
|
{% translate "Apply To" %} {{ corp.corporation_name }}
|
||||||
<div class="col-md-4 col-md-offset-4">
|
</h1>
|
||||||
<div class="row">
|
|
||||||
<form class="form-signin">
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-title mb-0">
|
||||||
|
{% translate "Application form" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
<div class="form-group">
|
<div class="card mb-3 form-group border-0">
|
||||||
<label class="control-label" for="id_{{ question.pk }}">{{ question.title }}</label>
|
<div class="card-header">
|
||||||
<div class=" ">
|
<div class="card-title mb-0">{{ question.title }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
{% if question.help_text %}
|
{% if question.help_text %}
|
||||||
<div class="text-center">{{ question.help_text }}</div>
|
<p class="text-muted">
|
||||||
|
{{ question.help_text }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for choice in question.choices.all %}
|
{% for choice in question.choices.all %}
|
||||||
<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 }}">
|
<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>
|
<label for="id_{{ question.pk }}_choice_{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea>
|
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="10"></textarea>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit" formmethod="post">{% translate "Submit" %}</button>
|
|
||||||
|
<div class="form-group clearfix">
|
||||||
|
{% translate "Submit" as button_text %}
|
||||||
|
{% bootstrap_button button_class="btn btn-primary" content=button_text name="submitApplicationForm" id="submitApplicationForm" %}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
{% load bootstrap %}
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "HR Application Management" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
{% block extra_css %}{% endblock extra_css %}
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "Personal Applications" %}
|
{% translate "Personal Applications" as page_header %}
|
||||||
<div class="text-right">
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
{% if create %}
|
{% if create %}
|
||||||
<a href="{% url 'hrapplications:create_view' %}">
|
<a href="{% url 'hrapplications:create_view' %}">
|
||||||
<button type="button" class="btn btn-success">{% translate "Create Application" %}</button>
|
<button type="button" class="btn btn-success">{% translate "Create Application" %}</button>
|
||||||
@@ -17,9 +25,10 @@
|
|||||||
<button type="button" class="btn btn-success" disabled>{% translate "Create Application" %}</button>
|
<button type="button" class="btn btn-success" disabled>{% translate "Create Application" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
{% if personal_apps %}
|
{% if personal_apps %}
|
||||||
<div class="panel panel-default">
|
<div class="card card-default mb-3">
|
||||||
|
<div class="card-body">
|
||||||
<table class="table table-condensed">
|
<table class="table table-condensed">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "Username" %}</th>
|
<th class="text-center">{% translate "Username" %}</th>
|
||||||
@@ -27,27 +36,28 @@
|
|||||||
<th class="text-center">{% translate "Status" %}</th>
|
<th class="text-center">{% translate "Status" %}</th>
|
||||||
<th class="text-center">{% translate "Actions" %}</th>
|
<th class="text-center">{% translate "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for personal_app in personal_apps %}
|
{% for personal_app in personal_apps %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ personal_app.user.username }}</td>
|
<td class="text-center">{{ personal_app.user.username }}</td>
|
||||||
<td class="text-center">{{ personal_app.form.corp.corporation_name }}</td>
|
<td class="text-center">{{ personal_app.form.corp.corporation_name }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if personal_app.approved == None %}
|
{% if personal_app.approved == None %}
|
||||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||||
{% elif personal_app.approved == True %}
|
{% elif personal_app.approved == True %}
|
||||||
<div class="label label-success">{% translate "Approved" %}</div>
|
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'hrapplications:personal_view' personal_app.id %}" class="btn btn-primary">
|
<a href="{% url 'hrapplications:personal_view' personal_app.id %}" class="btn btn-primary">
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<i class="fa-solid fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if personal_app.approved == None %}
|
{% if personal_app.approved == None %}
|
||||||
<a href="{% url 'hrapplications:personal_removal' personal_app.id %}" class="btn btn-danger">
|
<a href="{% url 'hrapplications:personal_removal' personal_app.id %}" class="btn btn-danger">
|
||||||
<span class="glyphicon glyphicon-remove"></span>
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@@ -55,23 +65,54 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.auth.human_resources %}
|
{% if perms.auth.human_resources %}
|
||||||
<h1 class="page-header text-center">{% translate "Application Management" %}
|
{% translate "Application Management" as page_header %}
|
||||||
<div class="text-right">
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
<!-- Button trigger modal -->
|
<!-- Button trigger modal -->
|
||||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal">
|
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#modal-hr-search">
|
||||||
{% translate "Search Applications" %}
|
{% translate "Search Applications" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
<ul class="nav nav-tabs">
|
<div class="card card-default">
|
||||||
<li class="active"><a data-toggle="tab" href="#pending">{% translate "Pending" %}</a></li>
|
<div class="card-body clearfix">
|
||||||
<li><a data-toggle="tab" href="#reviewed">{% translate "Reviewed" %}</a></li>
|
<ul class="nav nav-tabs" id="application-list" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a
|
||||||
|
class="nav-link active"
|
||||||
|
id="pending"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-pending"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="tab-pending"
|
||||||
|
aria-selected="true"
|
||||||
|
>
|
||||||
|
{% translate "Pending" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
id="reviewed"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-reviewed"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="tab-reviewed"
|
||||||
|
aria-selected="false"
|
||||||
|
>
|
||||||
|
{% translate "Reviewed" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
|
||||||
<div id="pending" class="tab-pane fade in active panel panel-default">
|
<div class="tab-content" id="application-list-content">
|
||||||
<div class="panel-body">
|
<div id="tab-pending" class="tab-pane fade show active" role="tabpanel" aria-labelledby="tab-pending">
|
||||||
{% if applications %}
|
{% if applications %}
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -82,6 +123,7 @@
|
|||||||
<th class="text-center">{% translate "Status" %}</th>
|
<th class="text-center">{% translate "Status" %}</th>
|
||||||
<th class="text-center">{% translate "Actions" %}</th>
|
<th class="text-center">{% translate "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for app in applications %}
|
{% for app in applications %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ app.created }}</td>
|
<td class="text-center">{{ app.created }}</td>
|
||||||
@@ -91,19 +133,19 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if app.approved == None %}
|
{% if app.approved == None %}
|
||||||
{% if app.reviewer_str %}
|
{% if app.reviewer_str %}
|
||||||
<div class="label label-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif app.approved == True %}
|
{% elif app.approved == True %}
|
||||||
<div class="label label-success">{% translate "Approved" %}</div>
|
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<i class="fa-solid fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -113,9 +155,8 @@
|
|||||||
<div class="alert alert-warning text-center">{% translate "No pending applications." %}</div>
|
<div class="alert alert-warning text-center">{% translate "No pending applications." %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div id="reviewed" class="tab-pane fade panel panel-default">
|
<div id="tab-reviewed" class="tab-pane fade" role="tabpanel" aria-labelledby="tab-reviewed">
|
||||||
<div class="panel-body">
|
|
||||||
{% if finished_applications %}
|
{% if finished_applications %}
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -126,6 +167,7 @@
|
|||||||
<th class="text-center">{% translate "Status" %}</th>
|
<th class="text-center">{% translate "Status" %}</th>
|
||||||
<th class="text-center">{% translate "Actions" %}</th>
|
<th class="text-center">{% translate "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for app in finished_applications %}
|
{% for app in finished_applications %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ app.created }}</td>
|
<td class="text-center">{{ app.created }}</td>
|
||||||
@@ -135,23 +177,24 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if app.approved == None %}
|
{% if app.approved == None %}
|
||||||
{% if app.reviewer_str %}
|
{% if app.reviewer_str %}
|
||||||
<div class="label label-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif app.approved == True %}
|
{% elif app.approved == True %}
|
||||||
<div class="label label-success">{% translate "Approved" %}</div>
|
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<i class="fa-solid fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if perms.hrapplications.delete_application %}
|
{% if perms.hrapplications.delete_application %}
|
||||||
<a href="{% url 'hrapplications:remove' app.id %}" class="btn btn-danger">
|
<a href="{% url 'hrapplications:remove' app.id %}" class="btn btn-danger">
|
||||||
<span class="glyphicon glyphicon-remove"></span>
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@@ -164,30 +207,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if perms.auth.human_resources %}
|
{% include "hrapplications/partials/modals/search.html" %}
|
||||||
<!-- Modal -->
|
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">{% translate "Close" %}</span></button>
|
|
||||||
<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">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ search_form|bootstrap }}
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Search" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{% load django_bootstrap5 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if perms.auth.human_resources %}
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="modal-hr-search" tabindex="-1" aria-labelledby="modalHrSearch" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title fs-5" id="modalHrSearchLabel">
|
||||||
|
{% translate "Application Search" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Close' %}"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<form class="form-signin" role="form" action="{% url 'hrapplications:search' %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% bootstrap_form search_form %}
|
||||||
|
|
||||||
|
<div class="form-group mt-3 clearfix">
|
||||||
|
{% translate "Search" as button_text %}
|
||||||
|
{% bootstrap_button button_class="btn btn-primary" content=button_text name="search" id="search" %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -1,49 +1,58 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
|
|
||||||
{% load bootstrap %}
|
{% load bootstrap %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "HR Application Management" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
{% block extra_css %}{% endblock extra_css %}
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
{% if perms.auth.human_resources %}
|
{% if perms.auth.human_resources %}
|
||||||
<h1 class="page-header text-center">{% translate "Application Search Results" %}
|
{% translate "Application Search Results" as page_header %}
|
||||||
<div class="text-right">
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
<!-- Button trigger modal -->
|
<!-- Button trigger modal -->
|
||||||
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#myModal">
|
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#modal-hr-search">
|
||||||
{% translate "Search Applications" %}
|
{% translate "Search Applications" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
|
||||||
<div class="container-fluid">
|
<div>
|
||||||
<table class="table table-bordered">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "Application ID" %}</th>
|
<th>{% translate "Application ID" %}</th>
|
||||||
<th class="text-center">{% translate "Username" %}</th>
|
<th>{% translate "Username" %}</th>
|
||||||
<th class="text-center">{% translate "Main Character" %}</th>
|
<th>{% translate "Main Character" %}</th>
|
||||||
<th class="text-center">{% translate "Corporation" %}</th>
|
<th>{% translate "Corporation" %}</th>
|
||||||
<th class="text-center">{% translate "Status" %}</th>
|
<th class="text-center">{% translate "Status" %}</th>
|
||||||
<th class="text-center">{% translate "Actions" %}</th>
|
<th class="text-end">{% translate "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for app in applications %}
|
{% for app in applications %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ app.id }}</td>
|
<td>{{ app.id }}</td>
|
||||||
<td class="text-center">{{ app.user }}</td>
|
<td>{{ app.user }}</td>
|
||||||
<td class="text-center">{{ app.main_character }}</td>
|
<td >{{ app.main_character }}</td>
|
||||||
<td class="text-center">{{ app.form.corp }}</td>
|
<td>{{ app.form.corp }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if app.approved == None %}
|
{% if app.approved == None %}
|
||||||
<div class="label label-warning">{% translate "Pending" %}</div>
|
<div class="badge bg-warning">{% translate "Pending" %}</div>
|
||||||
{% elif app.approved == True %}
|
{% elif app.approved == True %}
|
||||||
<div class="label label-success">{% translate "Approved" %}</div>
|
<div class="badge bg-success">{% translate "Approved" %}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="label label-danger">{% translate "Rejected" %}</div>
|
<div class="badge bg-danger">{% translate "Rejected" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-end">
|
||||||
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
<a href="{% url 'hrapplications:view' app.id %}" class="btn btn-primary">
|
||||||
<span class="glyphicon glyphicon-eye-open"></span>
|
<i class="fa-solid fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -53,27 +62,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if perms.auth.human_resources %}
|
{% include "hrapplications/partials/modals/search.html" %}
|
||||||
<!-- Modal -->
|
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">{% translate "Close" %}</span></button>
|
|
||||||
<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">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ search_form|bootstrap }}
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Search" %}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
{% extends "allianceauth/base.html" %}
|
{% extends "allianceauth/base-bs5.html" %}
|
||||||
{% load bootstrap %}
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "View Application" %}{% endblock page_title %}
|
{% block page_title %}
|
||||||
{% block extra_css %}{% endblock extra_css %}
|
{% translate "View Application" %}
|
||||||
|
{% endblock page_title %}
|
||||||
|
|
||||||
|
{% block header_nav_brand %}
|
||||||
|
{% translate "HR Application Management" %}
|
||||||
|
{% endblock header_nav_brand %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div>
|
||||||
<h1 class="page-header text-center">{% translate "View Application" %}</h1>
|
{% translate "View Application" as page_header %}
|
||||||
<div class="container-fluid">
|
{% include "framework/header/page-header.html" with title=page_header %}
|
||||||
<div class="col-md-6 col-md-offset-3">
|
|
||||||
<div class="row">
|
<div>
|
||||||
{% if app.approved %}
|
{% if app.approved %}
|
||||||
<div class="alert alert-success text-center">{% translate "Approved" %}</div>
|
<div class="alert alert-success text-center">{% translate "Approved" %}</div>
|
||||||
{% elif app.approved == False %}
|
{% elif app.approved == False %}
|
||||||
@@ -18,13 +24,18 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning text-center">{% translate "Pending" %}</div>
|
<div class="alert alert-warning text-center">{% translate "Pending" %}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if app.reviewer_str %}
|
{% if app.reviewer_str %}
|
||||||
<div class="alert alert-info text-center">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
<div class="alert alert-info text-center">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="panel panel-info">
|
<div class="card mb-3">
|
||||||
<div class="panel-heading">{% translate "Applicant" %}</div>
|
<div class="card-header bg-info">
|
||||||
|
<div class="card-title mb-0">{% translate "Applicant" %}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">{% translate "User" %}</th>
|
<th class="text-center">{% translate "User" %}</th>
|
||||||
@@ -36,49 +47,61 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-info">
|
</div>
|
||||||
<div class="panel-heading">{% translate "Characters" %}</div>
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header bg-info">
|
||||||
|
<div class="card-title mb-0">{% translate "Characters" %}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center"></th>
|
<th></th>
|
||||||
<th class="text-center">{% translate "Name" %}</th>
|
<th>{% translate "Name" %}</th>
|
||||||
<th class="text-center">{% translate "Corp" %}</th>
|
<th>{% translate "Corporation" %}</th>
|
||||||
<th class="text-center">{% translate "Alliance" %}</th>
|
<th>{% translate "Alliance" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for char in app.characters %}
|
{% for char in app.characters %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">
|
<td>
|
||||||
<img class="ra-avatar img-responsive img-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
<img class="ra-avatar img-responsive rounded-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ char.character_name }}</td>
|
<td>{{ char.character_name }}</td>
|
||||||
<td class="text-center">{{ char.corporation_name }}</td>
|
<td>{{ char.corporation_name }}</td>
|
||||||
<td class="text-center">{{ char.alliance_name }}</td>
|
<td>{{ char.alliance_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
{% for response in responses %}
|
{% for response in responses %}
|
||||||
<div class="panel panel-default">
|
<div class="card mb-3">
|
||||||
<div class="panel-heading">{{ response.question.title }}</div>
|
<div class="card-header">
|
||||||
<div class="alert">{{ response.answer|linebreaksbr }}</div>
|
<div class="card-title mb-0">{{ response.question.title }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">{{ response.answer|linebreaksbr }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
{% if buttons %}
|
{% if buttons %}
|
||||||
{% if perms.auth.human_resources %}
|
{% if perms.auth.human_resources %}
|
||||||
<div class="row">
|
<div class="card mb-3">
|
||||||
<div class="panel panel-primary">
|
<div class="card-header">
|
||||||
<div class="panel-heading">{% translate "Actions" %}</div>
|
<div class="card-title mb-0">{% translate "Actions" %}</div>
|
||||||
<div class="panel-body text-center">
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body text-center">
|
||||||
{% if app.approved == None %}
|
{% if app.approved == None %}
|
||||||
{% if app.reviewer == user %}
|
{% if app.reviewer == user %}
|
||||||
{% if perms.hrapplications.approve_application %}
|
{% if perms.hrapplications.approve_application %}
|
||||||
<a href="{% url 'hrapplications:approve' app.id %}" class="btn btn-success">{% translate "Approve" %}</a>
|
<a href="{% url 'hrapplications:approve' app.id %}" class="btn btn-success">{% translate "Approve" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.hrapplications.reject_application %}
|
{% if perms.hrapplications.reject_application %}
|
||||||
<a href="{% url 'hrapplications:reject' app.id %}" class="btn btn-danger">{% translate "Reject" %}</a>
|
<a href="{% url 'hrapplications:reject' app.id %}" class="btn btn-warning">{% translate "Reject" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.hrapplications.delete_application %}
|
{% if perms.hrapplications.delete_application %}
|
||||||
<a href="{% url 'hrapplications:remove' app.id %}" class="btn btn-danger">{% translate "Delete" %}</a>
|
<a href="{% url 'hrapplications:remove' app.id %}" class="btn btn-danger">{% translate "Delete" %}</a>
|
||||||
@@ -87,63 +110,70 @@
|
|||||||
<a href="{% url 'hrapplications:mark_in_progress' app.id %}" class="btn btn-warning">{% translate "Mark in Progress" %}</a>
|
<a href="{% url 'hrapplications:mark_in_progress' app.id %}" class="btn btn-warning">{% translate "Mark in Progress" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.hrapplications.add_applicationcomment %}
|
{% if perms.hrapplications.add_applicationcomment %}
|
||||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">{% translate "Comment" %}</button>
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modal-hr-comment">{% translate "Comment" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
|
<div class="card-group" id="accordion" role="tablist" aria-multiselectable="true">
|
||||||
<div class="panel panel-default">
|
<div class="card card-default">
|
||||||
<div class="panel-heading" role="tab" id="headingThree">
|
<div class="card-header">
|
||||||
<h4 class="panel-title">
|
<div class="card-title mb-0">
|
||||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
|
||||||
{% translate 'Comments' %} ({{ comments.count }})
|
{% translate 'Comments' %} ({{ comments.count }})
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseThree" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingThree">
|
</div>
|
||||||
<div class="panel-body">
|
|
||||||
|
<div class="card-body">
|
||||||
|
{% if comments %}
|
||||||
{% for comment in comments %}
|
{% for comment in comments %}
|
||||||
<div class="panel panel-default">
|
<div class="card card-default">
|
||||||
<div class="panel-heading" role="tab" id="">
|
<div class="card-header" role="tab" id="">
|
||||||
<div class="panel-title">
|
<div class="card-title mb-0 clearfix">
|
||||||
<div class="pull-right">{{ comment.created }}</div>
|
<div class="float-md-end">{{ comment.created }}</div>
|
||||||
<div class="pull-left">{% if comment.user.profile.main_character %}{{ comment.user.profile.main_character }}{% else %}{{ comment.user }}{% endif %}</div>
|
<div class="float-md-start">{% if comment.user.profile.main_character %}{{ comment.user.profile.main_character }}{% else %}{{ comment.user }}{% endif %}</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">{{ comment.text|linebreaks }}</div>
|
<div class="card-body">{{ comment.text|linebreaks }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% translate "No comments" %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.hrapplications.add_applicationcomment %}
|
{% if perms.hrapplications.add_applicationcomment %}
|
||||||
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
<div class="modal fade" id="modal-hr-comment" tabindex="-1" aria-labelledby="modalHrComment" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
<div class="modal-title fs-5" id="modalHrCommentLabel">
|
||||||
<span aria-hidden="true">×</span><span class="sr-only">{% translate "Close" %}</span>
|
{% translate "Add Comment" %}
|
||||||
</button>
|
|
||||||
<h4 class="modal-title" id="myModalLabel">{% translate "Add Comment" %}</h4>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Close' %}"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form class="form-signin" role="form" action="" method="POST">
|
<form class="form-signin" role="form" action="" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ comment_form|bootstrap }}
|
|
||||||
<br>
|
{% bootstrap_form comment_form %}
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Add Comment" %}</button>
|
|
||||||
|
<div class="form-group mt-3 clearfix">
|
||||||
|
{% translate "Add comment" as button_text %}
|
||||||
|
{% bootstrap_button button_class="btn btn-primary" content=button_text name="addComment" id="addComment" %}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
allianceauth/locale/cs/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/cs/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2776
allianceauth/locale/cs/LC_MESSAGES/django.po
Normal file
2776
allianceauth/locale/cs/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
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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user