Compare commits

...

178 Commits

Author SHA1 Message Date
Ariel Rin
12383d79c8 Version Bump 3.6.0 2023-08-01 21:37:25 +10:00
Ariel Rin
56e2875650 Merge branch 'pre-commit-update' into 'master'
`pre-commit` Update and File Permission Fixes (redone)

See merge request allianceauth/allianceauth!1523
2023-08-01 11:22:37 +00:00
Peter Pfeufer
d0118e6c0b [FIX] File permissions 2023-08-01 12:55:19 +02:00
Peter Pfeufer
7075ccdf7a [CHANGE] Django Upgrade checks applied 2023-08-01 12:52:05 +02:00
Ariel Rin
b2d540c010 Merge branch 'add-public-routes-feature' into 'master'
Add public routes feature

See merge request allianceauth/allianceauth!1514
2023-08-01 10:20:14 +00:00
Erik Kalkoken
7cb7e2c77b Add public routes feature 2023-08-01 10:20:13 +00:00
Ariel Rin
5d6a4ab1a9 Merge branch 'feature-show-running-tasks' into 'master'
Show running tasks on dashboard

See merge request allianceauth/allianceauth!1515
2023-08-01 10:15:43 +00:00
Erik Kalkoken
1122d617bd Show running tasks on dashboard 2023-08-01 10:15:42 +00:00
Ariel Rin
ef33501e45 Merge branch 'proper-favicon-support' into 'master'
Proper favicon support

See merge request allianceauth/allianceauth!1520
2023-08-01 10:14:21 +00:00
Ariel Rin
08fd86db8f Merge branch 'migrate-pep-621' into 'master'
Migrate to PEP 621

See merge request allianceauth/allianceauth!1513
2023-07-25 09:32:29 +00:00
Erik Kalkoken
c4193c15fc Migrate to PEP 621 2023-07-25 09:32:28 +00:00
Ariel Rin
903074080e Merge branch 'unknown_discord_group_patch' into 'master'
Discord: Don't fail on unknown groups, simply remove them.

See merge request allianceauth/allianceauth!1504
2023-07-25 09:27:19 +00:00
Ariel Rin
3046a26a02 Merge branch 'AA-Timer-Absolute' into 'master'
Adding Absolute Timers to base timerboard

See merge request allianceauth/allianceauth!1518
2023-07-25 09:26:10 +00:00
Hamish W
951c4135c2 Adding Absolute Timers to base timerboard 2023-07-25 09:26:10 +00:00
Ariel Rin
b256a0c5e1 Merge branch 'next-params' into 'master'
Encode Next Param for Login Redirection

See merge request allianceauth/allianceauth!1519
2023-07-25 09:02:31 +00:00
Ariel Rin
212b9b0f60 Merge branch 'stringify-crontab-arguments' into 'master'
[FIX] `crontab` arguments are of type `string`, not `int`

See merge request allianceauth/allianceauth!1517
2023-07-25 08:55:25 +00:00
Peter Pfeufer
fc29d7e80d [ADD] All modern favicon versions generated by realfavicongenerator.net 2023-07-19 12:10:02 +02:00
Peter Pfeufer
ec536c66a0 [ADD] Favicon redirect to Nginx docs 2023-07-19 11:40:45 +02:00
Peter Pfeufer
749ece45e2 [ADD] Favicon redirect to Apache2 docs 2023-07-19 11:39:04 +02:00
Peter Pfeufer
b04c8873d0 [ADD] Directive for default favicon 2023-07-19 11:37:07 +02:00
Aaron Kable
9a77175bf3 Allow get params from next at login 2023-07-17 10:25:36 +08:00
Peter Pfeufer
5d4c7b9030 [FIX] crontab arguments here as well 2023-07-14 19:22:29 +02:00
Aaron Kable
5f80259d57 fix test 2023-07-13 20:28:35 +08:00
Peter Pfeufer
dcd6bd1b36 [FIX] crontab arguments are of type string, not int 2023-07-12 13:11:18 +02:00
Ariel Rin
6f4dffe930 Version Bump 3.5.1 2023-07-11 22:38:26 +10:00
Ariel Rin
56d70e6c74 Add pkg-config to dockerfile 2023-07-11 22:36:15 +10:00
Ariel Rin
5e14ea4573 Version Bump 3.5.0 2023-07-11 13:01:50 +10:00
Ariel Rin
c743eca0f7 Merge branch 'fix-use-of-deprecated-function-calls' into 'master'
[FIX] Use of deprecated `logger.warn()` function calls

See merge request allianceauth/allianceauth!1516
2023-07-11 02:36:59 +00:00
Ariel Rin
2002f24178 Merge branch 'limit-django-registration' into 'master'
[COMPATIBILITY] Limit `django-registration` to <3.4

See merge request allianceauth/allianceauth!1512
2023-07-07 23:57:30 +00:00
Ariel Rin
6412aedf53 Merge branch 'add-docs-for-external-link' into 'master'
Add customization example to docs

See merge request allianceauth/allianceauth!1511
2023-07-07 23:56:07 +00:00
Erik Kalkoken
939df08b95 Add customization example to docs 2023-07-07 23:56:07 +00:00
Ariel Rin
d8506aa753 Merge branch 'pkg-config' into 'master'
add pkg-config to our system package dependencies, for mysqlclient 2.2.0

Closes #1376

See merge request allianceauth/allianceauth!1509
2023-07-07 23:54:41 +00:00
Ariel Rin
3f2cdac658 Merge branch 'devdocs-1' into 'master'
Add pkg-config install to dev setup and expand python downgrade note.

See merge request allianceauth/allianceauth!1510
2023-07-07 23:54:39 +00:00
Peter Pfeufer
d57ab01ff3 [FIX] Use of deprecated logger.warn() function calls 2023-07-07 17:51:55 +02:00
Peter Pfeufer
91b62bbe9d [COMPATIBILITY] Limit django-registration to <3.4 2023-07-04 12:51:12 +02:00
Max Tsero
557a52e3c8 Add pkg-config install to dev setup and expand python downgrade note. 2023-07-02 10:39:23 +00:00
Ariel Rin
f8fefd92a5 add pkg-config to our system package dependencies, for mysqlclient 2.2.0 2023-06-27 11:08:59 +10:00
Ariel Rin
f2c43ee921 Merge branch 'add-docs-app-unintall' into 'master'
Improve documentation for app removal

See merge request allianceauth/allianceauth!1508
2023-06-20 14:34:32 +00:00
Erik Kalkoken
99945b0146 Improve documentation for app removal 2023-06-20 14:34:32 +00:00
Ariel Rin
abb9dc4db6 Merge branch 'master' into 'master'
Add utf8mb4 unicode option to mysql config in local.py

See merge request allianceauth/allianceauth!1503
2023-06-03 07:04:52 +00:00
Valiantiam
eba5b80cde Add utf8mb4 unicode option to mysql config in local.py 2023-06-03 07:04:52 +00:00
Ariel Rin
5b39c887a5 Merge branch 'master' into 'master'
Add Grafana datasoure and dashboard provisioning

See merge request allianceauth/allianceauth!1501
2023-06-03 05:06:23 +00:00
Ariel Rin
183363e789 Merge branch 'add-ukrainian-to-userprofile-language-selection' into 'master'
[ADDED] Ukrainian to `UserProfile.LANGUAGE_CHOICES`

See merge request allianceauth/allianceauth!1505
2023-06-03 05:04:34 +00:00
Ariel Rin
d8704f4d8f Merge branch 'add-ipv6-to-nginx-config' into 'master'
[ADDED] IPv6 to Nginx config

See merge request allianceauth/allianceauth!1506
2023-06-03 05:03:17 +00:00
Peter Pfeufer
165ee44a63 [ADDED] IPv6 to Nginx config
IPv6 is almost 25 years old, time to add it to our config …
2023-05-31 09:29:04 +02:00
Peter Pfeufer
e8f508cecb [CHANGE] Switched to more modern models.TextChoices class for languages 2023-05-28 18:31:50 +02:00
Peter Pfeufer
3044f18900 [ADDED] Ukrainian to UserProfile.LANGUAGE_CHOICES 2023-05-28 17:38:51 +02:00
Aaron Kable
1cae20fe5f Dont fail on unknown groups, simply remove them. 2023-05-19 05:00:39 +08:00
MillerUk
79637020f3 add grafana datasoure and dashboard provisioning 2023-05-16 22:14:11 +00:00
MillerUk
2d34422e2d add grafana datasoure and dashboard provisioning 2023-04-30 20:32:40 +00:00
Ariel Rin
6b932b1188 Version Bump 3.4.0 2023-04-25 20:57:26 +10:00
Ariel Rin
f62153c746 Bump grafana minor for new installs 2023-04-25 20:38:48 +10:00
Ariel Rin
88216c3f81 Merge branch 'improve-dependency-bounds' into 'master'
Reduce upper bound version constraints

See merge request allianceauth/allianceauth!1498
2023-04-25 10:07:06 +00:00
Erik Kalkoken
dc983c31e3 Reduce upper bound version constraints 2023-04-25 10:07:06 +00:00
Ariel Rin
4204c44bde Merge branch 'transifex' into 'master'
Update from Transifex

See merge request allianceauth/allianceauth!1497
2023-04-25 10:05:38 +00:00
Ariel Rin
8da0122d17 Update from Transifex 2023-04-25 10:05:38 +00:00
Ariel Rin
c9fcf6e6bf Merge branch 'dev-team-update' into 'master'
[Change] Dev Team updated in readme

See merge request allianceauth/allianceauth!1499
2023-04-25 10:05:23 +00:00
Peter Pfeufer
36866cc59b [ADDED] snipere4agle1 to the list of active developers 2023-04-24 09:56:40 +02:00
Peter Pfeufer
298bdd98ed [Change] Dev Team updated in readme 2023-04-24 09:50:47 +02:00
Ariel Rin
819018748d Merge branch 'fix-dev-docs-mysql' into 'master'
Fix mysql Django config for developers

See merge request allianceauth/allianceauth!1483
2023-04-12 03:40:50 +00:00
Erik Kalkoken
32e8e0fdd0 Fix mysql Django config for developers 2023-04-12 03:40:50 +00:00
Ariel Rin
7625060a12 Merge branch 'improve-installation-docs' into 'master'
Improve installation guide database section for Ubuntu

See merge request allianceauth/allianceauth!1494
2023-04-12 01:52:54 +00:00
Erik Kalkoken
672cb13bfe Improve installation guide database section for Ubuntu 2023-04-12 01:52:54 +00:00
Ariel Rin
7170f75b89 Merge branch 'add-docs-copy-button' into 'master'
Add copy button to  bash snippets in docs

See merge request allianceauth/allianceauth!1493
2023-04-12 01:47:43 +00:00
Erik Kalkoken
8f60c7a00a Add copy button to bash snippets in docs 2023-04-12 01:47:43 +00:00
Ariel Rin
34ae6e402c Merge branch 'tsTests' into 'master'
Fix TS Tests when testing against a real MySQL backend

See merge request allianceauth/allianceauth!1495
2023-04-12 01:42:48 +00:00
Aaron Kable
0905e48994 Merge remote-tracking branch 'origin/master' into tsTests 2023-04-10 19:49:33 +08:00
Aaron Kable
02fcf7d500 fix TS Tests with mySql 2023-04-10 19:45:28 +08:00
Ariel Rin
8d8da50946 Merge branch '1481-alt' into 'master'
!1481 - Alternate

See merge request allianceauth/allianceauth!1489
2023-04-04 01:47:27 +00:00
Ariel Rin
c1499d173f !1481 - Alternate 2023-04-04 01:47:26 +00:00
Ariel Rin
b149baa4e5 Merge branch 'combine-imports' into 'master'
Combining imports

See merge request allianceauth/allianceauth!1478
2023-02-13 03:21:24 +00:00
Ariel Rin
4807c69b5e Merge branch 'isort' into 'master'
Standardise isort

See merge request allianceauth/allianceauth!1485
2023-02-13 03:11:32 +00:00
Ariel Rin
ebefa0e307 Standardise isort 2023-02-13 03:11:32 +00:00
Ariel Rin
468e7433f9 Merge branch 'py3.11' into 'master'
Add Python 3.11 Support

See merge request allianceauth/allianceauth!1487
2023-02-13 03:06:03 +00:00
Ariel Rin
3ca313f907 Add Python 3.11 Support 2023-02-13 03:06:03 +00:00
Ariel Rin
820065fc04 Merge branch 'update-install-docu' into 'master'
Improve installation guide

See merge request allianceauth/allianceauth!1488
2023-02-13 03:05:41 +00:00
Erik Kalkoken
3eddeefe28 Improve installation guide 2023-02-13 03:05:41 +00:00
Ariel Rin
82d7d7e3bf Merge branch 'arielcode' into 'master'
Extra < in bundle

See merge request allianceauth/allianceauth!1486
2023-02-02 03:27:34 +00:00
Ariel Rin
93194b4f2d pesky < 2023-01-25 16:17:29 +10:00
Ariel Rin
fa335253d3 Merge branch 'pypy' into 'master'
Add pypy testing

See merge request allianceauth/allianceauth!1474
2023-01-25 05:00:16 +00:00
Ariel Rin
d1af9416b3 Add pypy testing 2023-01-25 05:00:16 +00:00
Ariel Rin
f4ac2ea400 Merge branch 'faster-commands' into 'master'
Faster management commands

See merge request allianceauth/allianceauth!1484
2023-01-25 03:25:47 +00:00
Erik Kalkoken
31c1f8bb7d Faster management commands 2023-01-25 03:25:47 +00:00
Ariel Rin
57f7178f1e Merge branch 'docker_docs' into 'master'
Update Docker Docs and fix custom.dockerfile

See merge request allianceauth/allianceauth!1477
2022-12-31 09:37:54 +00:00
Ariel Rin
17d4a4c415 Merge branch 'change-packaging-dependency' into 'master'
Don't limit `packaging` to versions below 22.0

See merge request allianceauth/allianceauth!1479
2022-12-20 10:01:50 +00:00
Peter Pfeufer
18ce433fa0 Don't limit packaging to versions below 22.0 2022-12-20 10:01:50 +00:00
Ariel Rin
1eadb1d934 Merge branch 'fix-tox-4-issue' into 'master'
Adopt docs section to work with tox 4

See merge request allianceauth/allianceauth!1480
2022-12-20 08:38:59 +00:00
Erik Kalkoken
59a8f8a967 Adopt docs section to work with tox 4 2022-12-20 08:38:59 +00:00
Peter Pfeufer
2dc07b5519 Combining imports 2022-12-09 23:48:17 +01:00
Aaron Kable
3454520dfe update docker docs and fix custom.dockerfile 2022-11-24 21:56:33 +08:00
Ariel Rin
7a195d4158 Merge branch 'docs' into 'master'
Tuning Docs - Redis Compression

See merge request allianceauth/allianceauth!1475
2022-11-03 10:33:25 +00:00
Ariel Rin
b73072dec0 Tuning Docs - Redis Compression 2022-11-03 10:33:24 +00:00
Ariel Rin
1ca5e38bd9 Merge branch 'fix-docker-tag' into 'master'
fix missing v in tag

See merge request allianceauth/allianceauth!1476
2022-11-03 10:32:29 +00:00
Matteo Ghia
ecb737c6a5 fix missing v in tag 2022-11-01 13:45:44 +01:00
Ariel Rin
7063f53cdf Merge branch 'coverage' into 'master'
Adding Coverage % to MRs

See merge request allianceauth/allianceauth!1473
2022-10-25 10:10:33 +00:00
Ariel Rin
017424b9d4 Adding Coverage % to MRs 2022-10-25 10:10:33 +00:00
Ariel Rin
f6c26cf2ec Merge branch 'py3.11' into 'master'
Python 3.11 Stable Tests

See merge request allianceauth/allianceauth!1471
2022-10-25 02:51:12 +00:00
Ariel Rin
9a422bd4ca Python 3.11 Stable Tests 2022-10-25 02:51:12 +00:00
Ariel Rin
47fec23f2e cap django for docs 2022-10-23 16:42:16 +10:00
Ariel Rin
399ef1917d Version Bump 3.3.0 2022-10-14 21:46:28 +10:00
Ariel Rin
9db443ba54 Merge branch 'tokens-and-alts' into 'master'
CCP SSO Issues, Mitigations

See merge request allianceauth/allianceauth!1472
2022-10-14 11:44:18 +00:00
Ariel Rin
0f2f5ea0ba nowrap to stop buttons moving around 2022-10-14 20:21:07 +10:00
Ariel Rin
1f781c5037 datatables statesave 2022-10-12 20:52:59 +10:00
Ariel Rin
36dedfcbd2 add disclaimer 2022-10-12 20:51:36 +10:00
Ariel Rin
13a05606fb Scopes Typo 2022-10-12 20:29:42 +10:00
Ariel Rin
90ad7790e1 rename revoke to delete to be clearer 2022-10-12 20:22:20 +10:00
Ariel Rin
6b8341ab5a Add FA icon to user dropdown 2022-10-12 20:21:27 +10:00
Ariel Rin
d15f42b3fd Merge branch 'tokens-and-alts' of https://gitlab.com/aaronkable/allianceauth into tokens-and-alts 2022-10-12 20:07:49 +10:00
Aaron Kable
cc60b26f5a add token management link to user dropdown 2022-10-12 17:57:12 +08:00
Aaron Kable
36ff0af993 Add token management and restrict logins to mains only 2022-10-12 17:50:41 +08:00
Aaron Kable
f17c94a9e1 Add token management and restrict logins to mains only 2022-10-12 17:49:28 +08:00
Ariel Rin
7e3ba476f3 Merge branch 'docs' into 'master'
Discord Credential Clarification

See merge request allianceauth/allianceauth!1470
2022-10-12 08:07:02 +00:00
Ariel Rin
dd1313a2a9 Merge branch 'remove-unnecessary-lambda-statement' into 'master'
[REMOVED] Unnecessary `lambda` statement

See merge request allianceauth/allianceauth!1465
2022-10-09 08:17:46 +00:00
Ariel Rin
763003bd7d Clarify new discord developers layout 2022-10-09 17:41:41 +10:00
Ariel Rin
f3217443dd Merge branch 'remove-celery-backend-from-docker' into 'master'
[REMOVED] Celery backend from docker config

See merge request allianceauth/allianceauth!1466
2022-10-09 07:16:04 +00:00
Ariel Rin
a713ae1914 Merge branch 'fix-default-perms-for-static-files' into 'master'
[FIX] Default permissions for static files

See merge request allianceauth/allianceauth!1469
2022-10-09 06:15:18 +00:00
Peter Pfeufer
5815bac0df [FIX] Default permissions for static files
A little fallacy on my end in the docs.

655 is enough for files in those directories, but the directories themselves need to be traversal, so 755 for the directories ...
2022-09-22 19:54:58 +02:00
Ariel Rin
6154d2c2e7 Merge branch 'better-exclude-regex' into 'master'
[CHANGE] Better regex for exclusion in pre-commit

See merge request allianceauth/allianceauth!1468
2022-09-18 08:26:22 +00:00
Peter Pfeufer
b34661b35d [CHANGE] Better regex for exclusion in pre-commit 2022-09-18 08:26:22 +00:00
Ariel Rin
a9a7e03b80 Merge branch 'capntack-master-patch-07678' into 'master'
Update switch_to_non_root.md

See merge request allianceauth/allianceauth!1467
2022-09-16 14:48:08 +00:00
Tack
23c797ef64 Update switch_to_non_root.md
chmod requires "-R" to be Recursive, not "-r"
2022-09-14 16:51:10 +00:00
Ariel Rin
da102618a0 Version Bump 3.2.0 2022-09-14 23:26:02 +10:00
Ariel Rin
51ee281b14 Update from Transifex 2022-09-14 23:20:08 +10:00
Peter Pfeufer
9133232c20 [REMOVED] Celery backend from docker config 2022-09-14 13:06:30 +02:00
Peter Pfeufer
9cbabee126 [CHANGE] Language names always start with a capital letter 2022-09-13 21:00:31 +02:00
Peter Pfeufer
4026523a2e [REMOVED] Unnecessary lambda statement
The `lambda` statement in `base.py` is unnecessary and has no effect.

```py
ugettext = lambda s: s
LANGUAGES = (
    ("en", ugettext("English")),
    ("de", ugettext("German")),
    ("es", ugettext("Spanish")),
    ("zh-hans", ugettext("Chinese Simplified")),
    ("ru", ugettext("Russian")),
    ("ko", ugettext("Korean")),
    ("fr", ugettext("French")),
    ("ja", ugettext("Japanese")),
    ("it", ugettext("Italian")),
)
```

In this case `ugettext = lambda s: s` is pretty much the same as:
```py
def ugettext(s):
    return s
```
And would simply return the string the function receives as parameter.

So we can omit this completely and simplify the `LANGUAGES` list to:

```py
LANGUAGES = (
    ("en", "English"),
    ("de", "German"),
    ("es", "Spanish"),
    ("zh-hans", "Chinese Simplified"),
    ("ru", "Russian"),
    ("ko", "Korean"),
    ("fr", "French"),
    ("ja", "Japanese"),
    ("it", "Italian"),
)
```
2022-09-13 20:59:14 +02:00
Ariel Rin
7fbf96623b Update from Transifex 2022-09-12 11:41:08 +10:00
Ariel Rin
273bda173e Merge branch '1088-qol-improve-name-handling-for-smf' into 'master'
Displayed names for SMF

Closes #1088

See merge request allianceauth/allianceauth!1459
2022-09-11 13:53:25 +00:00
Ariel Rin
7bd5838ea1 Merge branch 'master' into 'master'
minor fixes to dev environment setup

See merge request allianceauth/allianceauth!1458
2022-09-11 13:51:42 +00:00
Ariel Rin
b232d9ab17 Merge branch 'use-SITE_URL-in-templates' into 'master'
[ADDED] `SITE_URL` usage in templates

See merge request allianceauth/allianceauth!1456
2022-09-11 13:51:21 +00:00
Ariel Rin
a11b870664 Merge branch 'switch-to-non-root' into 'master'
Add docs for switching to a non-root installation

See merge request allianceauth/allianceauth!1463
2022-09-11 13:48:44 +00:00
Erik Kalkoken
a27aae5d1c Add docs for switching to a non-root installation 2022-09-11 13:48:43 +00:00
Ariel Rin
117ef63d90 Merge branch 'trailing-slash-it' into 'master'
[ADDED] Missing trailing slashes to URLs

See merge request allianceauth/allianceauth!1457
2022-09-11 13:45:50 +00:00
Ariel Rin
1bde3d5672 Merge branch 'esi-related-links-on-login' into 'master'
[ADDED] ESI related links in login box

See merge request allianceauth/allianceauth!1455
2022-09-11 13:44:55 +00:00
Ariel Rin
d2355b1ec8 Merge branch 'none-is-not-an-alliance' into 'master'
[CHANGE] None is not an alliance name, so don't show `None`

See merge request allianceauth/allianceauth!1460
2022-09-11 13:44:34 +00:00
Ariel Rin
191d474a8e Merge branch 'task-queue-top-margin-fix' into 'master'
[FIX] Top margin on celery task percentage bar

See merge request allianceauth/allianceauth!1461
2022-09-11 13:44:11 +00:00
Peter Pfeufer
ec9a9733be [FIX] Top margin on celery task percentage bar 2022-09-11 13:44:11 +00:00
Ariel Rin
cf7a8cedf1 Merge branch 'dont-fight-against-bootstrap' into 'master'
[FIX] Use proper markup instead of fighting against Bootstrap

See merge request allianceauth/allianceauth!1462
2022-09-11 13:43:38 +00:00
Peter Pfeufer
18cbb994d5 [FIX] Use proper markup instead of fighting against Bootstrap 2022-09-08 11:22:40 +02:00
Peter Pfeufer
663388a0c2 [CHANGE] None is not an alliance name, so don't show None 2022-09-08 00:19:41 +02:00
Peter Pfeufer
7a943591ec [ADDED] Migration to update existing user's displayed names 2022-09-07 23:01:38 +02:00
Peter Pfeufer
cd189927fe [ADDED] Update displayed name when main is changed 2022-09-07 23:01:07 +02:00
Peter Pfeufer
8772349309 [ADDED] Main character name as displayed name on SMF service activation 2022-09-07 21:14:18 +02:00
Arc Tiru
cf20100cb5 minor fixes to dev environment setup 2022-09-07 11:00:38 -07:00
Peter Pfeufer
9b9c2ddc04 [ADDED] SITE_URLto test settings 2022-09-07 15:31:22 +02:00
Peter Pfeufer
34839e8344 [ADDED] Trailing slahes to URLs
URLs in AA usually use a trailing slash, so this was added to the ones that were missing it.
2022-09-07 15:18:06 +02:00
Peter Pfeufer
89ef4f4cbc [ADDED] SITE_URL usage in templates
Instead of {{ request.scheme }}://{{request.get_host}}`
2022-09-07 15:04:25 +02:00
Peter Pfeufer
2cc7f46aae [ADDED] ESI related links in login box 2022-09-07 14:45:04 +02:00
Ariel Rin
8d255fb720 Merge branch '834-check-if-character-is-online' into 'master'
[FIX] Check if character is online before accepting FAT click

Closes #834

See merge request allianceauth/allianceauth!1451
2022-09-07 06:29:19 +00:00
Ariel Rin
67cf68ad87 Merge branch 'no_unique_names' into 'master'
Corp and Alliance names are not unique

Closes #1317

See merge request allianceauth/allianceauth!1452
2022-09-07 06:28:01 +00:00
colcrunch
db1971d4c2 Corp and Alliance names are not unique 2022-09-07 06:28:01 +00:00
Ariel Rin
63c1521cba Merge branch 'add-missing-doctype' into 'master'
[FIX] Missing DOCTYPE and padding

See merge request allianceauth/allianceauth!1449
2022-09-07 06:25:45 +00:00
Ariel Rin
ba7ef11505 Merge branch 'fix-deprecated-translation-tags' into 'master'
[FIX] Deprecated `{% blocktrans %}` tags to `{% blocktranslate %}`

See merge request allianceauth/allianceauth!1454
2022-09-07 06:22:55 +00:00
Ariel Rin
d2e494b9be Merge branch 'modernize-css' into 'master'
CSS modernized

See merge request allianceauth/allianceauth!1448
2022-09-07 06:21:15 +00:00
Ariel Rin
98bab0b180 Merge branch 'bundle-all-the-things' into 'master'
Bundle the remaining static files

See merge request allianceauth/allianceauth!1447
2022-09-07 06:20:41 +00:00
Ariel Rin
c4efb2a11f Merge branch 'aa3-docker-fixes' into 'master'
[CHANGE] Docker updates for AA3

Closes #1352

See merge request allianceauth/allianceauth!1453
2022-09-07 06:19:47 +00:00
Ariel Rin
94e4895f29 Merge branch 'clarify-url-format' into 'master'
[MISC] Clarify URL format

See merge request allianceauth/allianceauth!1450
2022-09-07 06:18:43 +00:00
Peter Pfeufer
70eb1b5b50 [CHANGE] Deprecated {% blocktrans %} tags to {% blocktranslate %} 2022-09-06 23:48:36 +02:00
Peter Pfeufer
e247a94db3 [CHANGE] Updates for AA3
- SITE_URL introduced
- Redis cache fixed (#1352)
- Using the same type of quotes, not wildly mixing them
2022-09-06 22:50:49 +02:00
Peter Pfeufer
714431c932 [FIX] Check if character is online before accepting FAT click
Fixes #834
2022-08-07 12:08:57 +02:00
Peter Pfeufer
b026277ab0 [MISC] Clarify URL format
Seems his is causing confusion, so add a note that the URL should be without a trailing slash
2022-08-07 02:01:26 +02:00
Peter Pfeufer
11855f0b54 [FIX] Missing DOCTYPE and padding 2022-08-05 19:51:50 +02:00
Ariel Rin
635fbfe2c8 Version Bump v3.1.1 2022-08-05 21:35:51 +10:00
Ariel Rin
b10233daf0 Merge branch 'transifex' of https://gitlab.com/allianceauth/allianceauth 2022-08-05 21:23:58 +10:00
Ariel Rin
1aa3187491 Cap Django to 4.0.x 2022-08-05 21:23:40 +10:00
Ariel Rin
59f17a88f0 Update from Transifex 2022-08-05 21:20:32 +10:00
Peter Pfeufer
75db3195d4 CSS modernized 2022-08-02 23:56:47 +02:00
Peter Pfeufer
afe3fea757 Bundle the remaining static files
They are used in some template overrides. To prevent missing JS or CSS in the future in those template overrides, it's a good idea to provide HTML templates for these static files.
2022-08-01 14:15:55 +02:00
Ariel Rin
1072c00a28 Version Bump 3.1.0 2022-08-01 20:44:07 +10:00
Ariel Rin
b221c1ce24 highlight the settings file configration step 2022-08-01 19:28:28 +10:00
Ariel Rin
1617c775ee note SITE_URL in settings file config, plus explicitly other things from install guide 2022-08-01 19:19:01 +10:00
Ariel Rin
cc88a02001 Merge branch 'csrf-trusted-origins' into 'master'
Add `CSRF_TRUSTED_ORIGINS` to `local.py`

See merge request allianceauth/allianceauth!1446
2022-08-01 08:59:15 +00:00
Ariel Rin
ae5d0f4a2f Merge branch 'setuptools-test' into 'master'
Re-adding MANIFEST.in

See merge request allianceauth/allianceauth!1445
2022-08-01 08:53:11 +00:00
Peter Pfeufer
067e2c424e Added new variable SITE_URL to DISCORD_CALLBACK_URL 2022-07-31 17:02:02 +02:00
Peter Pfeufer
d64675a3b0 Added new variable SITE_URL to ESI_SSO_CALLBACK_URL 2022-07-31 16:57:37 +02:00
Peter Pfeufer
17a6b3225e Add CSRF_TRUSTED_ORIGINS to local.py
Addresses #1350
2022-07-31 16:55:34 +02:00
Peter Pfeufer
b83f591dc2 Re-adding MANIFEST.in 2022-07-31 01:01:43 +02:00
Ariel Rin
74651dd30a Update from Transifex 2022-07-30 18:21:20 +10:00
194 changed files with 10663 additions and 6376 deletions

4
.gitignore vendored
View File

@@ -69,11 +69,7 @@ celerybeat-schedule
#gitlab configs
.gitlab/
#transifex
.tx/
#other
.flake8
.pylintrc
Makefile
.isort.cfg

View File

@@ -25,7 +25,7 @@ before_script:
pre-commit-check:
<<: *only-default
stage: pre-commit
image: python:3.8-bullseye
image: python:3.10-bullseye
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
@@ -89,7 +89,7 @@ test-3.10-core:
test-3.11-core:
<<: *only-default
image: python:3.11-rc-bullseye
image: python:3.11-bullseye
script:
- tox -e py311-core
artifacts:
@@ -98,6 +98,18 @@ test-3.11-core:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-pvpy-core:
<<: *only-default
image: pypy:3.9-bullseye
script:
- tox -e pypy-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.8-all:
@@ -138,7 +150,7 @@ test-3.10-all:
test-3.11-all:
<<: *only-default
image: python:3.11-rc-bullseye
image: python:3.11-bullseye
script:
- tox -e py311-all
artifacts:
@@ -147,6 +159,19 @@ test-3.11-all:
coverage_report:
coverage_format: cobertura
path: coverage.xml
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
test-pvpy-all:
<<: *only-default
image: pypy:3.9-bullseye
script:
- tox -e pypy-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
build-test:

View File

@@ -5,35 +5,75 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-case-conflict
- id: check-json
- id: check-xml
# Identify invalid files
- id: check-ast
- id: check-yaml
- id: fix-byte-order-marker
- id: trailing-whitespace
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
- id: end-of-file-fixer
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
- id: mixed-line-ending
args: [ '--fix=lf' ]
- id: check-json
- id: check-toml
- id: check-xml
# git checks
- id: check-merge-conflict
- id: check-added-large-files
args: [ --maxkb=1000 ]
- id: detect-private-key
- id: check-case-conflict
# Python checks
# - id: check-docstring-first
- id: debug-statements
# - id: requirements-txt-fixer
- id: fix-encoding-pragma
args: [ '--remove' ]
args: [ --remove ]
- id: fix-byte-order-marker
# General quality checks
- id: mixed-line-ending
args: [ --fix=lf ]
- id: trailing-whitespace
args: [ --markdown-linebreak-ext=md ]
exclude: |
(?x)(
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json
)
- id: check-executables-have-shebangs
- id: end-of-file-fixer
exclude: |
(?x)(
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.4.0
rev: 2.7.2
hooks:
- id: editorconfig-checker
exclude: ^(LICENSE|allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))
exclude: |
(?x)(
LICENSE|
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [ --target-version=4.0 ]
- repo: https://github.com/asottile/pyupgrade
rev: v2.34.0
rev: v3.10.1
hooks:
- id: pyupgrade
args: [ --py38-plus ]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.20.1
hooks:
- id: setup-cfg-fmt

10
.tx/config Normal file
View File

@@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
lang_map = zh-Hans: zh_Hans
[o:alliance-auth:p:alliance-auth:r:django-po]
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO
minimum_perc = 0

View File

@@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
lang_map = zh-Hans:zh_Hans
[alliance-auth.django-po]
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
minimum_perc = 0
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO

6
README.md Executable file → Normal file
View File

@@ -36,7 +36,7 @@ Main features:
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: localization
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
@@ -56,13 +56,15 @@ Here is an example of the Alliance Auth web site with some plug-ins apps and ser
- [Aaron Kable](https://gitlab.com/aaronkable/)
- [Ariel Rin](https://gitlab.com/soratidus999/)
- [Basraah](https://gitlab.com/basraah/)
- [Col Crunch](https://gitlab.com/colcrunch/)
- [Erik Kalkoken](https://gitlab.com/ErikKalkoken/)
- [Rounon Dax](https://gitlab.com/ppfeufer)
- [snipereagle1](https://gitlab.com/mckernanin)
### Former Developers
- [Adarnof](https://gitlab.com/adarnof/)
- [Basraah](https://gitlab.com/basraah/)
### Beta Testers / Bug Fixers

View File

@@ -1,7 +1,11 @@
"""An auth system for EVE Online to help in-game organizations
manage online service access.
"""
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '3.0.0'
__version__ = '3.6.0'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}'

View File

@@ -8,13 +8,13 @@ from uuid import uuid4
class AnalyticsIdentifier(models.Model):
identifier = models.UUIDField(default=uuid4,
editable=False)
editable=False)
def save(self, *args, **kwargs):
if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object
raise ValidationError('There is can be only one \
AnalyticsIdentifier instance')
AnalyticsIdentifier instance')
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super().save(*args, **kwargs)

View File

@@ -2,6 +2,7 @@ import logging
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Permission
from django.contrib import messages
from .models import UserProfile, CharacterOwnership, OwnershipRecord
@@ -37,7 +38,13 @@ class StateBackend(ModelBackend):
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
if ownership.owner_hash == token.character_owner_hash:
logger.debug(f'Authenticating {ownership.user} by ownership of character {token.character_name}')
return ownership.user
if ownership.user.profile.main_character:
if ownership.user.profile.main_character.character_id == token.character_id:
return ownership.user
else: ## this is an alt, enforce main only.
if request:
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account.")
return None
else:
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
ownership.delete()
@@ -57,13 +64,20 @@ class StateBackend(ModelBackend):
if records.exists():
# we've seen this character owner before. Re-attach to their old user account
user = records[0].user
if user.profile.main_character:
if ownership.user.profile.main_character.character_id != token.character_id:
## this is an alt, enforce main only due to trust issues in SSO.
if request:
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
return None
token.user = user
co = CharacterOwnership.objects.create_by_token(token)
logger.debug(f'Authenticating {user} by matching owner hash record of character {co.character}')
if not user.profile.main_character:
# set this as their main by default if they have none
user.profile.main_character = co.character
user.profile.save()
# set this as their main by default as they have none
user.profile.main_character = co.character
user.profile.save()
return user
logger.debug(f'Unable to authenticate character {token.character_name}. Creating new user.')
return self.create_user(token)

View File

@@ -1,18 +1,28 @@
from django.conf.urls import include
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
from functools import wraps
from django.shortcuts import redirect
from typing import Callable, Iterable, Optional
from django.urls import include
from django.contrib import messages
from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.decorators import login_required
def user_has_main_character(user):
return bool(user.profile.main_character)
def decorate_url_patterns(urls, decorator):
def decorate_url_patterns(
urls, decorator: Callable, excluded_views: Optional[Iterable] = None
):
"""Decorate views given in url patterns except when they are explicitly excluded.
Args:
- urls: Django URL patterns
- decorator: Decorator to be added to each view
- exclude_views: Optional iterable of view names to be excluded
"""
url_list, app_name, namespace = include(urls)
def process_patterns(url_patterns):
@@ -22,6 +32,8 @@ def decorate_url_patterns(urls, decorator):
process_patterns(pattern.url_patterns)
else:
# this is a pattern
if excluded_views and pattern.lookup_str in excluded_views:
return
pattern.callback = decorator(pattern.callback)
process_patterns(url_list)

View File

@@ -1,8 +1,5 @@
from django.conf.urls import include
from allianceauth.authentication import views
from django.urls import re_path
from django.urls import path
from django.urls import include, re_path, path
urlpatterns = [
path('activate/complete/', views.activation_complete, name='registration_activation_complete'),

0
allianceauth/authentication/managers.py Executable file → Normal file
View File

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.0.10 on 2023-05-28 15:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentication", "0020_userprofile_language_userprofile_night_mode"),
]
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"),
],
default="",
max_length=10,
verbose_name="Language",
),
),
]

37
allianceauth/authentication/models.py Executable file → Normal file
View File

@@ -18,13 +18,13 @@ class State(models.Model):
priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.")
member_characters = models.ManyToManyField(EveCharacter, blank=True,
help_text="Characters to which this state is available.")
help_text="Characters to which this state is available.")
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True,
help_text="Corporations to whose members this state is available.")
help_text="Corporations to whose members this state is available.")
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True,
help_text="Alliances to whose members this state is available.")
help_text="Alliances to whose members this state is available.")
member_factions = models.ManyToManyField(EveFactionInfo, blank=True,
help_text="Factions to whose members this state is available.")
help_text="Factions to whose members this state is available.")
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
objects = StateManager()
@@ -63,6 +63,22 @@ class UserProfile(models.Model):
class Meta:
default_permissions = ('change',)
class Language(models.TextChoices):
"""
Choices for UserProfile.language
"""
ENGLISH = 'en', _('English')
GERMAN = 'de', _('German')
SPANISH = 'es', _('Spanish')
CHINESE = 'zh-hans', _('Chinese Simplified')
RUSSIAN = 'ru', _('Russian')
KOREAN = 'ko', _('Korean')
FRENCH = 'fr', _('French')
JAPANESE = 'ja', _('Japanese')
ITALIAN = 'it', _('Italian')
UKRAINIAN = 'uk', _('Ukrainian')
user = models.OneToOneField(
User,
related_name='profile',
@@ -76,20 +92,9 @@ class UserProfile(models.Model):
State,
on_delete=models.SET_DEFAULT,
default=get_guest_state_pk)
LANGUAGE_CHOICES = [
('en', _('English')),
('de', _('German')),
('es', _('Spanish')),
('zh-hans', _('Chinese Simplified')),
('ru', _('Russian')),
('ko', _('Korean')),
('fr', _('French')),
('ja', _('Japanese')),
('it', _('Italian')),
]
language = models.CharField(
_("Language"), max_length=10,
choices=LANGUAGE_CHOICES,
choices=Language.choices,
blank=True,
default='')
night_mode = models.BooleanField(

View File

@@ -6,24 +6,26 @@ CSS for allianceauth admin site
.img-circle {
border-radius: 50%;
}
.column-user_profile_pic {
width: 1px;
white-space: nowrap;
width: 1px;
}
/* tooltip */
.tooltip {
position: relative ;
position: relative;
}
.tooltip:hover::after {
content: attr(data-tooltip) ;
position: absolute ;
top: 1.1em ;
left: 1em ;
min-width: 200px ;
border: 1px #808080 solid ;
padding: 8px ;
color: black ;
background-color: rgb(255, 255, 204) ;
z-index: 1 ;
background-color: rgb(255 255 204);
border: 1px rgb(128 128 128) solid;
color: rgb(0 0 0);
content: attr(data-tooltip);
left: 1em;
min-width: 200px;
padding: 8px;
position: absolute;
top: 1.1em;
z-index: 1;
}

View File

@@ -1,35 +1,44 @@
from collections import namedtuple
"""Counters for Task Statistics."""
import datetime as dt
from typing import NamedTuple, Optional
from .event_series import EventSeries
from .helpers import ItemCounter
"""Global series for counting task events."""
# Global series for counting task events.
succeeded_tasks = EventSeries("SUCCEEDED_TASKS")
retried_tasks = EventSeries("RETRIED_TASKS")
failed_tasks = EventSeries("FAILED_TASKS")
running_tasks = ItemCounter("running_tasks")
_TaskCounts = namedtuple(
"_TaskCounts", ["succeeded", "retried", "failed", "total", "earliest_task", "hours"]
)
class _TaskCounts(NamedTuple):
succeeded: int
retried: int
failed: int
total: int
earliest_task: Optional[dt.datetime]
hours: int
running: int
def dashboard_results(hours: int) -> _TaskCounts:
"""Counts of all task events within the given timeframe."""
"""Counts of all task events within the given time frame."""
def earliest_if_exists(events: EventSeries, earliest: dt.datetime) -> list:
my_earliest = events.first_event(earliest=earliest)
return [my_earliest] if my_earliest else []
earliest = dt.datetime.utcnow() - dt.timedelta(hours=hours)
earliest_events = list()
earliest_events = []
succeeded_count = succeeded_tasks.count(earliest=earliest)
earliest_events += earliest_if_exists(succeeded_tasks, earliest)
retried_count = retried_tasks.count(earliest=earliest)
earliest_events += earliest_if_exists(retried_tasks, earliest)
failed_count = failed_tasks.count(earliest=earliest)
earliest_events += earliest_if_exists(failed_tasks, earliest)
running_count = running_tasks.value()
return _TaskCounts(
succeeded=succeeded_count,
retried=retried_count,
@@ -37,4 +46,5 @@ def dashboard_results(hours: int) -> _TaskCounts:
total=succeeded_count + retried_count + failed_count,
earliest_task=min(earliest_events) if earliest_events else None,
hours=hours,
running=running_count,
)

View File

@@ -1,3 +1,5 @@
"""Event series for Task Statistics."""
import datetime as dt
import logging
from typing import List, Optional
@@ -73,8 +75,8 @@ class EventSeries:
"""
if not event_time:
event_time = dt.datetime.utcnow()
id = self._redis.incr(self._key_counter)
self._redis.zadd(self._key_sorted_set, {id: event_time.timestamp()})
my_id = self._redis.incr(self._key_counter)
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})
def all(self) -> List[dt.datetime]:
"""List of all known events."""
@@ -101,9 +103,9 @@ class EventSeries:
- earliest: Date of first events to count(inclusive), or -infinite if not specified
- latest: Date of last events to count(inclusive), or +infinite if not specified
"""
min = "-inf" if not earliest else earliest.timestamp()
max = "+inf" if not latest else latest.timestamp()
return self._redis.zcount(self._key_sorted_set, min=min, max=max)
minimum = "-inf" if not earliest else earliest.timestamp()
maximum = "+inf" if not latest else latest.timestamp()
return self._redis.zcount(self._key_sorted_set, min=minimum, max=maximum)
def first_event(self, earliest: dt.datetime = None) -> Optional[dt.datetime]:
"""Date/Time of first event. Returns `None` if series has no events.
@@ -111,10 +113,10 @@ class EventSeries:
Args:
- earliest: Date of first events to count(inclusive), or any if not specified
"""
min = "-inf" if not earliest else earliest.timestamp()
minimum = "-inf" if not earliest else earliest.timestamp()
event = self._redis.zrangebyscore(
self._key_sorted_set,
min,
minimum,
"+inf",
withscores=True,
start=0,

View File

@@ -0,0 +1,44 @@
"""Helpers for Task Statistics."""
from typing import Optional
from django.core.cache import cache
class ItemCounter:
"""A process safe item counter."""
CACHE_KEY_BASE = "allianceauth-item-counter"
DEFAULT_CACHE_TIMEOUT = 24 * 3600
def __init__(self, name: str) -> None:
if not name:
raise ValueError("Must define a name")
self._name = str(name)
@property
def _cache_key(self) -> str:
return f"{self.CACHE_KEY_BASE}-{self._name}"
def reset(self, init_value: int = 0):
"""Reset counter to initial value."""
cache.set(self._cache_key, init_value, self.DEFAULT_CACHE_TIMEOUT)
def incr(self, delta: int = 1):
"""Increment counter by delta."""
try:
cache.incr(self._cache_key, delta)
except ValueError:
pass
def decr(self, delta: int = 1):
"""Decrement counter by delta."""
try:
cache.decr(self._cache_key, delta)
except ValueError:
pass
def value(self) -> Optional[int]:
"""Return current value or None if not yet initialized."""
return cache.get(self._cache_key)

View File

@@ -1,14 +1,15 @@
"""Signals for Task Statistics."""
from celery.signals import (
task_failure,
task_internal_error,
task_retry,
task_success,
worker_ready
task_failure, task_internal_error, task_postrun, task_prerun, task_retry,
task_success, worker_ready,
)
from django.conf import settings
from .counters import failed_tasks, retried_tasks, succeeded_tasks
from .counters import (
failed_tasks, retried_tasks, running_tasks, succeeded_tasks,
)
def reset_counters():
@@ -16,9 +17,11 @@ def reset_counters():
succeeded_tasks.clear()
failed_tasks.clear()
retried_tasks.clear()
running_tasks.reset()
def is_enabled() -> bool:
"""Return True if task statistics are enabled, else return False."""
return not bool(
getattr(settings, "ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED", False)
)
@@ -52,3 +55,15 @@ def record_task_failed(*args, **kwargs):
def record_task_internal_error(*args, **kwargs):
if is_enabled():
failed_tasks.add()
@task_prerun.connect
def record_task_prerun(*args, **kwargs):
if is_enabled():
running_tasks.incr()
@task_postrun.connect
def record_task_postrun(*args, **kwargs):
if is_enabled():
running_tasks.decr()

View File

@@ -8,25 +8,31 @@ from allianceauth.authentication.task_statistics.counters import (
succeeded_tasks,
retried_tasks,
failed_tasks,
running_tasks,
)
class TestDashboardResults(TestCase):
def test_should_return_counts_for_given_timeframe_only(self):
def test_should_return_counts_for_given_time_frame_only(self):
# given
earliest_task = now() - dt.timedelta(minutes=15)
succeeded_tasks.clear()
succeeded_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
succeeded_tasks.add(earliest_task)
succeeded_tasks.add()
succeeded_tasks.add()
retried_tasks.clear()
retried_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
retried_tasks.add(now() - dt.timedelta(seconds=30))
retried_tasks.add()
failed_tasks.clear()
failed_tasks.add(now() - dt.timedelta(hours=1, seconds=1))
failed_tasks.add()
running_tasks.reset(8)
# when
results = dashboard_results(hours=1)
# then
@@ -35,12 +41,14 @@ class TestDashboardResults(TestCase):
self.assertEqual(results.failed, 1)
self.assertEqual(results.total, 6)
self.assertEqual(results.earliest_task, earliest_task)
self.assertEqual(results.running, 8)
def test_should_work_with_no_data(self):
# given
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
running_tasks.reset()
# when
results = dashboard_results(hours=1)
# then
@@ -49,3 +57,4 @@ class TestDashboardResults(TestCase):
self.assertEqual(results.failed, 0)
self.assertEqual(results.total, 0)
self.assertIsNone(results.earliest_task)
self.assertEqual(results.running, 0)

View File

@@ -0,0 +1,74 @@
from unittest import TestCase
from allianceauth.authentication.task_statistics.helpers import ItemCounter
COUNTER_NAME = "test-counter"
class TestItemCounter(TestCase):
def test_can_create_counter(self):
# when
counter = ItemCounter(COUNTER_NAME)
# then
self.assertIsInstance(counter, ItemCounter)
def test_can_reset_counter_to_default(self):
# given
counter = ItemCounter(COUNTER_NAME)
# when
counter.reset()
# then
self.assertEqual(counter.value(), 0)
def test_can_reset_counter_to_custom_value(self):
# given
counter = ItemCounter(COUNTER_NAME)
# when
counter.reset(42)
# then
self.assertEqual(counter.value(), 42)
def test_can_increment_counter_by_default(self):
# given
counter = ItemCounter(COUNTER_NAME)
counter.reset(0)
# when
counter.incr()
# then
self.assertEqual(counter.value(), 1)
def test_can_increment_counter_by_custom_value(self):
# given
counter = ItemCounter(COUNTER_NAME)
counter.reset(0)
# when
counter.incr(8)
# then
self.assertEqual(counter.value(), 8)
def test_can_decrement_counter_by_default(self):
# given
counter = ItemCounter(COUNTER_NAME)
counter.reset(9)
# when
counter.decr()
# then
self.assertEqual(counter.value(), 8)
def test_can_decrement_counter_by_custom_value(self):
# given
counter = ItemCounter(COUNTER_NAME)
counter.reset(9)
# when
counter.decr(8)
# then
self.assertEqual(counter.value(), 1)
def test_can_decrement_counter_below_zero(self):
# given
counter = ItemCounter(COUNTER_NAME)
counter.reset(0)
# when
counter.decr(1)
# then
self.assertEqual(counter.value(), -1)

View File

@@ -17,16 +17,17 @@ from allianceauth.eveonline.tasks import update_character
@override_settings(
CELERY_ALWAYS_EAGER=True,ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
CELERY_ALWAYS_EAGER=True, ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
)
class TestTaskSignals(TestCase):
fixtures = ["disable_analytics"]
def test_should_record_successful_task(self):
# given
def setUp(self) -> None:
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
def test_should_record_successful_task(self):
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
@@ -39,10 +40,6 @@ class TestTaskSignals(TestCase):
self.assertEqual(failed_tasks.count(), 0)
def test_should_record_retried_task(self):
# given
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
@@ -55,10 +52,6 @@ class TestTaskSignals(TestCase):
self.assertEqual(retried_tasks.count(), 1)
def test_should_record_failed_task(self):
# given
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"

View File

@@ -14,9 +14,9 @@
<div class="panel panel-primary" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">
{% blocktrans with state=request.user.profile.state %}
{% blocktranslate with state=request.user.profile.state %}
Main Character (State: {{ state }})
{% endblocktrans %}
{% endblocktranslate %}
</h3>
</div>
<div class="panel-body">
@@ -103,13 +103,17 @@
{% endif %}
<div class="clearfix"></div>
<div class="row">
<div class="col-sm-6 button-wrapper">
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
title="Add Character">{% translate 'Add Character' %}</a>
<div class="col-sm-6">
<p>
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
title="Add Character">{% translate 'Add Character' %}</a>
</p>
</div>
<div class="col-sm-6 button-wrapper">
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
title="Change Main Character">{% translate "Change Main" %}</a>
<div class="col-sm-6">
<p>
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
title="Change Main Character">{% translate "Change Main" %}</a>
</p>
</div>
</div>
</div>
@@ -159,7 +163,7 @@
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td>
<td class="text-center">{{ char.alliance_name|default:"" }}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -0,0 +1,62 @@
{% extends "allianceauth/base.html" %}
{% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
{% block content %}
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
<div class="col-sm-12">
<table class="table table-aa" id="table_tokens" style="width:100%">
<thead>
<tr>
<th>{% translate "Scopes" %}</th>
<th class="text-right">{% translate "Actions" %}</th>
<th>{% translate "Character" %}</th>
</tr>
</thead>
<tbody>
{% for t in tokens %}
<tr>
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td>
<td nowrap class="text-right"><a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a> <a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a></td>
<td>{{t.character_name}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %}
</div>
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function(){
let grp = 2;
var table = $('#table_tokens').DataTable({
"columnDefs": [{ orderable: false, targets: [0,1] },{ "visible": false, "targets": grp }],
"order": [[grp, 'asc']],
"drawCallback": function (settings) {
var api = this.api();
var rows = api.rows({ page: 'current' }).nodes();
var last = null;
api.column(grp, { page: 'current' })
.data()
.each(function (group, i) {
if (last !== group) {
$(rows).eq(i).before('<tr class="info"><td colspan="3">' + group + '</td></tr>');
last = group;
}
});
},
"stateSave": true,
});
});
{% endblock %}

View File

@@ -1,4 +1,5 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
@@ -7,7 +8,7 @@
<meta name="description" content="">
<meta name="author" content="">
<meta property="og:title" content="{{ SITE_NAME }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static '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.">
{% include 'allianceauth/icons.html' %}
@@ -31,6 +32,7 @@
.panel-transparent {
background: rgba(48, 48, 48, 0.7);
color: #ffffff;
padding-bottom: 21px;
}
.panel-body {

View File

@@ -6,7 +6,7 @@
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
{{ language.name_local|capfirst }} ({{ language.code }})
</option>
{% endfor %}
</select>

View File

@@ -6,7 +6,7 @@
{% block page_title %}{% translate "Login" %}{% endblock %}
{% block middle_box_content %}
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next | urlencode}}{%endif%}">
<img class="img-responsive center-block" src="{% static 'allianceauth/authentication/img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" alt="{% translate 'Login with Eve SSO' %}">
</a>
{% endblock %}

View File

@@ -1,4 +1,7 @@
{% extends 'public/base.html' %}
{% load i18n %}
{% block content %}
<div class="col-md-4 col-md-offset-4">
{% if messages %}
@@ -6,6 +9,7 @@
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
{% endfor %}
{% endif %}
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<div class="col-md-12">
@@ -13,10 +17,25 @@
{% endblock %}
</div>
</div>
{% include 'public/lang_select.html' %}
<p class="text-center" style="margin-top: 2rem;">
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
<a href="https://www.eveonline.com/article/introducing-esi" target="_blank" rel="noopener noreferrer">
{% translate "Introducing ESI - A New API For Eve Online" %}
</a>
</p>
<p class="text-center">
<a href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
{% translate "Manage ESI Applications" %}
</a>
</p>
</div>
</div>
{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}

View File

@@ -116,10 +116,17 @@ class TestAuthenticate(TestCase):
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.user)
""" Alt Login disabled
def test_authenticate_alt_character(self):
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.user)
"""
def test_authenticate_alt_character_fail(self):
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
user = StateBackend().authenticate(token=t)
self.assertEqual(user, None)
def test_authenticate_unclaimed_character(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
@@ -128,6 +135,7 @@ class TestAuthenticate(TestCase):
self.assertEqual(user.username, 'Unclaimed_Character')
self.assertEqual(user.profile.main_character, self.unclaimed_character)
""" Alt Login disabled
def test_authenticate_character_record(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
@@ -135,6 +143,15 @@ class TestAuthenticate(TestCase):
self.assertEqual(user, self.old_user)
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
self.assertTrue(user.profile.main_character)
"""
def test_authenticate_character_record_fails(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.old_user)
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
self.assertTrue(user.profile.main_character)
def test_iterate_username(self):
t = Token(character_id=self.unclaimed_character.character_id,

View File

@@ -4,16 +4,16 @@ from urllib import parse
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http.response import HttpResponse
from django.shortcuts import reverse
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse, URLPattern
from allianceauth.eveonline.models import EveCharacter
from allianceauth.tests.auth_utils import AuthUtils
from ..decorators import main_character_required
from ..models import CharacterOwnership
from ..decorators import decorate_url_patterns, main_character_required
from ..models import CharacterOwnership
MODULE_PATH = 'allianceauth.authentication'
@@ -66,3 +66,33 @@ class DecoratorTestCase(TestCase):
setattr(self.request, 'user', self.main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 200)
class TestDecorateUrlPatterns(TestCase):
def test_should_add_decorator_by_default(self):
# given
decorator = mock.MagicMock(name="decorator")
view = mock.MagicMock(name="view")
path = mock.MagicMock(spec=URLPattern, name="path")
path.callback = view
path.lookup_str = "my_lookup_str"
urls = [path]
urlconf_module = urls
# when
decorate_url_patterns(urlconf_module, decorator)
# then
self.assertEqual(path.callback, decorator(view))
def test_should_not_add_decorator_when_excluded(self):
# given
decorator = mock.MagicMock(name="decorator")
view = mock.MagicMock(name="view")
path = mock.MagicMock(spec=URLPattern, name="path")
path.callback = view
path.lookup_str = "my_lookup_str"
urls = [path]
urlconf_module = urls
# when
decorate_url_patterns(urlconf_module, decorator, excluded_views=["my_lookup_str"])
# then
self.assertEqual(path.callback, view)

View File

@@ -22,5 +22,20 @@ urlpatterns = [
views.add_character,
name='add_character'
),
path(
'account/tokens/manage/',
views.token_management,
name='token_management'
),
path(
'account/tokens/delete/<int:token_id>',
views.token_delete,
name='token_delete'
),
path(
'account/tokens/refresh/<int:token_id>',
views.token_refresh,
name='token_refresh'
),
path('dashboard/', views.dashboard, name='dashboard'),
]

View File

@@ -61,6 +61,44 @@ def dashboard(request):
}
return render(request, 'authentication/dashboard.html', context)
@login_required
def token_management(request):
tokens = request.user.token_set.all()
context = {
'tokens': tokens
}
return render(request, 'authentication/tokens.html', context)
@login_required
def token_delete(request, token_id=None):
try:
token = Token.objects.get(id=token_id)
if request.user == token.user:
token.delete()
messages.success(request, "Token Deleted.")
else:
messages.error(request, "This token does not belong to you.")
except Token.DoesNotExist:
messages.warning(request, "Token does not exist")
return redirect('authentication:token_management')
@login_required
def token_refresh(request, token_id=None):
try:
token = Token.objects.get(id=token_id)
if request.user == token.user:
try:
token.refresh()
messages.success(request, "Token refreshed.")
except Exception as e:
messages.warning(request, f"Failed to refresh token. {e}")
else:
messages.error(request, "This token does not belong to you.")
except Token.DoesNotExist:
messages.warning(request, "Token does not exist")
return redirect('authentication:token_management')
@login_required
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)

View File

@@ -5,5 +5,6 @@ from .views import NightModeRedirectView
def auth_settings(request):
return {
'SITE_NAME': settings.SITE_NAME,
'SITE_URL': settings.SITE_URL,
'NIGHT_MODE': NightModeRedirectView.night_mode_state(request),
}

View File

@@ -14,6 +14,7 @@ def sync_user_groups(modeladmin, request, queryset):
agc.update_all_states_group_membership()
@admin.register(AutogroupsConfig)
class AutogroupsConfigAdmin(admin.ModelAdmin):
formfield_overrides = {
models.CharField: {'strip': False}
@@ -36,6 +37,5 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
return actions
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
admin.site.register(ManagedCorpGroup)
admin.site.register(ManagedAllianceGroup)

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.7 on 2022-08-14 16:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0016_character_names_are_not_unique'),
]
operations = [
migrations.AlterField(
model_name='eveallianceinfo',
name='alliance_name',
field=models.CharField(max_length=254, db_index=True),
),
migrations.AlterField(
model_name='evecorporationinfo',
name='corporation_name',
field=models.CharField(max_length=254, db_index=True),
),
]

View File

@@ -71,7 +71,7 @@ class EveAllianceInfo(models.Model):
"""An alliance in Eve Online."""
alliance_id = models.PositiveIntegerField(unique=True)
alliance_name = models.CharField(max_length=254, unique=True)
alliance_name = models.CharField(max_length=254, db_index=True)
alliance_ticker = models.CharField(max_length=254)
executor_corp_id = models.PositiveIntegerField()
@@ -139,7 +139,7 @@ class EveCorporationInfo(models.Model):
"""A corporation in Eve Online."""
corporation_id = models.PositiveIntegerField(unique=True)
corporation_name = models.CharField(max_length=254, unique=True)
corporation_name = models.CharField(max_length=254, db_index=True)
corporation_ticker = models.CharField(max_length=254)
member_count = models.IntegerField()
ceo_id = models.PositiveIntegerField(blank=True, null=True, default=None)

View File

@@ -8,6 +8,7 @@ from django.conf import settings
from esi.clients import esi_client_factory
from allianceauth import __version__
from allianceauth.utils.django import StartupCommand
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(
@@ -175,15 +176,16 @@ class EveProvider:
class EveSwaggerProvider(EveProvider):
def __init__(self, token=None, adapter=None):
if settings.DEBUG:
if settings.DEBUG or StartupCommand().is_management_command:
self._client = None
logger.info(
'DEBUG mode detected: ESI client will be loaded on-demand.'
)
logger.info('ESI client will be loaded on-demand')
else:
logger.info('Loading ESI client')
try:
self._client = esi_client_factory(
token=token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
token=token,
spec_file=SWAGGER_SPEC_PATH,
app_info_text=f"allianceauth v{__version__}"
)
except (HTTPError, RefResolutionError):
logger.exception(

0
allianceauth/eveonline/views.py Executable file → Normal file
View File

View File

@@ -30,7 +30,7 @@
<td class="text-center">{{ fat.user }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %}
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}

View File

@@ -5,7 +5,7 @@
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
{% if char_id %}
<div class="text-right">
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a>
@@ -14,11 +14,11 @@
{% endif %}
</h1>
<h2>
{% blocktrans count links=n_fats trimmed %}
{% blocktranslate count links=n_fats trimmed %}
{{ user }} has collected one link this month.
{% plural %}
{{ user }} has collected {{ links }} links this month.
{% endblocktrans %}
{% endblocktranslate %}
</h2>
<table class="table table-responsive">
<tr>
@@ -34,11 +34,11 @@
</table>
{% if created_fats %}
<h2>
{% blocktrans count links=n_created_fats trimmed %}
{% blocktranslate count links=n_created_fats trimmed %}
{{ user }} has created one link this month.
{% plural %}
{{ user }} has created {{ links }} links this month.
{% endblocktrans %}
{% endblocktranslate %}
</h2>
{% if created_fats %}
<table class="table">

View File

@@ -5,7 +5,7 @@
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ year }}{% endblocktrans %}
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
<div class="text-right">
<a href="{% url 'fatlink:personal_statistics_year' previous_year %}" class="btn btn-info">{% translate "Previous year" %}</a>
{% if next_year %}

View File

@@ -5,7 +5,7 @@
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<div class="text-right">
<a href="{% url 'fatlink:statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
{% if next_month %}

View File

@@ -5,7 +5,7 @@
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<div class="text-right">
<a href="{% url 'fatlink:statistics_month' previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
{% if next_month %}

View File

@@ -33,7 +33,7 @@
<td class="text-center">{{ fat.fatlink.fleet }}</td>
<td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %}
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
{% else %}
<td class="text-center">{{ fat.system }}</td>
{% endif %}

View File

@@ -248,59 +248,82 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
@login_required
@token_required(
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
scopes=[
'esi-location.read_location.v1',
'esi-location.read_ship_type.v1',
'esi-universe.read_structures.v1',
'esi-location.read_online.v1',
]
)
def click_fatlink_view(request, token, fat_hash=None):
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
character = EveCharacter.objects.get_character_by_id(token.character_id)
character_online = c.Location.get_characters_character_id_online(
character_id=token.character_id
).result()
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
if character_online["online"] is True:
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
character = EveCharacter.objects.get_character_by_id(token.character_id)
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
if character:
# get data
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
location['solar_system_name'] = \
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
if character:
# get data
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
location['solar_system_name'] = \
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
if location['station_id']:
location['station_name'] = \
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
elif location['structure_id']:
location['station_name'] = \
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
'name']
if location['station_id']:
location['station_name'] = \
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
elif location['structure_id']:
location['station_name'] = \
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
'name']
else:
location['station_name'] = "No Station"
ship['ship_type_name'] = provider.get_itemtype(ship['ship_type_id']).name
fat = Fat()
fat.system = location['solar_system_name']
fat.station = location['station_name']
fat.shiptype = ship['ship_type_name']
fat.fatlink = fatlink
fat.character = character
fat.user = request.user
try:
fat.full_clean()
fat.save()
messages.success(request, _('Fleet participation registered.'))
except ValidationError as e:
err_messages = []
for errorname, message in e.message_dict.items():
err_messages.append(message[0])
messages.error(request, ' '.join(err_messages))
else:
location['station_name'] = "No Station"
ship['ship_type_name'] = provider.get_itemtype(ship['ship_type_id']).name
context = {
'character_id': token.character_id,
'character_name': token.character_name,
'character_portrait_url': EveCharacter.generic_portrait_url(
token.character_id, 128
),
}
fat = Fat()
fat.system = location['solar_system_name']
fat.station = location['station_name']
fat.shiptype = ship['ship_type_name']
fat.fatlink = fatlink
fat.character = character
fat.user = request.user
try:
fat.full_clean()
fat.save()
messages.success(request, _('Fleet participation registered.'))
except ValidationError as e:
err_messages = []
for errorname, message in e.message_dict.items():
err_messages.append(message[0])
messages.error(request, ' '.join(err_messages))
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
else:
context = {
'character_id': token.character_id,
'character_name': token.character_name,
'character_portrait_url': EveCharacter.generic_portrait_url(
token.character_id, 128
),
}
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
messages.error(request, _('FAT link has expired.'))
else:
messages.error(request, _('FAT link has expired.'))
messages.warning(
request,
_(
f"Cannot register the fleet participation for {character.character_name}. The character needs to be online."
),
)
return redirect('fatlink:view')

View File

@@ -1,5 +1,4 @@
{% extends "allianceauth/base.html" %}
{% load static %}
{% load i18n %}
{% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %}

View File

@@ -60,7 +60,7 @@
<i class="glyphicon glyphicon-list-alt"></i>
</a>
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
<a id="clipboard-copy" data-clipboard-text="{{ SITE_URL }}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
<i class="glyphicon glyphicon-copy"></i>
</a>
</td>

View File

@@ -5,7 +5,7 @@ app_name = "groupmanagement"
urlpatterns = [
# groups
path("groups", views.groups_view, name="groups"),
path("groups/", views.groups_view, name="groups"),
path("group/request/join/<int:group_id>/", views.group_request_add, name="request_add"),
path(
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"

0
allianceauth/groupmanagement/views.py Executable file → Normal file
View File

2
allianceauth/hrapplications/admin.py Executable file → Normal file
View File

@@ -10,6 +10,7 @@ class ChoiceInline(admin.TabularInline):
verbose_name_plural = 'Choices (optional)'
verbose_name= 'Choice'
@admin.register(ApplicationQuestion)
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['title', 'help_text', 'multi_select']}),
@@ -18,6 +19,5 @@ class QuestionAdmin(admin.ModelAdmin):
admin.site.register(Application)
admin.site.register(ApplicationComment)
admin.site.register(ApplicationQuestion, QuestionAdmin)
admin.site.register(ApplicationForm)
admin.site.register(ApplicationResponse)

0
allianceauth/hrapplications/forms.py Executable file → Normal file
View File

0
allianceauth/hrapplications/models.py Executable file → Normal file
View File

18
allianceauth/hrapplications/views.py Executable file → Normal file
View File

@@ -57,7 +57,7 @@ def hr_application_create_view(request, form_id=None):
app_form = get_object_or_404(ApplicationForm, id=form_id)
if request.method == "POST":
if Application.objects.filter(user=request.user).filter(form=app_form).exists():
logger.warn(f"User {request.user} attempting to duplicate application to {app_form.corp}")
logger.warning(f"User {request.user} attempting to duplicate application to {app_form.corp}")
else:
application = Application(user=request.user, form=app_form)
application.save()
@@ -92,7 +92,7 @@ def hr_application_personal_view(request, app_id):
}
return render(request, 'hrapplications/view.html', context=context)
else:
logger.warn(f"User {request.user} not authorized to view {app}")
logger.warning(f"User {request.user} not authorized to view {app}")
return redirect('hrapplications:personal_view')
@@ -105,9 +105,9 @@ def hr_application_personal_removal(request, app_id):
logger.info(f"User {request.user} deleting {app}")
app.delete()
else:
logger.warn(f"User {request.user} attempting to delete reviewed app {app}")
logger.warning(f"User {request.user} attempting to delete reviewed app {app}")
else:
logger.warn(f"User {request.user} not authorized to delete {app}")
logger.warning(f"User {request.user} not authorized to delete {app}")
return redirect('hrapplications:index')
@@ -132,7 +132,7 @@ def hr_application_view(request, app_id):
logger.info(f"Saved comment by user {request.user} to {app}")
return redirect('hrapplications:view', app_id)
else:
logger.warn("User %s does not have permission to add ApplicationComments" % request.user)
logger.warning("User %s does not have permission to add ApplicationComments" % request.user)
return redirect('hrapplications:view', app_id)
else:
logger.debug("Returning blank HRApplication comment form.")
@@ -171,7 +171,7 @@ def hr_application_approve(request, app_id):
app.save()
notify(app.user, "Application Accepted", message="Your application to %s has been approved." % app.form.corp, level="success")
else:
logger.warn(f"User {request.user} not authorized to approve {app}")
logger.warning(f"User {request.user} not authorized to approve {app}")
return redirect('hrapplications:index')
@@ -187,7 +187,7 @@ def hr_application_reject(request, app_id):
app.save()
notify(app.user, "Application Rejected", message="Your application to %s has been rejected." % app.form.corp, level="danger")
else:
logger.warn(f"User {request.user} not authorized to reject {app}")
logger.warning(f"User {request.user} not authorized to reject {app}")
return redirect('hrapplications:index')
@@ -208,7 +208,7 @@ def hr_application_search(request):
app_list = app_list.filter(
form__corp__corporation_id=request.user.profile.main_character.corporation_id)
except AttributeError:
logger.warn(
logger.warning(
"User %s missing main character model: unable to filter applications to search" % request.user)
applications = app_list.filter(
@@ -246,6 +246,6 @@ def hr_application_mark_in_progress(request, app_id):
app.save()
notify(app.user, "Application In Progress", message=f"Your application to {app.form.corp} is being reviewed by {app.reviewer_str}")
else:
logger.warn(
logger.warning(
f"User {request.user} unable to mark {app} in progress: already being reviewed by {app.reviewer}")
return redirect("hrapplications:view", app_id)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1 @@
from .core import notify # noqa: F401
default_app_config = 'allianceauth.notifications.apps.NotificationsConfig'

View File

@@ -15,18 +15,22 @@ class NotificationAdmin(admin.ModelAdmin):
ordering = ("-timestamp", )
search_fields = ["user__username", "user__profile__main_character__character_name"]
@admin.display(
ordering="user__profile__main_character__character_name"
)
def _main(self, obj):
try:
return obj.user.profile.main_character
except AttributeError:
return obj.user
_main.admin_order_field = "user__profile__main_character__character_name"
@admin.display(
ordering="user__profile__state__name"
)
def _state(self, obj):
return obj.user.profile.state
_state.admin_order_field = "user__profile__state__name"
def has_change_permission(self, request, obj=None):
return False

View File

@@ -44,7 +44,7 @@ def notification_view(request, notif_id):
notif.mark_viewed()
return render(request, 'notifications/view.html', context)
else:
logger.warn(
logger.warning(
"User %s not authorized to view notif_id %s belonging to user %s",
request.user,
notif_id, notif.user

View File

@@ -34,7 +34,7 @@ class OpTimer(models.Model):
fc = models.CharField(max_length=254, default="")
post_time = models.DateTimeField(default=timezone.now)
eve_character = models.ForeignKey(EveCharacter, null=True,
on_delete=models.SET_NULL)
on_delete=models.SET_NULL)
description = models.TextField(blank=True, default="")
type = models.ForeignKey(OpTimerType, null=True, on_delete=models.SET_NULL)

View File

@@ -8,10 +8,10 @@
<h1 class="page-header">{% translate "Permissions Overview" %}</h1>
<p>
{% if request.GET.all != 'yes' %}
{% blocktrans %}Showing only applied permissions{% endblocktrans %}
{% blocktranslate %}Showing only applied permissions{% endblocktranslate %}
<a href="{% url 'permissions_tool:overview' %}?all=yes" class="btn btn-primary">{% translate "Show All" %}</a>
{% else %}
{% blocktrans %}Showing all permissions{% endblocktrans %}
{% blocktranslate %}Showing all permissions{% endblocktranslate %}
<a href="{% url 'permissions_tool:overview' %}?all=no" class="btn btn-primary">{% translate "Show Applied" %}</a>
{% endif %}
</p>

View File

@@ -41,23 +41,23 @@ CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
CELERYBEAT_SCHEDULE = {
'esi_cleanup_callbackredirect': {
'task': 'esi.tasks.cleanup_callbackredirect',
'schedule': crontab(minute=0, hour='*/4'),
'schedule': crontab(minute='0', hour='*/4'),
},
'esi_cleanup_token': {
'task': 'esi.tasks.cleanup_token',
'schedule': crontab(minute=0, hour=0),
'schedule': crontab(minute='0', hour='0'),
},
'run_model_update': {
'task': 'allianceauth.eveonline.tasks.run_model_update',
'schedule': crontab(minute=0, hour="*/6"),
'schedule': crontab(minute='0', hour="*/6"),
},
'check_all_character_ownership': {
'task': 'allianceauth.authentication.tasks.check_all_character_ownership',
'schedule': crontab(minute=0, hour='*/4'),
'schedule': crontab(minute='0', hour='*/4'),
},
'analytics_daily_stats': {
'task': 'allianceauth.analytics.tasks.analytics_daily_stats',
'schedule': crontab(minute=0, hour=2),
'schedule': crontab(minute='0', hour='2'),
}
}
@@ -84,17 +84,17 @@ LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale/'),
)
ugettext = lambda s: s
LANGUAGES = (
('en', ugettext('English')),
('de', ugettext('German')),
('es', ugettext('Spanish')),
('zh-hans', ugettext('Chinese Simplified')),
('ru', ugettext('Russian')),
('ko', ugettext('Korean')),
('fr', ugettext('French')),
('ja', ugettext('Japanese')),
('it', ugettext('Italian')),
("en", "English"),
("de", "German"),
("es", "Spanish"),
("zh-hans", "Chinese Simplified"),
("ru", "Russian"),
("ko", "Korean"),
("fr", "French"),
("ja", "Japanese"),
("it", "Italian"),
("uk", "Ukrainian"),
)
TEMPLATES = [

View File

@@ -13,6 +13,13 @@ STATIC_ROOT = "/var/www/{{ project_name }}/static/"
# in page titles and the site header.
SITE_NAME = '{{ project_name }}'
# This is your websites URL, set it accordingly
# Make sure this URL is WITHOUT a trailing slash
SITE_URL = "https://example.com"
# Django security
CSRF_TRUSTED_ORIGINS = [SITE_URL]
# Change this to enable/disable debug mode, which displays
# useful error messages but can leak sensitive data.
DEBUG = False
@@ -25,6 +32,10 @@ INSTALLED_APPS += [
# To change the logging level for extensions, uncomment the following line.
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
# By default apps are prevented from having public views for security reasons.
# If you want to allow specific apps to have public views
# you can put there names here (same name as in INSTALLED_APPS):
APPS_WITH_PUBLIC_VIEWS = []
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
DATABASES['default'] = {
@@ -39,15 +50,16 @@ DATABASES['default'] = {
# Register an application at https://developers.eveonline.com for Authentication
# & API Access and fill out these settings. Be sure to set the callback URL
# to https://example.com/sso/callback substituting your domain for example.com
# to https://example.com/sso/callback substituting your domain for example.com in
# CCP's developer portal
# Logging in to auth requires the publicData scope (can be overridden through the
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
ESI_SSO_CLIENT_ID = ''
ESI_SSO_CLIENT_SECRET = ''
ESI_SSO_CALLBACK_URL = ''
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback"
ESI_USER_CONTACT_EMAIL = '' # A server maintainer that CCP can contact in case of issues.
# By default emails are validated before new users can log in.
# By default, emails are validated before new users can log in.
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
# https://www.sparkpost.com/docs/integrations/django/
# https://elasticemail.com/resources/settings/smtp-api/

View File

@@ -1,9 +1,8 @@
from django.conf.urls import include
from allianceauth import urls
from django.urls import re_path
from django.urls import include, path
urlpatterns = [
re_path(r'', include(urls)),
path('', include(urls)),
]
handler500 = 'allianceauth.views.Generic500Redirect'

View File

@@ -66,6 +66,8 @@ class NameFormatConfigAdmin(admin.ModelAdmin):
form = NameFormatConfigForm
list_display = ('service_name', 'get_state_display_string')
@admin.display(
description='States'
)
def get_state_display_string(self, obj):
return ', '.join([state.name for state in obj.states.all()])
get_state_display_string.short_description = 'States'

View File

@@ -1,15 +1,17 @@
from django.conf.urls import include
from django.urls import re_path
from string import Formatter
from typing import Iterable, Optional
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
from django.urls import include, re_path
from django.utils.functional import cached_property
from django.conf import settings
from string import Formatter
from allianceauth.hooks import get_hooks
from .models import NameFormatConfig
def get_extension_logger(name):
"""
Takes the name of a plugin/extension and generates a child logger of the extensions logger
@@ -156,8 +158,32 @@ class MenuItemHook:
class UrlHook:
def __init__(self, urls, namespace, base_url):
"""A hook for registering the URLs of a Django app.
Args:
- urls: The urls module to include
- namespace: The URL namespace to apply. This is usually just the app name.
- base_url: The URL prefix to match against in regex form.
Example ``r'^app_name/'``.
This prefix will be applied in front of all URL patterns included.
It is possible to use the same prefix as existing apps (or no prefix at all),
but standard URL resolution ordering applies
(hook URLs are the last ones registered).
- excluded_views: Optional list of views to be excluded
from auto-decorating them with the
default ``main_character_required`` decorator, e.g. to make them public.
Views must be specified by their qualified name,
e.g. ``["example.views.my_public_view"]``
"""
def __init__(
self,
urls,
namespace: str,
base_url: str,
excluded_views : Optional[Iterable[str]] = None
):
self.include_pattern = re_path(base_url, include(urls, namespace=namespace))
self.excluded_views = set(excluded_views or [])
class NameFormatter:

View File

@@ -588,16 +588,17 @@ class DiscordClient:
return None # User is no longer a member
guild_roles = RolesSet(self.guild_roles(guild_id=guild_id))
logger.debug('Current guild roles: %s', guild_roles.ids())
_roles = set(member_info.roles)
if not guild_roles.has_roles(member_info.roles):
guild_roles = RolesSet(
self.guild_roles(guild_id=guild_id, use_cache=False)
)
if not guild_roles.has_roles(member_info.roles):
role_ids = set(member_info.roles).difference(guild_roles.ids())
raise RuntimeError(
f'Discord user {user_id} has unknown roles: {role_ids}'
)
return guild_roles.subset(member_info.roles)
logger.warning(f'Discord user {user_id} has unknown roles: {role_ids}')
for _r in role_ids:
_roles.remove(_r)
return guild_roles.subset(_roles)
@classmethod
def _is_member_unknown_error(cls, r: requests.Response) -> bool:

View File

@@ -899,8 +899,8 @@ class TestGuildMemberRoles(NoSocketsTestCase):
mock_guild_roles.return_value = {role_a, role_b}
client = DiscordClientStub(TEST_BOT_TOKEN, mock_redis)
# when/then
with self.assertRaises(RuntimeError):
client.guild_member_roles(TEST_GUILD_ID, TEST_USER_ID)
roles = client.guild_member_roles(TEST_GUILD_ID, TEST_USER_ID)
self.assertEqual(roles, RolesSet([role_a]))
# TODO: Re-enable after adding Discord general error handling
# def test_should_raise_exception_if_member_info_is_invalid(

View File

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -49,7 +49,7 @@ class DiscourseTasks:
DiscourseManager.update_groups(user)
except Exception as e:
logger.exception(e)
logger.warn("Discourse group sync failed for %s, retrying in 10 mins" % user)
logger.warning("Discourse group sync failed for %s, retrying in 10 mins" % user)
raise self.retry(countdown=60 * 10)
logger.debug("Updated user %s discourse groups." % user)

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
app_name = 'example'

View File

@@ -2,8 +2,7 @@ from django.contrib import admin
from .models import Ips4User
@admin.register(Ips4User)
class Ips4UserAdmin(admin.ModelAdmin):
list_display = ('user', 'username', 'id')
search_fields = ('user__username', 'username', 'id')
admin.site.register(Ips4User, Ips4UserAdmin)

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -1,13 +1,16 @@
<tr>
<td class="text-center">{{ service_name }}</td>
<td class="text-center">{{ username }}</td>
<td class="text-center"><a href="mumble://{{ service_url }}">{{ service_url }}</a></td>
<td class="text-center">
{% if username == "" %}
<td class="text-center">{{ service_url }}</td>
<td class="text-center">
<a href="{% url 'mumble:activate' %}" title="Activate" class="btn btn-warning">
<span class="glyphicon glyphicon-ok"></span>
</a>
</td>
{% else %}
<td class="text-center"><a href="mumble://{{ connect_url }}">{{ service_url }}</a></td>
<td class="text-center">
<a href="{% url 'mumble:set_password' %}" title="Set Password" class="btn btn-warning">
<span class="glyphicon glyphicon-pencil"></span>
</a>
@@ -17,9 +20,9 @@
<a href="{% url 'mumble:deactivate' %}" title="Deactivate" class="btn btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a>
<a href="mumble://{{ connect_url }}" class="btn btn-success" title="Connect">
<a href="mumble://{{ connect_url }}" class="btn btn-success" title="Connect">
<span class="glyphicon glyphicon-arrow-right"></span>
</a>
</td>
{% endif %}
</td>
</tr>

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

0
allianceauth/services/modules/openfire/manager.py Executable file → Normal file
View File

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

2
allianceauth/services/modules/phpbb3/manager.py Executable file → Normal file
View File

@@ -176,7 +176,7 @@ class Phpbb3Manager:
logger.debug(f"Proceeding to add phpbb user {username_clean} and pwhash starting with {pwhash[0:5]}")
# check if the username was simply revoked
if Phpbb3Manager.check_user(username_clean):
logger.warn("Unable to add phpbb user with username %s - already exists. Updating user instead." % username)
logger.warning("Unable to add phpbb user with username %s - already exists. Updating user instead." % username)
Phpbb3Manager.__update_user_info(username_clean, email, pwhash)
else:
try:

View File

@@ -1,5 +1,4 @@
from django.conf.urls import include
from django.urls import path
from django.urls import include, path
from . import views

View File

@@ -38,6 +38,12 @@ class SmfService(ServicesHook):
if SmfTasks.has_account(user):
SmfTasks.update_groups.delay(user.pk)
def sync_nickname(self, user):
logger.debug(f"Updating {self.name} displayed name for {user}")
if SmfTasks.has_account(user):
SmfTasks.update_display_name.apply_async(args=[user.pk], countdown=5) # cooldown on this task to ensure DB clean when syncing
def update_all_groups(self):
logger.debug('Update all %s groups called' % self.name)
SmfTasks.update_all_groups.delay()

View File

@@ -5,11 +5,14 @@ from datetime import datetime
import hashlib
import logging
import re
from typing import Tuple
from packaging import version
from django.db import connections
from django.conf import settings
from django.contrib.auth.models import User
from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__)
@@ -37,6 +40,10 @@ class SmfManager:
SQL_DEL_USER = r"DELETE FROM %smembers where member_name = %%s" % TABLE_PREFIX
SQL_UPD_USER = r"UPDATE %smembers SET email_address = %%s, passwd = %%s, real_name = %%s WHERE member_name = %%s" % TABLE_PREFIX
SQL_UPD_DISPLAY_NAME = r"UPDATE %smembers SET real_name = %%s WHERE member_name = %%s" % TABLE_PREFIX
SQL_DIS_USER = r"UPDATE %smembers SET email_address = %%s, passwd = %%s WHERE member_name = %%s" % TABLE_PREFIX
SQL_USER_ID_FROM_USERNAME = r"SELECT id_member from %smembers WHERE member_name = %%s" % TABLE_PREFIX
@@ -174,50 +181,75 @@ class SmfManager:
return out
@classmethod
def add_user(cls, username, email_address, groups, characterid):
def add_user(cls, username, email_address, groups, main_character: EveCharacter) -> Tuple:
"""
Add a user to SMF
:param username:
:param email_address:
:param groups:
:param main_character:
:return:
"""
main_character_id = main_character.character_id
main_character_name = main_character.character_name
logger.debug(
f"Adding smf user with member_name {username}, "
f"email_address {email_address}, "
f"characterid {characterid}"
f"Adding smf user with member_name: {username}, "
f"email_address: {email_address}, "
f"characterid: {main_character_id}, "
f"main character: {main_character_name}"
)
cursor = connections['smf'].cursor()
username_clean = cls.santatize_username(username)
passwd = cls.generate_random_pass()
pwhash = cls.gen_hash(username_clean, passwd)
logger.debug(f"Proceeding to add smf user {username} and pwhash starting with {pwhash[0:5]}")
register_date = cls.get_current_utc_date()
logger.debug(f"Proceeding to add smf user {username} and pwhash starting with {pwhash[0:5]}")
# check if the username was simply revoked
if cls.check_user(username) is True:
logger.warning(f"Unable to add smf user with username {username} - already exists. Updating user instead.")
cls.__update_user_info(username_clean, email_address, pwhash)
logger.warning(
f"Unable to add smf user with username {username} - "
f"already exists. Updating user instead."
)
cls.__update_user_info(
username_clean, email_address, pwhash, main_character_name
)
else:
try:
smf_version = cls._get_current_smf_version()
sql_add_user_arguments = [
username_clean,
pwhash,
email_address,
register_date,
main_character_name,
]
if version.parse(smf_version) < version.parse("2.1"):
logger.debug("SMF compatibility: < 2.1")
cursor.execute(
cls.SQL_ADD_USER_SMF_20,
[username_clean, pwhash, email_address, register_date, username_clean]
)
cursor.execute(cls.SQL_ADD_USER_SMF_20, sql_add_user_arguments)
else:
logger.debug("SMF compatibility: >= 2.1")
cursor.execute(
cls.SQL_ADD_USER_SMF_21,
[username_clean, pwhash, email_address, register_date, username_clean]
)
cls.add_avatar(username_clean, characterid)
cursor.execute(cls.SQL_ADD_USER_SMF_21, sql_add_user_arguments)
cls.add_avatar(username_clean, main_character_id)
logger.info(f"Added smf member_name {username_clean}")
cls.update_groups(username_clean, groups)
except Exception as e:
logger.warning(f"Unable to add smf user {username_clean}: {e}")
pass
return username_clean, passwd
@classmethod
def __update_user_info(cls, username, email_address, passwd):
def __update_user_info(cls, username, email_address, passwd, main_character_name):
logger.debug(
f"Updating smf user {username} info: "
f"username {email_address} "
@@ -225,7 +257,9 @@ class SmfManager:
)
cursor = connections['smf'].cursor()
try:
cursor.execute(cls.SQL_DIS_USER, [email_address, passwd, username])
cursor.execute(
cls.SQL_UPD_USER, [email_address, passwd, main_character_name, username]
)
logger.info(f"Updated smf user {username} info")
except Exception as e:
logger.exception(f"Unable to update smf user {username} info. ({e})")
@@ -243,6 +277,27 @@ class SmfManager:
logger.error(f"Unable to delete smf user {username} - user not found on smf.")
return False
@classmethod
def update_display_name(cls, user: User):
logger.debug(f"Updating SMF displayed name for user {user}")
cursor = connections['smf'].cursor()
smf_username = user.smf.username
try:
display_name = user.profile.main_character.character_name
except Exception as exc:
logger.exception(
f"Unable to find a main character name for {user}, skipping... ({exc})"
)
display_name = smf_username
if cls.check_user(smf_username):
cursor.execute(cls.SQL_UPD_DISPLAY_NAME, [display_name, smf_username])
logger.info(f"Updated displayed name for smf user {smf_username}")
return True
logger.error(f"Unable to update smf user {smf_username} - user not found on smf.")
return False
@classmethod
def update_groups(cls, username, groups):
userid = cls.get_user_id(username)

View File

@@ -0,0 +1,29 @@
from django.db import migrations
from ..manager import SmfManager
def on_migrate(apps, schema_editor):
SmfUser = apps.get_model("smf", "SmfUser")
db_alias = schema_editor.connection.alias
all_smf_users = SmfUser.objects.using(db_alias).all()
for smf_user in all_smf_users:
try:
auth_user = smf_user.user
except:
pass
else:
SmfManager.update_display_name(auth_user)
def on_migrate_zero(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('smf', '0002_service_permissions'),
]
operations = [
migrations.RunPython(on_migrate, on_migrate_zero),
]

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