Compare commits

...

571 Commits

Author SHA1 Message Date
Ariel Rin
7898594909 Version Bump 4.0.0a3 2023-10-27 23:23:48 +10:00
Ariel Rin
cfd12ee3cc Merge branch 'v4theme' into 'v4.x'
remove rogue span tag

See merge request allianceauth/allianceauth!1546
2023-10-27 13:22:38 +00:00
Ariel Rin
2c9177b19f remove rogue span tag 2023-10-27 13:22:38 +00:00
Ariel Rin
abff26fb6e Version Bump 4.0.0a2 2023-10-27 22:48:06 +10:00
Ariel Rin
e8c3b5225c Merge branch 'v4.x' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-27 22:42:53 +10:00
Ariel Rin
98fd1dcc4c Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-27 22:42:30 +10:00
Ariel Rin
cfe46e4ca5 Merge branch 'fix-same-name-template-tag-modules' into 'v4.x'
[FIX] Give template tag modules unique names

See merge request allianceauth/allianceauth!1548
2023-10-27 12:15:27 +00:00
Peter Pfeufer
4675193416 [REEMOVE] services.templatetags as its no longer needed 2023-10-27 14:08:44 +02:00
Ariel Rin
a84fa1ca69 Merge branch 'aa-css-framework' into 'v4.x'
Alliance Auth CSS Framework

See merge request allianceauth/allianceauth!1544
2023-10-27 11:58:16 +00:00
Ariel Rin
8f6cb0b9bb Merge branch 'fix-notification-colors' into 'v4.x'
[FIX]  Restore notification level colours

See merge request allianceauth/allianceauth!1547
2023-10-27 11:54:40 +00:00
Ariel Rin
1c8634f1c8 Merge branch 'theme-work' into 'v4.x'
Theme test fixes

See merge request allianceauth/allianceauth!1549
2023-10-27 11:33:08 +00:00
Aaron Kable
2a21599d45 BS5 Theme test fixes 2023-10-27 11:33:08 +00:00
Peter Pfeufer
e379c01655 [FIX] Typo in variable name while we're at it 2023-10-27 08:31:55 +02:00
Peter Pfeufer
afa3d2e7cc [FIX] Give template tags modules unique names
This fixes the check error:

?: (templates.E003) 'menu_items' is used for multiple template tag modules: 'allianceauth.menu.templatetags.menu_items', 'allianceauth.services.templatetags.menu_items'
2023-10-27 08:28:09 +02:00
Peter Pfeufer
e5ed33aeec [FIX] [Bootstrap] Uniform default table background
Also removed empty style and class arguments while I was at it
2023-10-26 18:21:23 +02:00
Peter Pfeufer
b12471e775 [CHANGE] Bring back the level colors to the notification list 2023-10-26 15:30:54 +02:00
Peter Pfeufer
5e70dab11f [FIX] Ensure the modifier is only applied to elements with the base class 2023-10-24 12:54:20 +02:00
Peter Pfeufer
f728c786b3 [FIX] Grammar and indentation (it's important!) 2023-10-24 12:27:08 +02:00
Peter Pfeufer
7056912d54 [FIX] Path to image in .md file 2023-10-24 11:58:34 +02:00
Peter Pfeufer
7efed950ca [CHANGE] Seems it needs to be a PNG image … 2023-10-24 11:53:00 +02:00
Peter Pfeufer
886acf2005 [ADD] CSS framework docs to custom documentation index 2023-10-24 11:36:54 +02:00
Peter Pfeufer
b2dec3bff2 [FIX] Typo … 2023-10-24 11:31:15 +02:00
Peter Pfeufer
f0a402e141 [ADD] Documentation 2023-10-24 11:25:52 +02:00
Peter Pfeufer
2e2afd7923 [ADD] Callout boxes
Not quite alerts, but custom and helpful notes for folks. Requires a base and modifier class.
2023-10-24 11:05:15 +02:00
Peter Pfeufer
e9ea09bc56 [CHANGE] Rename auth-base-bs5.css to auth-framework.css 2023-10-24 10:58:45 +02:00
Ariel Rin
186fa1be03 Correct Django version back to !1541 after porting !1513 2023-10-24 12:37:37 +10:00
Ariel Rin
37d1d84fc3 Merge branch 'aa4-bs5-template-fixes' into 'v4.x'
v4 Template fixes

See merge request allianceauth/allianceauth!1541
2023-10-24 02:04:50 +00:00
Peter Pfeufer
ee24706e43 v4 Template fixes 2023-10-24 02:04:49 +00:00
Ariel Rin
07e85727ea Merge branch 'v4theme' into 'v4.x'
Theme handling improvements

See merge request allianceauth/allianceauth!1542
2023-10-21 09:08:28 +00:00
Ariel Rin
4912f0f8f0 Theme handling improvements 2023-10-21 09:08:28 +00:00
Ariel Rin
424246df26 Version Bump 3.7.1 2023-10-19 14:00:29 +10:00
Ariel Rin
563e2210ef Bump Django-ESI to >=5.0.0 2023-10-19 13:11:35 +10:00
Ariel Rin
02a1078005 Merge branch 'remove-thirdparty' into 'master'
Remove outdated supervisor configs - refer to docs

See merge request allianceauth/allianceauth!1533
2023-10-19 03:03:35 +00:00
Ariel Rin
30107de44e Merge branch 'docs-precommit' into 'master'
Add code-style docs

Closes #1379

See merge request allianceauth/allianceauth!1534
2023-10-19 03:03:14 +00:00
Ariel Rin
77a08cd218 Add code-style docs 2023-10-07 22:32:19 +10:00
Ariel Rin
e5a09027e5 Remove outdated supervisor configs - refer to docs 2023-10-07 21:53:03 +10:00
Ariel Rin
24376262f0 minor doc structure changes 2023-10-07 21:27:08 +10:00
Ariel Rin
efe0c6963b move doc dependencies to pyproject 2023-10-07 20:45:39 +10:00
Ariel Rin
a4644028ae file path typos 2023-10-07 20:44:16 +10:00
Ariel Rin
3a77b4a429 Add missing docker tags, make docker buildsteps more readable 2023-10-07 19:57:55 +10:00
Ariel Rin
52b6c5d341 Rework default celery configuration and documentation 2023-10-07 19:31:53 +10:00
Ariel Rin
fa375a551c Merge branch 'celry' into 'v4.x'
Rework default celery configuration and documentation

See merge request allianceauth/allianceauth!1482
2023-10-07 09:28:04 +00:00
Aaron Kable
00a93e6fe9 Rework default celery configuration and documentation 2023-10-07 09:28:04 +00:00
Ariel Rin
656e69d4b2 4.4.0a1 - use pep44, refer to !1323 2023-10-07 18:54:02 +10:00
Ariel Rin
3b55d370d0 Merge branch 'v4.x' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-07 18:40:45 +10:00
Ariel Rin
5c126ffe82 Version Bump 4.0.0-alpha.1 2023-10-07 18:40:00 +10:00
Ariel Rin
99be753836 Merge branch 'v4.x-theme' into 'v4.x'
BS5 Theme

See merge request allianceauth/allianceauth!1464
2023-10-07 08:20:22 +00:00
Aaron Kable
2e78aa5f26 BS5 Theme 2023-10-07 08:20:22 +00:00
Ariel Rin
567d97f38a Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-10-07 17:38:30 +10:00
Ariel Rin
8b895b76b5 Version Bump 3.7.0 2023-10-07 15:27:11 +10:00
Ariel Rin
babd71702f Merge branch 'docs' into 'master'
Remove CentOS Section from NGINX docs

See merge request allianceauth/allianceauth!1531
2023-10-07 04:59:12 +00:00
Ariel Rin
3ec3cbdff7 Merge branch 'fix-tasks-running' into 'master'
Fix tasks running counter

See merge request allianceauth/allianceauth!1529
2023-10-07 04:52:04 +00:00
Erik Kalkoken
51611e1237 Fix tasks running counter 2023-10-07 04:52:03 +00:00
Ariel Rin
39519bab91 Merge branch 'new-discord-username-format' into 'master'
[ADD] Respect and display the new Discord username format when eligible

See merge request allianceauth/allianceauth!1526
2023-10-07 04:40:46 +00:00
Ariel Rin
90dc6a4d4c Merge branch 'fix-reference-before-assignment' into 'master'
[FIX] Reference before assignment

Closes #1369 and #1375

See merge request allianceauth/allianceauth!1530
2023-10-07 04:40:00 +00:00
Ariel Rin
53ffd7f885 Merge branch 'set-lang-attribute' into 'master'
[ADD] Language code to page and language selector

See merge request allianceauth/allianceauth!1528
2023-10-07 04:39:01 +00:00
Ariel Rin
efc7475228 Merge branch 'celery-broker-connection' into 'master'
Prepare our Celery config for Celery 6

See merge request allianceauth/allianceauth!1532
2023-10-07 04:38:21 +00:00
Peter Pfeufer
380c41400b [CHANGE] Updating celery.py to prevent deprecation warning
```
[2023-08-14 06:41:04,904: WARNING/MainProcess] /mnt/sda1/Development/Python/AllianceAuth/venv-3.11/lib/python3.11/site-packages/celery/worker/consumer/consumer.py:498: CPendingDeprecationWarning: The broker_connection_retry configuration setting will no longer determine
whether broker connection retries are made during startup in Celery 6.0 and above.
If you wish to retain the existing behavior for retrying connections on startup,
you should set broker_connection_retry_on_startup to True.
```
2023-09-15 11:42:47 +02:00
colcrunch
079c12a72e Remove CentOS heading and add notes about the differing config methods to the relevant lines in the install section. 2023-09-07 07:11:05 -04:00
Peter Pfeufer
4f1ebedc44 [FIX] Reference before assignment
`ownership` doesn't exist at this point.
To get the main character, `user` is used here.


```
Traceback (most recent call last):
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/core/handlers/exception.py", line 56, in inner
    response = get_response(request)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/esi/decorators.py", line 116, in _wrapped_view
    return view_func(request, token, *args, **kwargs)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/allianceauth/authentication/views.py", line 156, in sso_login
    user = authenticate(token=token)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/views/decorators/debug.py", line 42, in sensitive_variables_wrapper
    return func(*func_args, **func_kwargs)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/django/contrib/auth/__init__.py", line 77, in authenticate
    user = backend.authenticate(request, **credentials)
  File "/home/allianceserver/venv/auth/lib/python3.10/site-packages/allianceauth/authentication/backends.py", line 68, in authenticate
    if ownership.user.profile.main_character.character_id != token.character_id:
UnboundLocalError: local variable 'ownership' referenced before assignment
```
2023-09-01 20:46:00 +02:00
Peter Pfeufer
66822107e3 [ADD] Language code to page and language selector 2023-08-28 21:40:28 +02:00
Peter Pfeufer
7856cd5ce4 [ADD] Respect and display the new Discord username format when eligible 2023-08-26 00:47:08 +02:00
Ariel Rin
d6821b3fd6 Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-08-14 15:13:54 +10:00
Ariel Rin
90375246fd Merge branch 'analytics' into 'v4.x'
Analytics UA to V4 Conversion

See merge request allianceauth/allianceauth!1500
2023-08-14 03:31:33 +00:00
Ariel Rin
a2f217ace5 Analytics UA to V4 Conversion 2023-08-14 03:31:33 +00:00
Ariel Rin
25cf2fdcd5 Merge branch 'docs' into 'v4.x'
V4.x Docker Refactoring and Docs

See merge request allianceauth/allianceauth!1507
2023-08-14 03:05:44 +00:00
Ariel Rin
4305ae7995 V4.x Docker Refactoring and Docs 2023-08-14 03:05:44 +00:00
Ariel Rin
36b3077caa Version Bump 3.6.1 2023-08-10 14:37:27 +10:00
Ariel Rin
1786f3a642 Merge branch 'prepare-base-template-for-public-views' into 'master'
[FIX] Templates prepared for public views

See merge request allianceauth/allianceauth!1525
2023-08-10 04:23:48 +00:00
Peter Pfeufer
55927c6f15 [FIX] Allow messages also for non-logged-in user to be displayed 2023-08-08 23:20:43 +02:00
Peter Pfeufer
8fbe0ba45d [CHANGE] Comment 2023-08-07 07:23:21 +02:00
Peter Pfeufer
1563805ddb [ADD] CCP's "No Character" character image as user menu image for public 2023-08-05 18:51:12 +02:00
Ariel Rin
c58ed53369 Merge branch 'fix-negative-running-tasks' into 'master'
Fix negative running tasks

See merge request allianceauth/allianceauth!1524
2023-08-05 09:17:42 +00:00
Peter Pfeufer
32128ace1c [FIX] Better explanation in local.py project template 2023-08-02 23:35:25 +02:00
Peter Pfeufer
7290eaad7e [FIX] Notifications menu item removed in public views 2023-08-02 23:22:14 +02:00
Peter Pfeufer
f23d4f4dd1 [FIX] base.html prepared for public views
Certain things need to be behind `{% if user.is_authenticated %}` in order for the base template to play nice with public views.
2023-08-02 23:17:28 +02:00
ErikKalkoken
ab3f10e6f2 Add more tests for ItemCounter 2023-08-02 16:11:24 +02:00
ErikKalkoken
20187cc73e Add locks to ensure process safety 2023-08-02 15:58:36 +02:00
ErikKalkoken
1f55fbfccc Add minimum to ItemCounter and refactor redis client init 2023-08-02 15:41:47 +02:00
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
4aff4006e3 Merge branch 'v4-bumps' into 'v4.x'
V4.x Major versions

See merge request allianceauth/allianceauth!1502
2023-06-09 06:07:39 +00:00
Ariel Rin
55c188f2d0 more docker image bumps 2023-06-03 17:32:30 +10:00
Ariel Rin
f36f824a4b run pre-commit 2023-06-03 17:13:51 +10:00
Ariel Rin
6fbf33bcdd update pre-commit 2023-06-03 17:13:17 +10:00
Ariel Rin
ed3c2c8529 dj4.1 #31395 changed testdata upstream, breaks setting up classes or something 2023-06-03 17:08:06 +10: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
Ariel Rin
05d7fb1f63 repr workaround no longer needed 2023-05-03 14:23:10 +10:00
Ariel Rin
3b19db2564 let pre-commit do some work 2023-05-03 13:18:05 +10:00
Ariel Rin
98aa44c070 dj 4.2 2023-05-03 13:17:55 +10:00
Ariel Rin
8d46ee65af target dj4.2 2023-05-03 12:51:56 +10:00
Ariel Rin
49780b871d python bumps 2023-05-03 12:46:49 +10:00
Ariel Rin
2b7d24fc28 Merge branch 'v4.x' of https://gitlab.com/allianceauth/allianceauth into v4-bumps 2023-05-03 12:24:46 +10:00
Ariel Rin
b8f86a618f py312 rc tests 2023-05-03 12:20:17 +10:00
Ariel Rin
9921011742 docker bumps 2023-05-03 12:19:52 +10: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
be2fbe862b Version Bump 3.0.0 2022-07-30 19:10:07 +10:00
Ariel Rin
f95bee0921 roll back readthedocs python version, pending #1331 2022-07-30 18:54:53 +10:00
Ariel Rin
2f9ae8b054 Django bugfix bump before release 2022-07-30 18:40:27 +10:00
Ariel Rin
74651dd30a Update from Transifex 2022-07-30 18:21:20 +10:00
Ariel Rin
9cdcd8365c Merge branch 'docs' into 'v3.x'
Refactor Docs with OS Versions and non-root

See merge request allianceauth/allianceauth!1390
2022-07-30 07:26:43 +00:00
Ariel Rin
f5d70a2c48 Refactor Docs with OS Versions and non-root 2022-07-30 07:26:42 +00:00
Ariel Rin
f40ebbfba4 Merge branch 'v3.x' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-07-30 16:35:31 +10:00
Ariel Rin
2551a9dd64 Merge branch 'the-big-template-cleanup' into 'v3.x'
Big Template Cleanup

See merge request allianceauth/allianceauth!1443
2022-07-29 12:49:58 +00:00
Ariel Rin
e3b01ccbc9 Merge branch 'apache-ssl-settings-in-local-py' into 'v3.x'
Added fix for "apache vs django" proxy headers to docs

See merge request allianceauth/allianceauth!1440
2022-07-29 12:48:35 +00:00
Ariel Rin
267a392945 Merge branch 'timerboard-mandatory-fields' into 'v3.x'
[FIX] Set `planet_moon` field as not required

See merge request allianceauth/allianceauth!1441
2022-07-23 05:13:03 +00:00
Ariel Rin
634d021bf2 Merge branch 'fix-hr-search-result-duplication' into 'v3.x'
[FIX] Search result duplication in HR module

See merge request allianceauth/allianceauth!1444
2022-07-23 05:12:15 +00:00
Ariel Rin
4e8bfba738 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-07-19 20:46:24 +10:00
Ariel Rin
297f98f046 Version Bump 2.15.1 2022-07-19 19:07:47 +10:00
Ariel Rin
27dad05927 Merge branch 'fix-discord-update-username' into 'master'
Fix discord update username

See merge request allianceauth/allianceauth!1442
2022-07-19 08:48:26 +00:00
Erik Kalkoken
697e9dd772 Fix discord update username 2022-07-19 08:48:25 +00:00
Peter Pfeufer
312951ea3f One search result per user is enough
No need to show the same result for each alt ....
2022-07-18 23:02:28 +02:00
Peter Pfeufer
e4bf96cfb6 Deprecated attributes removed 2022-07-18 21:51:07 +02:00
Peter Pfeufer
3bd6baa8f9 Templates cleaned up / fixed
- Deprecated CSS atrributes removed
- HTML fixes
    - Mandatory attributes added
    - Missing semicolons added
    - Missing closing tags added
    - Missing label association in forms added/fixed
    - Missing quotes added
    - Closing tags that have no opening tag removed
- Bootstrap fixes
- Unused template tags removed
2022-07-18 21:39:20 +02:00
Peter Pfeufer
06e38dcd93 [FIX] Set planet_moon field as not required
It is explicitly set to be not required in the form, so it shouldn't be required in the Django backend
2022-07-18 19:37:25 +02:00
Peter Pfeufer
f47b9eee5b Added fix for "apache vs django" proxy headers 2022-07-18 18:10:54 +02:00
Ariel Rin
0d4cab66b2 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-07-18 21:25:23 +10:00
Ariel Rin
dc23ee8ad2 Delay automated docker builds, to allow publishing pipeline to catch up 2022-07-18 10:14:05 +00:00
Ariel Rin
65f2efc890 Version Bump 2.15.0 2022-07-18 19:22:50 +10:00
Ariel Rin
def30900b4 Merge branch 'discord_bugfixes_and_refactor' into 'master'
Fix managed roles and reserved groups bugs in Discord Service and more

Closes #1345 and #1334

See merge request allianceauth/allianceauth!1429
2022-07-18 09:12:32 +00:00
Erik Kalkoken
d7fabccddd Fix managed roles and reserved groups bugs in Discord Service and more 2022-07-18 09:12:32 +00:00
Ariel Rin
45289e1d17 Merge branch 'fix-filterdropdown-bug' into 'master'
Fix filterdropdown bug

See merge request allianceauth/allianceauth!1439
2022-07-18 09:04:35 +00:00
Ariel Rin
e7bafaa4d8 Merge branch 'datatables-filterdropdown-js-update' into 'v3.x'
Update `filterDropDown.js` to latest available version

See merge request allianceauth/allianceauth!1438
2022-07-18 09:01:30 +00:00
Peter Pfeufer
ba3f1507be LICENSE file added 2022-07-18 10:51:40 +02:00
ErikKalkoken
7b9bf08aa3 Fix bug in filterDropDown bundle 2022-07-15 13:39:48 +02:00
Peter Pfeufer
360458f574 Update with latest version 2022-07-12 18:27:28 +02:00
Ariel Rin
def6431052 Version Bump 2.14.0 2022-07-11 14:27:49 +10:00
Ariel Rin
a47bd8d7c7 Version Bump 3.0.0b3 2022-07-11 14:20:53 +10:00
Ariel Rin
22a270aedb Merge branch 'filterdropdown-backwards-compatibility' into 'master'
Add filterdropdown bundle to AA2 to ensure backwards compatibility

See merge request allianceauth/allianceauth!1437
2022-07-11 04:15:25 +00:00
Ariel Rin
54bce4315b Merge branch 'add-filterdropdown-js-to-bundles' into 'v3.x'
Add filterdropdown js to bundles

See merge request allianceauth/allianceauth!1436
2022-07-11 04:14:46 +00:00
Peter Pfeufer
c930f7bbeb Also adds timers.js, eve-time.js and refresh_notifications.js
As these seem to be used in some apps as well
2022-07-09 15:57:43 +02:00
Peter Pfeufer
8c1f06d7b8 Added refresh_notifications.js to bundles
Probably used in template overrides
2022-07-09 15:51:55 +02:00
Peter Pfeufer
815b6fa030 Added eve-time.js to bundles
Probably used in template overrides
2022-07-09 15:50:09 +02:00
Peter Pfeufer
7c05217900 Add timers.js to bundle
It's used in `mumbletemps`
2022-07-09 15:45:25 +02:00
Peter Pfeufer
64ee273953 Add filterdropdown bundle to AA2 to ensure backwards compatibility 2022-07-09 13:43:05 +02:00
Peter Pfeufer
d8c2944966 [FIX] table HTML syntax 2022-07-07 11:55:10 +02:00
Peter Pfeufer
7669c9e55d Add filterDropDown.js to bundles 2022-07-07 11:54:31 +02:00
Ariel Rin
71c9faaf28 Version Bump 3.0.0b2 2022-07-07 18:15:37 +10:00
Ariel Rin
236c70316c Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-07-07 18:07:49 +10:00
Ariel Rin
0d0686f58a Merge branch 'allianceauth-prefix-static-files-directory' into v3.x 2022-07-07 18:01:12 +10:00
Ariel Rin
3706a1aedf Merge branch 'improve-autodocs-for-models' into 'master'
Improve autodocs for models & more

See merge request allianceauth/allianceauth!1435
2022-07-07 07:38:58 +00:00
Ariel Rin
47f1b77320 Merge branch 'consolidate-redis-client-access' into 'master'
Ensure backwards compatibility when fetching a redis client

See merge request allianceauth/allianceauth!1428
2022-07-07 07:37:21 +00:00
Erik Kalkoken
8dec242a93 Ensure backwards compatibility when fetching a redis client 2022-07-07 07:37:21 +00:00
Ariel Rin
6b934060dd Merge branch 'remove-old-unused-templates' into 'v3.x'
Remove unused templates

See merge request allianceauth/allianceauth!1431
2022-07-07 07:36:21 +00:00
Ariel Rin
ff88a16163 Merge branch 'smf-2.1-compatibility' into 'v3.x'
SMF 2.1 compatibility

See merge request allianceauth/allianceauth!1432
2022-07-07 07:35:58 +00:00
Ariel Rin
e81a66b74b Merge branch 'setup.cfg-vs-manifest-part-2' into 'v3.x'
setup.cfg vs MANIFEST.in (Round 2)

See merge request allianceauth/allianceauth!1434
2022-07-07 07:34:17 +00:00
ErikKalkoken
2ff200c566 Refer to django-esi docs 2022-06-27 13:43:45 +02:00
ErikKalkoken
091a2637ea Add extension to improve autodocs for Django models & enable source links 2022-06-27 13:41:15 +02:00
Peter Pfeufer
6c7729308c Should be find_namespace 2022-06-27 05:39:45 +02:00
Peter Pfeufer
0195ef23d5 Attempt to remove MANIFEST.in file (Part 2) 2022-06-27 05:38:31 +02:00
Peter Pfeufer
a7afa4a0c3 Background for login page fixed 2022-06-25 14:44:28 +02:00
Peter Pfeufer
004100091f Moved authentication and services into allianceauth folder 2022-06-25 14:01:32 +02:00
Peter Pfeufer
20231ce198 Load static files from their new place 2022-06-25 13:51:46 +02:00
Peter Pfeufer
0851a6d085 [Cleanup] Removed {% load static %} when no static files are loaded 2022-06-25 13:51:10 +02:00
Peter Pfeufer
0cd36ad5bc Moved SSO button from "root" to "authentication" 2022-06-25 13:46:56 +02:00
Peter Pfeufer
7618dd0f91 Removed obsolete attribute 2022-06-25 13:31:12 +02:00
Peter Pfeufer
cf49a2cb65 Moved static files to their own directory 2022-06-25 13:30:32 +02:00
Peter Pfeufer
cbdce18633 Use regex to determine the SMF version (thanks @colcrunch) 2022-06-24 23:01:16 +02:00
Peter Pfeufer
a0d14eb1d3 SQL queries need to be different depending on SMF version
It's not a work of art, but it does the job. If anyone has a better idea, hit the comments ...
2022-06-24 22:40:04 +02:00
Peter Pfeufer
53ce4d2453 f-strings in log messages
Easier to read and modernized the code
2022-06-24 21:09:32 +02:00
Peter Pfeufer
1ddb041d6d Add Exception messages to exception logging 2022-06-24 21:00:54 +02:00
Peter Pfeufer
43cbfd1c47 Stop usage of deprecated logger functions 2022-06-24 20:57:58 +02:00
Peter Pfeufer
b9a8495a43 Add exception message to log output 2022-06-24 20:56:38 +02:00
Peter Pfeufer
e296477880 Use pwhash instead of passwd
This way we ensure that the initial password actually works and the user doesn't have to set a new one right away.
2022-06-24 20:55:04 +02:00
Peter Pfeufer
bd5c2d8cbc Removed non existent table columns from SQL query 2022-06-24 20:54:05 +02:00
Peter Pfeufer
ccd40d5c68 Remove unused templates 2022-06-23 21:26:13 +02:00
Ariel Rin
7f8ca4fad2 Version Bump 3.0.0b1 2022-06-18 14:48:40 +10:00
Ariel Rin
4bb9a7155d this should point at master, not an old branch 2022-06-18 14:48:14 +10:00
Ariel Rin
2ac79954f3 update from Transifex 2022-06-18 14:42:26 +10:00
Ariel Rin
585e1f47f3 cap docutils to enable recommonmark to still work. 2022-06-18 14:21:56 +10:00
Ariel Rin
a33c474b35 Use new Redis in tests 2022-06-18 14:05:39 +10:00
Ariel Rin
61c3d8964b use new django-redis package, reorganize to match setup.cfg order for better comparison 2022-06-18 13:48:46 +10:00
Ariel Rin
1c927c5820 move secret_detection gitlab job into gitlab stage 2022-06-18 13:41:39 +10:00
Ariel Rin
ff0fa0329d Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-06-18 13:28:15 +10:00
Ariel Rin
e51ea439ca update pre-commit in prep 2022-06-18 13:19:59 +10:00
Ariel Rin
113977b19f Version Bump 2.13.0 2022-06-18 13:07:36 +10:00
Ariel Rin
8f39b50b6d Merge branch 'Maestro-Zacht-fix-fat-attributeerror' into 'master'
fixed attribute error

See merge request allianceauth/allianceauth!1421
2022-06-18 02:53:11 +00:00
Maestro-Zacht
95b309c358 fixed attribute error 2022-06-18 02:53:11 +00:00
Ariel Rin
cf3df3b715 Merge branch 'fix_issue_1328' into 'master'
Fix: Changing group's state setting does not kick existing non-conforming group members

Closes #1328

See merge request allianceauth/allianceauth!1400
2022-06-18 02:47:14 +00:00
Erik Kalkoken
d815028c4d Fix: Changing group's state setting does not kick existing non-conforming group members 2022-06-18 02:47:14 +00:00
Ariel Rin
ac5570abe2 Merge branch 'fix_issue_1268' into 'master'
Fix: Service group updates broken when adding users to groups

Closes #1268

See merge request allianceauth/allianceauth!1403
2022-06-18 02:41:23 +00:00
Erik Kalkoken
84ad571aa4 Fix: Service group updates broken when adding users to groups 2022-06-18 02:41:23 +00:00
Ariel Rin
38e7705ae7 Merge branch 'docs-dark-mode' into 'master'
Add automatic dark mode to docs

See merge request allianceauth/allianceauth!1427
2022-06-18 02:39:59 +00:00
ErikKalkoken
0b6af014fa Add automatic dark mode to docs 2022-06-17 21:49:18 +02:00
Ariel Rin
2401f2299d Merge branch 'fix-doc-redis-issue' into 'master'
Fix: Broken docs generation on readthedocs.org (2nd attempt)

See merge request allianceauth/allianceauth!1425
2022-06-17 11:58:45 +00:00
Erik Kalkoken
919768c8bb Fix: Broken docs generation on readthedocs.org (2nd attempt) 2022-06-17 11:58:45 +00:00
Ariel Rin
24db21463b Merge branch 'docs-template-tags-example' into 'master'
Add example for template tags to docs

See merge request allianceauth/allianceauth!1426
2022-06-17 11:58:05 +00:00
Erik Kalkoken
1e029af83a Add example for template tags to docs 2022-06-17 11:58:05 +00:00
Ariel Rin
53dd8ce606 Merge branch 'buil-tests' into 'v3.x'
Add build test to downloadable artifacts

See merge request allianceauth/allianceauth!1424
2022-06-17 11:56:20 +00:00
Peter Pfeufer
0f4003366d Add build test to downloadable artifacts 2022-06-17 11:56:20 +00:00
Ariel Rin
ac6f3c267f Version Bump v3.0.0a5 2022-06-06 23:02:04 +10:00
Ariel Rin
39ad625fa1 Revert "Remove MANIFEST.in, it's redundant the way we use it"
This reverts commit 13e2f4e27d.
2022-06-06 22:59:23 +10:00
Ariel Rin
1db67025bf Version Bump v3.0.0a4 2022-06-06 22:53:37 +10:00
Ariel Rin
13ab6c072a stick with old license_file line to keep pre-commit happy for now 2022-06-06 22:52:20 +10:00
Ariel Rin
cec1dd84ef include subpackages in setuptools package discovery 2022-06-06 12:39:42 +00:00
Ariel Rin
fae5805322 Version Bump v3.0.0a3 2022-06-06 11:41:26 +00:00
Ariel Rin
16ea9500be Fix Classifiers in new format 2022-06-06 11:38:04 +00:00
Ariel Rin
18f5dc0f47 Version Bump 3.0.0a2 2022-06-06 11:16:06 +00:00
Ariel Rin
ba3e941fe8 Merge branch 'issue/1335-aa-3x--django-4-ghost-migration-for-mumble' into 'v3.x'
Override abstract and explicitly set `related_name` for `MumbleUser`

See merge request allianceauth/allianceauth!1416
2022-06-06 10:56:42 +00:00
Peter Pfeufer
4c416b03b1 Override abstract and explicitly set related_name for MumbleUser 2022-06-06 10:56:42 +00:00
Ariel Rin
e2abb64171 Merge branch 'body-bottom-margin' into 'v3.x'
Bottom margin added to body

See merge request allianceauth/allianceauth!1415
2022-06-06 10:49:23 +00:00
Peter Pfeufer
c66aa13ae1 Bottom margin added to body 2022-06-06 10:49:22 +00:00
Ariel Rin
2b31be789d Merge branch 'fix-issue-1336' into 'master'
Fix: Broken docs generation on readthedocs.org

Closes #1336

See merge request allianceauth/allianceauth!1423
2022-06-06 10:48:16 +00:00
Erik Kalkoken
bf1b4bb549 Fix: Broken docs generation on readthedocs.org 2022-06-06 10:48:16 +00:00
Ariel Rin
3b539c8577 dependency bumps for Django 4.0 2022-06-04 06:10:05 +00:00
Ariel Rin
dd42b807f0 Version Bump 2.12.1 2022-05-13 00:19:45 +10:00
Ariel Rin
542fbafd98 Merge branch 'cherry-pick-4836559a' into 'v2.12.x'
Merge branch 'fix-decimal_widthratio-template-tag' into 'v2.12.x'

See merge request allianceauth/allianceauth!1420
2022-05-12 14:14:01 +00:00
Ariel Rin
37b9f5c882 Merge branch 'fix-decimal_widthratio-template-tag' into 'v3.x'
[FIX] Division by zero in decimal_widthratio template tag

See merge request allianceauth/allianceauth!1419

(cherry picked from commit 4836559abe)

8dd07b97 [FIX] Devision by zero in decimal_widthratio template tag
17b06c88 Make it a string in accordance to the return value type
2022-05-12 13:33:45 +00:00
Ariel Rin
4836559abe Merge branch 'fix-decimal_widthratio-template-tag' into 'v3.x'
[FIX] Division by zero in decimal_widthratio template tag

See merge request allianceauth/allianceauth!1419
2022-05-12 13:33:17 +00:00
Peter Pfeufer
17b06c8845 Make it a string in accordance to the return value type 2022-05-12 13:31:06 +02:00
Peter Pfeufer
8dd07b97c7 [FIX] Devision by zero in decimal_widthratio template tag
Fixes: #1343
2022-05-12 13:27:26 +02:00
Ariel Rin
9e139495ac Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-05-12 20:50:41 +10:00
Ariel Rin
5bde9a6952 Version Bump 2.12.0 2022-05-12 18:54:22 +10:00
Ariel Rin
23ad9d02d3 Merge branch 'cherry-pick-7fa76d6d' into 'v2.11.x'
Update GitLab CI to conform with the changes to artifacts collection, 2.11.x backport

See merge request allianceauth/allianceauth!1418
2022-05-12 04:30:07 +00:00
Ariel Rin
f99878cc29 Update .gitlab-ci.yml 2022-05-12 04:07:43 +00:00
Ariel Rin
e64431b06c Merge branch 'update-gitlab-ci' into 'v3.x'
Update GitLab CI to conform with the changes to artifacts collection

See merge request allianceauth/allianceauth!1417

(cherry picked from commit 7fa76d6d37)

a3cce358 Update GitLab CI to conform with the changes to artifacts collection
2022-05-12 04:06:04 +00:00
Ariel Rin
0b2993c1c3 Merge branch 'improve_notifications_2' into 'v2.11.x'
Improve notifications

See merge request allianceauth/allianceauth!1411
2022-05-12 04:02:17 +00:00
Erik Kalkoken
75bccf1b0f Improve notifications 2022-05-12 04:02:17 +00:00
Ariel Rin
7fa76d6d37 Merge branch 'update-gitlab-ci' into 'v3.x'
Update GitLab CI to conform with the changes to artifacts collection

See merge request allianceauth/allianceauth!1417
2022-05-12 04:00:34 +00:00
Ariel Rin
945bc92898 Merge branch 'admin-dash-improvement' into 'v2.11.x'
Improve Admin Celery Bar

See merge request allianceauth/allianceauth!1414
2022-05-12 03:57:02 +00:00
Ariel Rin
ec7d14a839 Merge branch 'fix_issue_1222' into 'v2.11.x'
Close security loopholes to make non-superuser admins usable

See merge request allianceauth/allianceauth!1413
2022-05-12 03:56:22 +00:00
Erik Kalkoken
dd1a368ff6 Close security loopholes to make non-superuser admins usable 2022-05-12 03:56:22 +00:00
Peter Pfeufer
a3cce35881 Update GitLab CI to conform with the changes to artifacts collection
See: https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscobertura-removed
2022-05-11 17:51:43 +02:00
colcrunch
54085617dc Add a few pixels of margin-top to bar labels to better center them. 2022-04-16 15:46:01 -04:00
colcrunch
8cdc5af453 Improve celery bar by using decimalized width values (2 decimal places) to reduce likelyhood of an empty portion of the bar. 2022-04-16 15:44:53 -04:00
Ariel Rin
da93940e13 Just an empty Tag Commit, because 2.11.2 bump went wonky 2022-03-29 14:48:39 +10:00
Ariel Rin
f53b43d9dc Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.11.x 2022-03-29 14:47:40 +10:00
Ariel Rin
497a167ca7 Version Bump v2.11.2 2022-03-29 14:46:59 +10:00
Ariel Rin
852c5a3037 Bump Django-ESI to 4.x, inc breaking CCP change in 4.0.1 2022-03-29 14:40:30 +10:00
Ariel Rin
90f6777a7a Version Bump 2.11.1 2022-03-20 14:42:39 +10:00
Ariel Rin
ed4f71a283 Merge branch 'remove-manifest-file' into 'v3.x'
Remove MANIFEST.in, it's redundant the way we use it

See merge request allianceauth/allianceauth!1410
2022-03-20 04:36:09 +00:00
Peter Pfeufer
13e2f4e27d Remove MANIFEST.in, it's redundant the way we use it
See: https://packaging.python.org/en/latest/guides/using-manifest-in/
2022-03-13 09:42:22 +01:00
Ariel Rin
17343dfeae Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-03-09 20:57:07 +10:00
Ariel Rin
4b5978fb58 Merge branch 'version-links' into 'v3.x'
Link "Latest Stable" and "Latest Pre-Release" versions to their tags on GitLab

See merge request allianceauth/allianceauth!1405
2022-03-09 10:05:05 +00:00
Ariel Rin
a8d890abaf Merge branch 'improve_task_statistics' into 'master'
Improve task statistics

See merge request allianceauth/allianceauth!1409
2022-03-09 10:04:14 +00:00
Erik Kalkoken
79379b444c Improve task statistics 2022-03-09 10:04:13 +00:00
Ariel Rin
ace1de5c68 Merge branch 'fix-docker-new-redis' into 'master'
Fix docker for new redis

See merge request allianceauth/allianceauth!1406
2022-03-09 10:02:01 +00:00
Ariel Rin
0a30eea3b4 Merge branch 'deprecated-settings' into 'v3.x'
Remove deprecated settings

See merge request allianceauth/allianceauth!1407
2022-03-09 09:53:15 +00:00
Ariel Rin
a15d281c40 Merge branch 'switch-to-setup.cfg' into 'v3.x'
Switch to setup.cfg due to deprecation of setup.py

See merge request allianceauth/allianceauth!1408
2022-03-09 09:50:40 +00:00
Peter Pfeufer
6846bb7cdc Switch to setup.cfg due to deprecation of setup.py 2022-03-09 09:50:40 +00:00
Peter Pfeufer
1d240a40dd Remove deprecated settings
Examples:

RemovedInDjango50Warning: The USE_L10N setting is deprecated. Starting with Django 5.0, localized formatting of data will always be enabled. For example Django will display numbers and dates using the format of the current locale.
  warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)

RemovedInDjango41Warning: 'allianceauth' defines default_app_config = 'allianceauth.apps.AllianceAuthConfig'. Django now detects this configuration automatically. You can remove default_app_config.
  app_config = AppConfig.create(entry)
2022-03-03 12:06:16 +01:00
Kevin McKernan
5d6128e9ea remove collectstatic command from dockerfile 2022-03-01 13:23:49 -07:00
Ariel Rin
c377bcec5f add redis to readthedocs container 2022-03-01 07:40:15 +00:00
Ariel Rin
9b74fb4dbd Attempted Docs Fixes 2022-03-01 03:33:12 +00:00
Peter Pfeufer
6744c0c143 Link Latest stable and Latest pre-release versions to their tags on git 2022-02-28 21:19:33 +01:00
Ariel Rin
131cc5ed0a Version Bump 2.11.0 2022-02-26 17:26:55 +10:00
Ariel Rin
9297bed43f Version Bump 2.10.2 2022-02-26 16:37:20 +10:00
Ariel Rin
b2fddc683a Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.10.x 2022-02-26 16:32:45 +10:00
Ariel Rin
1e9f5e6430 Version Bump 3.0.0a1 2022-02-26 06:26:36 +00:00
Ariel Rin
ceaa064e62 Merge branch 'usersettings' into 'v3.x'
Persistent User Settings

See merge request allianceauth/allianceauth!1333
2022-02-26 06:19:38 +00:00
Ariel Rin
1aad3e4512 Persistent User Settings 2022-02-26 06:19:38 +00:00
Ariel Rin
f83c3c2811 Merge branch 'ErikKalkoken/allianceauth-fix_character_names' into v3.x 2022-02-26 15:53:46 +10:00
Ariel Rin
a23ec6d318 switch new task module to new redis method 2022-02-26 15:52:15 +10:00
Ariel Rin
ecc53888bc Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-02-26 15:23:01 +10:00
Ariel Rin
9af634d16a Merge branch 'fix_show_available_groups_for_user_only' into 'master'
Fix: Users can be assigned to groups depite not matching state restrictions

See merge request allianceauth/allianceauth!1402
2022-02-26 05:19:45 +00:00
Erik Kalkoken
a68163caa3 Fix: Users can be assigned to groups depite not matching state restrictions 2022-02-26 05:19:45 +00:00
Ariel Rin
00770fd034 Merge branch 'improve_celery_info_on_dashboard' into 'master'
Improve celery infos on Dashboard

See merge request allianceauth/allianceauth!1384
2022-02-26 05:15:30 +00:00
Erik Kalkoken
01164777ed Improve celery infos on Dashboard 2022-02-26 05:15:30 +00:00
Ariel Rin
e54f72091f specific known working dependency commit 2022-02-26 14:27:00 +10:00
Ariel Rin
00f5e3e1e0 Version Bump 2.10.1 2022-02-21 00:02:12 +10:00
Ariel Rin
8b2527f408 Merge branch 'capsleekxmpp' into 'master'
Cap sleekxmpp to 1.3.2

See merge request allianceauth/allianceauth!1401
2022-02-20 13:44:27 +00:00
Ariel Rin
b7500e4e4e Cap sleekxmpp to 1.3.2 2022-02-20 13:44:27 +00:00
Kevin McKernan
4f4bd0c419 add note to docker README about Apple M1 support 2022-02-20 23:41:12 +10:00
Ariel Rin
75b5b28804 use Django-ESI 4.0.0a1, instead of Ariels branch 2022-02-20 23:24:26 +10:00
Ariel Rin
f81a2ed237 Merge branch 'django4' into 'v3.x'
dropin replace sleeksmpp with slixmpp, alter test

See merge request allianceauth/allianceauth!1399
2022-02-20 13:19:19 +00:00
Ariel Rin
49e01157e7 dropin replace sleeksmpp with slixmpp, alter test 2022-02-20 13:19:19 +00:00
Ariel Rin
28420a729e Merge branch 'html5-fixes' into 'v3.x'
[FIX] Use proper HTML5 tags instead of self-closing XML/(X)HTML tags

See merge request allianceauth/allianceauth!1398
2022-02-11 02:10:48 +00:00
Peter Pfeufer
52a4cf8d52 [FIX] Use proper HTML5 tags instead of self-closing XML/XHTML tags 2022-02-08 20:22:53 +01:00
Ariel Rin
703c2392a9 Merge branch 'django4' into 'v3.x'
v2.10.x Uplifts, DJ4, Py3.8 + More

See merge request allianceauth/allianceauth!1387
2022-02-08 13:04:45 +00:00
Ariel Rin
18c9a66437 Merge branch 'fix-ifequal-errors' into 'django4'
Fix ifequal errors

See merge request soratidus999/allianceauth!9
2022-02-06 07:16:56 +00:00
Ariel Rin
9687d57de9 Merge branch 'update-url-configs' into 'django4'
Switch to `path`, use `re_path` only when really needed

See merge request soratidus999/allianceauth!8
2022-02-06 07:16:46 +00:00
Peter Pfeufer
60c2e57d83 Fix ifequal errors 2022-02-02 16:12:43 +01:00
Peter Pfeufer
b14bff0145 We should do this properly .. 2022-02-02 15:28:36 +01:00
Peter Pfeufer
9166886665 That one slipped through the cracks ... 2022-02-02 15:27:07 +01:00
Peter Pfeufer
c74010d441 Docs updated 2022-02-02 15:25:45 +01:00
Peter Pfeufer
640a21e4db Switch to path, use re_path only when really needed 2022-02-02 15:09:48 +01:00
Ariel Rin
8ae4e02012 Merge branch 'docker-bump-version' into 'v2.10.x'
Bump version for Docker deployment to v2.10.x.

See merge request allianceauth/allianceauth!1396
2022-02-02 13:26:33 +00:00
Weyland
cc9a07197d Bump version for Docker deployment to v2.10.x. 2022-02-02 13:30:05 +01:00
Ariel Rin
fd442a5735 django.conf.urls.url is deprecated 2022-02-02 21:56:01 +10:00
Ariel Rin
c7b99044bc django.conf.urls.url is deprecated, more to fix 2022-02-02 21:39:37 +10:00
Ariel Rin
234451a7d4 temporarily use django-esi MR 2022-02-02 21:37:01 +10:00
Ariel Rin
ffff904ab1 Pull specific commit from git temporarily 2022-02-02 15:18:20 +10:00
Ariel Rin
d71a26220c Merge branch 'v3.x' of https://gitlab.com/allianceauth/allianceauth into django4 2022-02-02 14:24:47 +10:00
Ariel Rin
beeeb8dc5d Merge branch 'v3.x' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-02-02 14:17:15 +10:00
Ariel Rin
19244cc4c6 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v3.x 2022-02-02 14:16:46 +10:00
Ariel Rin
cc94ba6b5e ensure latest django patch 2022-02-02 14:16:33 +10:00
Ariel Rin
c9926cc877 Merge tag 'v2.10.0' of https://gitlab.com/allianceauth/allianceauth into django4 2022-02-02 14:15:32 +10:00
Ariel Rin
1d14e1b0af Merge branch 'rediscache' into 'v3.x'
Swap the Redis Cache client

See merge request allianceauth/allianceauth!1394
2022-02-02 04:12:04 +00:00
Aaron Kable
297da44a5a Swap the Redis Cache client 2022-02-02 04:12:04 +00:00
Ariel Rin
402ff53a5c Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into django4 2022-02-02 12:38:24 +10:00
ErikKalkoken
2d6e4a0df1 Merge branch 'master' into fix_character_names 2022-02-01 00:31:25 +01:00
Ariel Rin
f18dd1029b Version Bump v2.10.0 2022-01-31 20:58:09 +10:00
Ariel Rin
fd8d43571a Merge branch 'analytics' into 'master'
Analytics - Extra Ignore Path

See merge request allianceauth/allianceauth!1347
2022-01-31 09:23:43 +00:00
Ariel Rin
13e88492f1 Analytics - Extra Ignore Path 2022-01-31 09:23:43 +00:00
Ariel Rin
38df580a56 Merge branch 'analytics_update' into 'master'
Add setting to disable analytics

See merge request allianceauth/allianceauth!1373
2022-01-27 05:14:12 +00:00
Erik Kalkoken
ba39318313 Add setting to disable analytics 2022-01-27 05:14:11 +00:00
Ariel Rin
d8c6035405 Merge branch 'ts3_reserved_groups' into 'master'
Implement reserved group names in Teamspeak3 service module.

See merge request allianceauth/allianceauth!1380
2022-01-27 05:10:22 +00:00
Ariel Rin
2ef3da916b Merge branch 'datatablessavestate' into 'master'
Add DataTables stateSave feature

See merge request allianceauth/allianceauth!1374
2022-01-27 05:05:37 +00:00
Ariel Rin
d32d8b26ce Merge branch 'delete_characters' into 'master'
Fix: Can not update biomassed characters

See merge request allianceauth/allianceauth!1381
2022-01-27 05:02:57 +00:00
Erik Kalkoken
f348b1a34c Fix: Can not update biomassed characters 2022-01-27 05:02:57 +00:00
Ariel Rin
86aaa3edda Merge branch 'fix-grafana-image-2' into 'master'
fix grafana image again, thanks grafana for not tagging your new images properly

See merge request allianceauth/allianceauth!1393
2022-01-27 04:57:40 +00:00
Ariel Rin
26017056c7 Merge branch 'evetime-js-update' into 'master'
Evetime js update

See merge request allianceauth/allianceauth!1395
2022-01-27 04:35:15 +00:00
Peter Pfeufer
e39a3c072b Evetime js update 2022-01-27 04:35:15 +00:00
Kevin McKernan
827291dda4 fix grafana image again, thanks grafana for not tagging your new images properly 2022-01-07 10:48:50 -07:00
ErikKalkoken
defcfa3316 Character names are not unique 2022-01-05 19:47:15 +01:00
ErikKalkoken
3209b71b0a Fix imports and flake8 issues 2022-01-05 19:28:44 +01:00
Ariel Rin
80b3ca0a1e Merge branch 'v2.10.x' of https://gitlab.com/allianceauth/allianceauth into django4 2021-12-29 16:35:37 +10:00
Ariel Rin
8351bd2fa3 Forgot to remove a 3.7 test 2021-12-29 16:34:14 +10:00
Ariel Rin
255966ed3b Secret Detection was split from SAST 2021-12-29 16:32:42 +10:00
Ariel Rin
8d6ebf4770 Merge branch 'v2.10.x' of https://gitlab.com/allianceauth/allianceauth into django4 2021-12-29 15:52:39 +10:00
Ariel Rin
2ca752bf78 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into django4 2021-12-29 15:52:01 +10:00
Ariel Rin
79e1192f67 Update Pre-Commit 2021-12-29 15:45:33 +10:00
Ariel Rin
ff610efc84 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.10.x 2021-12-28 21:58:38 +10:00
Ariel Rin
ea8958ccc3 Version Bump v2.9.4 2021-12-28 21:56:46 +10:00
Ariel Rin
20554df857 Merge branch 'jqueryui' into 'master'
Missing jQuery-UI Images

See merge request allianceauth/allianceauth!1391
2021-12-28 11:55:37 +00:00
Ariel Rin
750f43eaf0 Missing jQuery-UI Images 2021-12-28 11:55:37 +00:00
Ariel Rin
09cf28ec9f Merge branch 'jqueryui' into 'master'
Removing Themeing from jQuery UI

See merge request allianceauth/allianceauth!1386
2021-12-28 11:30:05 +00:00
Ariel Rin
b61746b3cb Removing Themeing from jQuery UI 2021-12-28 11:30:05 +00:00
Ariel Rin
22c22fafeb Merge branch 'py3.11' into 'master'
Python 3.11 Testing

See merge request allianceauth/allianceauth!1388
2021-12-28 11:29:33 +00:00
Ariel Rin
577c4395c4 Python 3.11 Testing 2021-12-28 11:29:33 +00:00
Ariel Rin
d241f476f7 Merge branch 'fix-grafana-image' into 'master'
update grafana image

See merge request allianceauth/allianceauth!1389
2021-12-28 10:43:43 +00:00
Kevin McKernan
5832ed0c30 update grafana image 2021-12-28 10:43:43 +00:00
Ariel Rin
6b68a739ef Initial spam of version bumps 2021-12-24 17:43:15 +10:00
Ariel Rin
909bd0ba15 Fix deprecations removed in dj4 2021-12-24 17:42:53 +10:00
Ariel Rin
05110abc59 remove 3.7 testing 2021-12-24 16:25:29 +10:00
Ariel Rin
bd9ea225be Rogue comment annoying MRs 2021-12-24 05:02:46 +00:00
Ariel Rin
4a575dd70c Merge branch 'groupmanagement-auto-leave-improvements' into 'master'
Hide "Leave Requests" tab when GROUPMANAGEMENT_AUTO_LEAVE = True

See merge request allianceauth/allianceauth!1378
2021-12-24 04:54:55 +00:00
Ariel Rin
b80ee16a7c Merge branch 'fix_max_notifications_warning' into 'master'
Fix: NOTIFICATIONS_MAX_PER_USER warning when not set

See merge request allianceauth/allianceauth!1383
2021-12-24 04:53:40 +00:00
Ariel Rin
c888371e6c Merge branch 'fix_esi_spec' into 'master'
Add missing ESI operation to eveonline swagger spec file.

See merge request allianceauth/allianceauth!1382
2021-12-24 04:51:49 +00:00
Ariel Rin
a64d99eb91 Target Py3.8 2021-12-24 14:48:30 +10:00
Ariel Rin
0e45403195 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.10.x 2021-12-24 14:41:09 +10:00
Ariel Rin
e16a9ffe65 update pre-commit 2021-12-24 14:26:15 +10:00
Ariel Rin
57de122ef8 Move away frfom 3.6 even for pre-commit 2021-12-24 14:23:41 +10:00
Adarnof
8de2c3bfcb Update name of serverquery IP file changed in TS3 v3.13.0
Changelog indicates old filenames are still accepted, but newly installed servers come with the new file names.
Closes #1298
2021-12-16 22:23:15 -05:00
Adarnof
6688f73565 Use integer teamspeak group IDs when filtering. 2021-12-15 23:54:53 -05:00
ErikKalkoken
7d929cb6e2 Fix: NOTIFICATIONS_MAX_PER_USER warning when not set 2021-12-09 18:12:18 +01:00
Adarnof
72740b9e4d Prevent assignment of reserved groups to AuthTSgroup mappings.
Implemented in TS group updates to prevent their creation / delete once
reserved, and the admin site for when a reserved group name is created
but before the TS group sync occurs.
2021-12-08 23:41:10 -05:00
Adarnof
f7d279fa16 Add missing ESI operation to minimized spec file. My bad. 2021-12-07 23:42:02 -05:00
Ariel Rin
ff7c9c48f3 Merge branch 'v2.9.x' of https://gitlab.com/allianceauth/allianceauth 2021-12-02 02:26:33 +10:00
Adarnof
d11832913d Implement reserved group names in Teamspeak3 service module.
Closes #1302
2021-12-01 00:50:29 -05:00
Ariel Rin
724e0e83f2 Merge branch 'fix-docker-script' into 'v2.9.x'
fix download script

See merge request allianceauth/allianceauth!1379
2021-11-30 11:44:35 +00:00
Kevin McKernan
333f091f1a fix download script 2021-11-29 13:43:32 -07:00
Peter Pfeufer
cfbb0b993a Behavior added to documentation 2021-11-28 19:07:14 +01:00
Peter Pfeufer
582b6754a4 Hide "Leave Requests" tab when GROUPMANAGEMENT_AUTO_LEAVE = True 2021-11-28 18:12:02 +01:00
Ariel Rin
7767c46bf4 Version Bump v2.9.3 2021-11-28 15:37:17 +00:00
Ariel Rin
bf34cef896 Merge branch 'transifex' into 'v2.9.x'
Update from Transifex

See merge request allianceauth/allianceauth!1377
2021-11-28 15:36:14 +00:00
Ariel Rin
c085ec6860 Update from Transifex 2021-11-29 01:34:13 +10:00
Ariel Rin
5f5d0316b2 Merge branch 'transifex' into 'v2.9.x'
Update from Transifex

See merge request allianceauth/allianceauth!1376
2021-11-28 15:25:50 +00:00
Ariel Rin
f9ec64c3ad Update from Transifex 2021-11-28 15:25:50 +00:00
Ariel Rin
0dfd0ad4b0 Django support for Python 3.10 was added to django 3.2.9, missed this in MR !1367 2021-11-28 15:17:53 +00:00
Ariel Rin
e88e11b9ba Merge branch 'jqueryui' into 'v2.9.x'
Add jqueryui Bundles

See merge request allianceauth/allianceauth!1372
2021-11-28 14:50:22 +00:00
Ariel Rin
7a2a79ca7b Merge branch 'groups_blacklist' into 'v2.9.x'
Add blacklist for groups and ignore blacklisted roles in Discord service

See merge request allianceauth/allianceauth!1371
2021-11-28 14:48:49 +00:00
Erik Kalkoken
4c0683c484 Add blacklist for groups and ignore blacklisted roles in Discord service 2021-11-28 14:48:49 +00:00
Ariel Rin
dfe62db8ee add datatables savestate feature 2021-11-27 23:02:33 +10:00
Ariel Rin
025c824fbb Merge branch 'build-docker-image' into 'v2.9.x'
Build docker image in gitlab, add docs for turn key docker setup

See merge request allianceauth/allianceauth!1356
2021-11-27 04:45:21 +00:00
Kevin McKernan
930c5d7c7a Build docker image in gitlab, add docs for turn key docker setup 2021-11-27 04:45:21 +00:00
Ariel Rin
8b8dcc0127 Merge branch 'group-auto-leave-enhancements' into 'v2.9.x'
Group auto leave enhancements

See merge request allianceauth/allianceauth!1369
2021-11-27 03:45:31 +00:00
Peter Pfeufer
4ad8e88bd8 Old reference to AUTO_LEAVE removed 2021-11-27 04:40:23 +01:00
Ariel Rin
89d4640e92 Merge branch 'py310' into 'v2.9.x'
Python 3.10 Support

See merge request allianceauth/allianceauth!1367
2021-11-27 03:35:40 +00:00
Ariel Rin
60b12bad61 Python 3.10 Support 2021-11-27 03:35:40 +00:00
Ariel Rin
2595fa5c51 Merge branch 'gitlab-api-request-error-handling' into 'v2.9.x'
Added error handling when fetching from GitLab API

See merge request allianceauth/allianceauth!1370
2021-11-27 03:31:39 +00:00
Ariel Rin
3e487e5f13 Add jqueryui bundles 2021-11-24 17:56:15 +10:00
Peter Pfeufer
b6d6c68e54 Fix tests
Thanks to @ErikKalkoken for helping here
2021-11-23 02:31:18 +01:00
Peter Pfeufer
49548d6f9f test_can_handle_connection_timeout added 2021-11-23 01:23:34 +01:00
Peter Pfeufer
8faadc23b0 Set logger to warning to not trigger a notification to admins every time 2021-11-23 01:02:08 +01:00
Peter Pfeufer
20da1ebfab request.raise_for_status() re-added 2021-11-22 18:56:33 +01:00
Peter Pfeufer
03305c72c7 Added error handling when fetching from GitLab API 2021-11-22 18:10:02 +01:00
Peter Pfeufer
a636fd1cf0 Add it to the docu 2021-11-21 18:24:04 +01:00
Peter Pfeufer
d8797a8dc6 Prefix the setting so it is clear where it belongs to 2021-11-21 18:18:05 +01:00
Ariel Rin
363e18e15d Merge branch 'more_group_notifications' into 'v2.9.x'
Add option to notify approvers about new group requests

See merge request allianceauth/allianceauth!1363
2021-11-20 01:32:20 +00:00
Erik Kalkoken
982cac8c43 Add option to notify approvers about new group requests 2021-11-20 01:32:20 +00:00
Ariel Rin
fabf64b838 Merge branch 'cherry-pick-f17ebbed' into 'v2.9.x'
Use danger and error message tags to render correctly on admin site.

Closes #1305

See merge request allianceauth/allianceauth!1366
2021-11-20 01:27:26 +00:00
Ariel Rin
e3e6ebe953 Merge branch 'improve_menu_bar' into 'v2.9.x'
Remove seconds from eve clock in menu bar

See merge request allianceauth/allianceauth!1365
2021-11-20 01:26:24 +00:00
Ariel Rin
7ad9b52546 Merge branch 'fix_tox_ini' into 'v2.9.x'
Fix tox environment config

See merge request allianceauth/allianceauth!1364
2021-11-20 01:16:13 +00:00
Adarnof
abb5090d63 Use danger and error message tags to render correctly on admin site.
Closes #1305


(cherry picked from commit f17ebbede6)
2021-11-20 01:14:07 +00:00
Ariel Rin
52ae05d057 Merge branch 'messages' into 'master'
Use danger and error tags to render messages correctly on admin site.

See merge request allianceauth/allianceauth!1362
2021-11-20 01:13:27 +00:00
ErikKalkoken
3cd216d119 Remove seconds from eve clock in menu bar 2021-11-11 21:14:48 +01:00
ErikKalkoken
581edc0a38 Fix environment config in tox.ini 2021-11-11 17:37:27 +01:00
Adarnof
f17ebbede6 Use danger and error message tags to render correctly on admin site.
Closes #1305
2021-11-03 21:02:03 -04:00
Ariel Rin
2bd2c09c23 Version Bump 2.9.2 2021-10-30 05:23:48 +00:00
Ariel Rin
c1cf859ec9 Merge branch 'srp-provider' into 'v2.9.x'
Follow up test fixes missed from !1358

See merge request allianceauth/allianceauth!1361
2021-10-30 05:22:38 +00:00
Ariel Rin
1ebf864998 adapt tests 2021-10-30 05:22:38 +00:00
Ariel Rin
0948e34e48 Merge branch 'transifex' into 'v2.9.x'
Update from Transifex

See merge request allianceauth/allianceauth!1360
2021-10-30 04:54:00 +00:00
Ariel Rin
f9a1ea9c83 Update from Transifex 2021-10-30 04:54:00 +00:00
Ariel Rin
d02a8ebc1b Merge branch 'dependencies' into 'v2.9.x'
Dependencies, CDN imports, CSS optimization

See merge request allianceauth/allianceauth!1350
2021-10-30 04:35:55 +00:00
Ariel Rin
5c2625b648 Dependencies, CDN imports, CSS optimization 2021-10-30 04:35:55 +00:00
Ariel Rin
13174d006e Merge branch 'issue/1211-feature-request-optimer-labels' into 'v2.9.x'
Add types and description to optimers

Closes #1211

See merge request allianceauth/allianceauth!1357
2021-10-30 04:34:49 +00:00
Peter Pfeufer
2c59cc4cc3 Add types and description to optimers 2021-10-30 04:34:49 +00:00
Ariel Rin
41c81d3226 Merge branch 'chunkupdateerrortype' into 'v2.9.x'
Correct logging of affiliation endpoint to Info not Error

See merge request allianceauth/allianceauth!1359
2021-10-30 04:32:12 +00:00
Ariel Rin
2ec7d3b4d9 Merge branch 'srp-provider' into 'v2.9.x'
Decouple allianceauth.srp ESI Provider from allianceauth.eveonline

See merge request allianceauth/allianceauth!1358
2021-10-30 04:29:28 +00:00
Ariel Rin
b607b73598 chunk updates to be info, not error 2021-10-30 12:43:49 +10:00
Ariel Rin
f52791cd1f document swagger spec operations 2021-10-27 18:18:23 +10:00
Ariel Rin
d829facbd4 use own esi provider for itemtypes 2021-10-27 18:14:18 +10:00
Ariel Rin
44ac3a9ff2 decouple srp provider from eveonline 2021-10-27 17:54:14 +10:00
Ariel Rin
a19302babc Merge branch 'chunking_updates' into 'master'
Breakout Model updates into separate tasks

See merge request allianceauth/allianceauth!1343
2021-10-19 00:49:39 +00:00
Aaron Kable
18a627b01e Breakout Model updates into separate tasks 2021-10-19 00:49:39 +00:00
Ariel Rin
eddb5480e9 Merge branch 'v2.9.x' into 'master'
Bring up Master to 2.9.0

See merge request allianceauth/allianceauth!1344
2021-10-18 01:58:24 +00:00
Ariel Rin
5b26757662 Merge branch 'features/kick-from-discord-admin' into 'master'
Add override to delete the user from discord itself

See merge request allianceauth/allianceauth!1337
2021-10-17 07:34:22 +00:00
580 changed files with 25744 additions and 12426 deletions

View File

@@ -22,3 +22,7 @@ indent_style = tab
[*.bat]
indent_style = tab
[{Dockerfile,*.dockerfile}]
indent_style = space
indent_size = 4

4
.gitignore vendored
View File

@@ -38,7 +38,6 @@ htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
@@ -70,9 +69,6 @@ celerybeat-schedule
#gitlab configs
.gitlab/
#transifex
.tx/
#other
.flake8
.pylintrc

View File

@@ -1,12 +1,20 @@
.only-default: &only-default
only:
- master
- branches
- merge_requests
stages:
- pre-commit
- gitlab
- test
- deploy
- pre-commit
- gitlab
- test
- deploy
- docker
include:
- template: Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
before_script:
- apt-get update && apt-get install redis-server -y
@@ -15,8 +23,9 @@ before_script:
- pip install wheel tox
pre-commit-check:
<<: *only-default
stage: pre-commit
image: python:3.6-buster
image: python:3.11-bullseye
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
@@ -33,75 +42,286 @@ sast:
dependency_scanning:
stage: gitlab
before_script:
- apt-get update && apt-get install redis-server libmariadb-dev -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
- apt-get update && apt-get install redis-server libmariadb-dev -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
test-3.7-core:
image: python:3.7-bullseye
script:
- tox -e py37-core
artifacts:
when: always
reports:
cobertura: coverage.xml
secret_detection:
stage: gitlab
before_script: []
test-3.8-core:
<<: *only-default
image: python:3.8-bullseye
script:
- tox -e py38-core
- tox -e py38-core
artifacts:
when: always
reports:
cobertura: coverage.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.9-core:
<<: *only-default
image: python:3.9-bullseye
script:
- tox -e py39-core
- tox -e py39-core
artifacts:
when: always
reports:
cobertura: coverage.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.7-all:
image: python:3.7-bullseye
test-3.10-core:
<<: *only-default
image: python:3.10-bullseye
script:
- tox -e py37-all
- tox -e py310-core
artifacts:
when: always
reports:
cobertura: coverage.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.11-core:
<<: *only-default
image: python:3.11-bullseye
script:
- tox -e py311-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-pvpy-core:
<<: *only-default
image: pypy:3.9-bullseye
script:
- tox -e pypy-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.12-core:
<<: *only-default
image: python:3.12-rc-bullseye
script:
- tox -e py312-core
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.8-all:
<<: *only-default
image: python:3.8-bullseye
script:
- tox -e py38-all
- tox -e py38-all
artifacts:
when: always
reports:
cobertura: coverage.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.9-all:
<<: *only-default
image: python:3.9-bullseye
script:
- tox -e py39-all
- tox -e py39-all
artifacts:
when: always
reports:
cobertura: coverage.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.10-all:
<<: *only-default
image: python:3.10-bullseye
script:
- tox -e py310-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test-3.11-all:
<<: *only-default
image: python:3.11-bullseye
script:
- tox -e py311-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
test-pvpy-all:
<<: *only-default
image: pypy:3.9-bullseye
script:
- tox -e pypy-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.12-all:
<<: *only-default
image: python:3.12-rc-bullseye
script:
- tox -e py312-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
build-test:
stage: test
image: python:3.11-bullseye
before_script:
- python -m pip install --upgrade pip
- python -m pip install --upgrade build
- python -m pip install --upgrade setuptools wheel
script:
- python -m build
artifacts:
when: always
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- dist/*
expire_in: 1 year
test-docs:
<<: *only-default
image: python:3.11-bullseye
script:
- tox -e docs
deploy_production:
stage: deploy
image: python:3.9-bullseye
image: python:3.11-bullseye
before_script:
- pip install twine wheel
- python -m pip install --upgrade pip
- python -m pip install --upgrade build
- python -m pip install --upgrade setuptools wheel twine
script:
- python setup.py sdist bdist_wheel
- twine upload dist/*
- python -m build
- python -m twine upload dist/*
rules:
- if: $CI_COMMIT_TAG
build-image:
before_script: []
image: docker:24.0
stage: docker
services:
- docker:24.0-dind
script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_SHORT_SHA
CURRENT_TAG=$CI_REGISTRY_IMAGE/auth:$CI_COMMIT_TAG
MINOR_TAG=$CI_REGISTRY_IMAGE/auth:$(echo $CI_COMMIT_TAG | cut -d '.' -f 1-2)
MAJOR_TAG=$CI_REGISTRY_IMAGE/auth:$(echo $CI_COMMIT_TAG | cut -d '.' -f 1)
LATEST_TAG=$CI_REGISTRY_IMAGE/auth:latest
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder
docker buildx build .
--tag $IMAGE_TAG
--tag $CURRENT_TAG
--tag $MINOR_TAG
--tag $MAJOR_TAG
--tag $LATEST_TAG
--file docker/Dockerfile
--platform linux/amd64,linux/arm64
--push
--build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
rules:
- if: $CI_COMMIT_TAG
when: delayed
start_in: 10 minutes
build-image-dev:
before_script: []
image: docker:24.0
stage: docker
services:
- docker:24.0-dind
script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_COMMIT_BRANCH-$CI_COMMIT_SHORT_SHA
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder
docker buildx build .
--tag $IMAGE_TAG
--file docker/Dockerfile
--platform linux/amd64,linux/arm64
--push
--build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
rules:
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
when: manual
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME != ""'
when: never
build-image-mr:
before_script: []
image: docker:24.0
stage: docker
services:
- docker:24.0-dind
script: |
CURRENT_DATE=$(echo $CI_COMMIT_TIMESTAMP | head -c 10 | tr -d -)
IMAGE_TAG=$CI_REGISTRY_IMAGE/auth:$CURRENT_DATE-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME-$CI_COMMIT_SHORT_SHA
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder
docker buildx build .
--tag $IMAGE_TAG
--file docker/Dockerfile
--platform linux/amd64,linux/arm64
--push
--build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual
- if: '$CI_PIPELINE_SOURCE != "merge_request_event"'
when: never

View File

@@ -5,30 +5,81 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
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.3.54
rev: 2.7.2
hooks:
- id: editorconfig-checker
exclude: ^(LICENSE|allianceauth\/static\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))
exclude: |
(?x)(
LICENSE|
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/asottile/pyupgrade
rev: v2.29.0
rev: v3.10.1
hooks:
- id: pyupgrade
args: [ --py37-plus ]
args: [ --py38-plus ]
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.14.0
hooks:
- id: django-upgrade
args: [--target-version=4.2]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.3.0
hooks:
- id: setup-cfg-fmt
args: [ --include-version-classifiers ]

View File

@@ -5,19 +5,25 @@
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
apt_packages:
- redis
tools:
python: "3.8"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml
# Optionally build your docs in additional formats such as PDF and ePub
formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
install:
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
- docs

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

View File

@@ -1,7 +0,0 @@
include LICENSE
include README.md
include MANIFEST.in
graft allianceauth
global-exclude __pycache__
global-exclude *.py[co]

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,8 +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__ = '2.9.1'
__version__ = '4.0.0a3'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}'
default_app_config = 'allianceauth.apps.AllianceAuthConfig'

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.analytics.apps.AnalyticsConfig'

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsPath, AnalyticsTokens
from .models import AnalyticsIdentifier, AnalyticsTokens
@admin.register(AnalyticsIdentifier)
@@ -13,9 +13,3 @@ class AnalyticsIdentifierAdmin(admin.ModelAdmin):
class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ]
list_display = ('name', 'type',)
@admin.register(AnalyticsPath)
class AnalyticsPathAdmin(admin.ModelAdmin):
search_fields = ['ignore_path', ]
list_display = ('ignore_path',)

View File

@@ -4,6 +4,3 @@ from django.apps import AppConfig
class AnalyticsConfig(AppConfig):
name = 'allianceauth.analytics'
label = 'analytics'
def ready(self):
import allianceauth.analytics.signals

View File

@@ -3,11 +3,10 @@
"model": "analytics.AnalyticsTokens",
"pk": 1,
"fields": {
"name": "AA Team Public Google Analytics (Universal)",
"name": "AA Team Public Google Analytics (V4)",
"type": "GA-V4",
"token": "UA-186249766-2",
"send_page_views": "False",
"send_celery_tasks": "False",
"token": "G-6LYSMYK8DE",
"secret": "KLlpjLZ-SRGozS5f5wb_kw",
"send_stats": "False"
}
},

View File

@@ -1,49 +0,0 @@
from bs4 import BeautifulSoup
from django.utils.deprecation import MiddlewareMixin
from .models import AnalyticsTokens, AnalyticsIdentifier
from .tasks import send_ga_tracking_web_view
import re
class AnalyticsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
"""Django Middleware: Process Page Views and creates Analytics Celery Tasks"""
analyticstokens = AnalyticsTokens.objects.all()
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
try:
title = BeautifulSoup(
response.content, "html.parser").html.head.title.text
except AttributeError:
title = ''
for token in analyticstokens:
# Check if Page View Sending is Disabled
if token.send_page_views is False:
continue
# Check Exclusions
ignore = False
for ignore_path in token.ignore_paths.values():
ignore_path_regex = re.compile(ignore_path["ignore_path"])
if re.search(ignore_path_regex, request.path) is not None:
ignore = True
if ignore is True:
continue
tracking_id = token.token
locale = request.LANGUAGE_CODE
path = request.path
try:
useragent = request.headers["User-Agent"]
except KeyError:
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
send_ga_tracking_web_view.s(tracking_id=tracking_id,
client_id=client_id,
page=path,
title=title,
locale=locale,
useragent=useragent).\
apply_async(priority=9)
return response

View File

@@ -1,11 +1,11 @@
# Generated by Django 3.1.13 on 2021-10-15 05:02
from django.core.exceptions import ObjectDoesNotExist
from django.db import migrations
def modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
# Add /admin/ and /user_notifications_count/ path to ignore
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
@@ -17,8 +17,19 @@ def modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
def undo_modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
# nothing should need to migrate away here?
return True
#
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.get(token="UA-186249766-2")
try:
admin = AnalyticsPath.objects.get(ignore_path=r"^\/admin\/.*", analyticstokens=token)
user_notifications_count = AnalyticsPath.objects.get(ignore_path=r"^\/user_notifications_count\/.*", analyticstokens=token)
admin.delete()
user_notifications_count.delete()
except ObjectDoesNotExist:
# Its fine if it doesnt exist, we just dont want them building up when re-migrating
pass
class Migration(migrations.Migration):

View File

@@ -0,0 +1,40 @@
# Generated by Django 3.2.8 on 2021-10-19 01:47
from django.core.exceptions import ObjectDoesNotExist
from django.db import migrations
def modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
# Add the /account/activate path to ignore
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
account_activate = AnalyticsPath.objects.create(ignore_path=r"^\/account\/activate\/.*")
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.get(token="UA-186249766-2")
token.ignore_paths.add(account_activate)
def undo_modify_aa_team_token_add_page_ignore_paths(apps, schema_editor):
#
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.get(token="UA-186249766-2")
try:
account_activate = AnalyticsPath.objects.get(ignore_path=r"^\/account\/activate\/.*", analyticstokens=token)
account_activate.delete()
except ObjectDoesNotExist:
# Its fine if it doesnt exist, we just dont want them building up when re-migrating
pass
class Migration(migrations.Migration):
dependencies = [
('analytics', '0005_alter_analyticspath_ignore_path'),
]
operations = [
migrations.RunPython(modify_aa_team_token_add_page_ignore_paths, undo_modify_aa_team_token_add_page_ignore_paths)
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.6 on 2022-08-30 05:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('analytics', '0006_more_ignore_paths'),
]
operations = [
migrations.AddField(
model_name='analyticstokens',
name='secret',
field=models.CharField(blank=True, max_length=254),
),
]

View File

@@ -0,0 +1,64 @@
# Generated by Django 3.1.4 on 2020-12-30 08:53
from django.db import migrations
from django.core.exceptions import ObjectDoesNotExist
def add_aa_team_token(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
AnalyticsPath = apps.get_model('analytics', 'AnalyticsPath')
token = Tokens()
try:
ua_token = Tokens.objects.get(token="UA-186249766-2")
original_send_page_views = ua_token.send_page_views
original_send_celery_tasks = ua_token.send_celery_tasks
original_send_stats = ua_token.send_stats
except ObjectDoesNotExist:
original_send_page_views = True
original_send_celery_tasks = True
original_send_stats = True
try:
user_notifications_count = AnalyticsPath.objects.get(ignore_path=r"^\/user_notifications_count\/.*",)
except ObjectDoesNotExist:
user_notifications_count = AnalyticsPath.objects.create(ignore_path=r"^\/user_notifications_count\/.*")
try:
admin = AnalyticsPath.objects.get(ignore_path=r"^\/admin\/.*")
except ObjectDoesNotExist:
admin = AnalyticsPath.objects.create(ignore_path=r"^\/admin\/.*")
try:
account_activate = AnalyticsPath.objects.get(ignore_path=r"^\/account\/activate\/.*")
except ObjectDoesNotExist:
account_activate = AnalyticsPath.objects.create(ignore_path=r"^\/account\/activate\/.*")
token.type = 'GA-V4'
token.token = 'G-6LYSMYK8DE'
token.secret = 'KLlpjLZ-SRGozS5f5wb_kw'
token.send_page_views = original_send_page_views
token.send_celery_tasks = original_send_celery_tasks
token.send_stats = original_send_stats
token.name = 'AA Team Public Google Analytics (V4)'
token.save()
token.ignore_paths.add(admin, user_notifications_count, account_activate)
token.save()
def remove_aa_team_token(apps, schema_editor):
# Have to define some code to remove this identifier
# In case of migration rollback?
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.filter(token="G-6LYSMYK8DE").delete()
class Migration(migrations.Migration):
dependencies = [
('analytics', '0007_analyticstokens_secret'),
]
operations = [migrations.RunPython(
add_aa_team_token, remove_aa_team_token)]

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.0.10 on 2023-05-08 05:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('analytics', '0008_add_AA_GA-4_Team_Token '),
]
operations = [
migrations.RemoveField(
model_name='analyticstokens',
name='ignore_paths',
),
migrations.RemoveField(
model_name='analyticstokens',
name='send_celery_tasks',
),
migrations.RemoveField(
model_name='analyticstokens',
name='send_page_views',
),
migrations.DeleteModel(
name='AnalyticsPath',
),
]

View File

@@ -7,22 +7,19 @@ from uuid import uuid4
class AnalyticsIdentifier(models.Model):
identifier = models.UUIDField(default=uuid4,
editable=False)
identifier = models.UUIDField(
default=uuid4,
editable=False)
def save(self, *args, **kwargs):
if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object
raise ValidationError('There is can be only one \
AnalyticsIdentifier instance')
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
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)
class AnalyticsPath(models.Model):
ignore_path = models.CharField(max_length=254, default="/example/", help_text="Regex Expression, If matched no Analytics Page View is sent")
class AnalyticsTokens(models.Model):
class Analytics_Type(models.TextChoices):
@@ -32,7 +29,5 @@ class AnalyticsTokens(models.Model):
name = models.CharField(max_length=254)
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
token = models.CharField(max_length=254, blank=False)
send_page_views = models.BooleanField(default=False)
send_celery_tasks = models.BooleanField(default=False)
secret = models.CharField(max_length=254, blank=True)
send_stats = models.BooleanField(default=False)
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)

View File

@@ -1,50 +0,0 @@
from allianceauth.analytics.tasks import analytics_event
from celery.signals import task_failure, task_success
import logging
logger = logging.getLogger(__name__)
@task_failure.connect
def process_failure_signal(
exception, traceback,
sender, task_id, signal,
args, kwargs, einfo, **kw):
logger.debug("Celery task_failure signal %s" % sender.__class__.__name__)
category = sender.__module__
if 'allianceauth.analytics' not in category:
if category.endswith(".tasks"):
category = category[:-6]
action = sender.__name__
label = f"{exception.__class__.__name__}"
analytics_event(category=category,
action=action,
label=label)
@task_success.connect
def celery_success_signal(sender, result=None, **kw):
logger.debug("Celery task_success signal %s" % sender.__class__.__name__)
category = sender.__module__
if 'allianceauth.analytics' not in category:
if category.endswith(".tasks"):
category = category[:-6]
action = sender.__name__
label = "Success"
value = 0
if isinstance(result, int):
value = result
analytics_event(category=category,
action=action,
label=label,
value=value)

View File

@@ -3,7 +3,6 @@ import logging
from django.conf import settings
from django.apps import apps
from celery import shared_task
from allianceauth import __version__
from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import (
install_stat_addons,
@@ -12,17 +11,17 @@ from .utils import (
logger = logging.getLogger(__name__)
BASE_URL = "https://www.google-analytics.com/"
BASE_URL = "https://www.google-analytics.com"
DEBUG_URL = f"{BASE_URL}debug/collect"
COLLECTION_URL = f"{BASE_URL}collect"
DEBUG_URL = f"{BASE_URL}/debug/mp/collect"
COLLECTION_URL = f"{BASE_URL}/mp/collect"
if getattr(settings, "ANALYTICS_ENABLE_DEBUG", False) and settings.DEBUG:
# Force sending of analytics data during in a debug/test environemt
# Usefull for developers working on this feature.
# Force sending of analytics data during in a debug/test environment
# Useful for developers working on this feature.
logger.warning(
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
"This debug instance will send analytics data!")
"You have 'ANALYTICS_ENABLE_DEBUG' Enabled! "
"This debug instance will send analytics data!")
DEBUG_URL = COLLECTION_URL
ANALYTICS_URL = COLLECTION_URL
@@ -31,177 +30,143 @@ if settings.DEBUG is True:
ANALYTICS_URL = DEBUG_URL
def analytics_event(category: str,
action: str,
label: str,
value: int = 0,
def analytics_event(namespace: str,
task: str,
label: str = "",
result: str = "",
value: int = 1,
event_type: str = 'Celery'):
"""
Send a Google Analytics Event for each token stored
Includes check for if its enabled/disabled
Parameters
-------
`category` (str): Celery Namespace
`action` (str): Task Name
`label` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a binary True/False
`event_type` (str): Optional, Celery or Stats only, Default to Celery
Args:
`namespace` (str): Celery Namespace
`task` (str): Task Name
`label` (str): Optional, additional task label
`result` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a Boolean
`event_type` (str): Optional, Celery or Stats only, Default to Celery
"""
analyticstokens = AnalyticsTokens.objects.all()
client_id = AnalyticsIdentifier.objects.get(id=1).identifier.hex
for token in analyticstokens:
if event_type == 'Celery':
allowed = token.send_celery_tasks
elif event_type == 'Stats':
for token in AnalyticsTokens.objects.filter(type='GA-V4'):
if event_type == 'Stats':
allowed = token.send_stats
else:
allowed = False
if allowed is True:
tracking_id = token.token
send_ga_tracking_celery_event.s(tracking_id=tracking_id,
client_id=client_id,
category=category,
action=action,
label=label,
value=value).\
apply_async(priority=9)
send_ga_tracking_celery_event.s(
measurement_id=token.token,
secret=token.secret,
namespace=namespace,
task=task,
label=label,
result=result,
value=value).apply_async(priority=9)
@shared_task()
def analytics_daily_stats():
"""Celery Task: Do not call directly
Gathers a series of daily statistics and sends analytics events containing them"""
Gathers a series of daily statistics
Sends analytics events containing them
"""
users = install_stat_users()
tokens = install_stat_tokens()
addons = install_stat_addons()
logger.debug("Running Daily Analytics Upload")
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='existence',
value=1,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='users',
value=users,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='tokens',
value=tokens,
event_type='Stats')
analytics_event(category='allianceauth.analytics',
action='send_install_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label='addons',
value=addons,
event_type='Stats')
for appconfig in apps.get_app_configs():
analytics_event(category='allianceauth.analytics',
action='send_extension_stats',
analytics_event(namespace='allianceauth.analytics',
task='send_extension_stats',
label=appconfig.label,
value=1,
event_type='Stats')
@shared_task()
def send_ga_tracking_web_view(
tracking_id: str,
client_id: str,
page: str,
title: str,
locale: str,
useragent: str) -> requests.Response:
"""Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware
Parameters
----------
`tracking_id` (str): Unique Server Identifier
`client_id` (str): GA Token
`page` (str): Page Path
`title` (str): Page Title
`locale` (str): Browser Language
`useragent` (str): Browser UserAgent
Returns
-------
requests.Reponse Object
"""
headers = {"User-Agent": useragent}
payload = {
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'pageview',
'dp': page,
'dt': title,
'ul': locale,
'ua': useragent,
'aip': 1,
'an': "allianceauth",
'av': __version__
}
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Page View HTTP{response.status_code}")
return response
@shared_task()
def send_ga_tracking_celery_event(
tracking_id: str,
client_id: str,
category: str,
action: str,
label: str,
value: int) -> requests.Response:
measurement_id: str,
secret: str,
namespace: str,
task: str,
label: str = "",
result: str = "",
value: int = 1):
"""Celery Task: Do not call directly
Sends Page View events to GA, Called only via analytics.middleware
Sends an events to GA
Parameters
----------
`tracking_id` (str): Unique Server Identifier
`client_id` (str): GA Token
`category` (str): Celery Namespace
`action` (str): Task Name
`label` (str): Optional, Task Success/Exception
`measurement_id` (str): GA Token
`secret` (str): GA Authentication Secret
`namespace` (str): Celery Namespace
`task` (str): Task Name
`label` (str): Optional, additional task label
`result` (str): Optional, Task Success/Exception
`value` (int): Optional, If bulk, Query size, can be a binary True/False
Returns
-------
requests.Reponse Object
"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"}
parameters = {
'measurement_id': measurement_id,
'api_secret': secret
}
payload = {
'v': '1',
'tid': tracking_id,
'cid': client_id,
't': 'event',
'ec': category,
'ea': action,
'el': label,
'ev': value,
'aip': 1,
'an': "allianceauth",
'av': __version__
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex,
"user_properties": {
"allianceauth_version": {
"value": "allianceauth_version"
}
response = requests.post(
ANALYTICS_URL, data=payload,
timeout=5, headers=headers)
logger.debug(f"Analytics Celery/Stats Event HTTP{response.status_code}")
return response
},
'non_personalized_ads': True,
"events": [{
"name": "celery_event",
"params": {
"namespace": namespace,
"task": task,
'result': result,
'label': label,
"value": value
}
}]
}
try:
response = requests.post(
ANALYTICS_URL,
params=parameters,
json=payload,
timeout=10)
response.raise_for_status()
logger.debug(
f"Analytics Celery/Stats Event HTTP{response.status_code}")
return response.status_code
except requests.exceptions.HTTPError as e:
logger.debug(e)
return response.status_code
except requests.exceptions.ConnectionError as e:
logger.debug(e)
return "Failed"

View File

@@ -1,23 +0,0 @@
from allianceauth.analytics.middleware import AnalyticsMiddleware
from unittest.mock import Mock
from django.test.testcases import TestCase
class TestAnalyticsMiddleware(TestCase):
def setUp(self):
self.middleware = AnalyticsMiddleware()
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/testURL/'
self.request.session = {}
self.request.LANGUAGE_CODE = 'en'
self.response = Mock()
self.response.content = 'hello world'
def test_middleware(self):
response = self.middleware.process_response(self.request, self.response)
self.assertEqual(self.response, response)

View File

@@ -23,4 +23,5 @@ class TestAnalyticsIdentifier(TestCase):
with self.assertRaises(ValidationError):
AnalyticsIdentifier.objects.create(identifier=uuid_2)
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
self.assertEqual(AnalyticsIdentifier.objects.get(pk=1).identifier, UUID(uuid_1))
self.assertEqual(AnalyticsIdentifier.objects.get(
pk=1).identifier, UUID(uuid_1))

View File

@@ -1,119 +1,69 @@
import requests_mock
from django.test.utils import override_settings
from allianceauth.analytics.tasks import (
analytics_event,
send_ga_tracking_celery_event,
send_ga_tracking_web_view)
from django.test.testcases import TestCase
send_ga_tracking_celery_event)
from allianceauth.utils.testing import NoSocketsTestCase
class TestAnalyticsTasks(TestCase):
def test_analytics_event(self):
GOOGLE_ANALYTICS_DEBUG_URL = 'https://www.google-analytics.com/debug/mp/collect'
@override_settings(CELERY_ALWAYS_EAGER=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True)
@requests_mock.Mocker()
class TestAnalyticsTasks(NoSocketsTestCase):
def test_analytics_event(self, requests_mocker):
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
analytics_event(
category='allianceauth.analytics',
action='send_tests',
label='test',
value=1,
event_type='Stats')
namespace='allianceauth.analytics',
task='send_tests',
label='test',
value=1,
result="Success",
event_type='Stats')
def test_send_ga_tracking_web_view_sent(self):
# This test sends if the event SENDS to google
# Not if it was successful
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent)
self.assertEqual(response.status_code, 200)
def test_send_ga_tracking_web_view_success(self):
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
json_response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent).json()
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
def test_send_ga_tracking_web_view_invalid_token(self):
tracking_id = 'UA-IntentionallyBadTrackingID-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
page = '/index/'
title = 'Hello World'
locale = 'en'
useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
json_response = send_ga_tracking_web_view(
tracking_id,
client_id,
page,
title,
locale,
useragent).json()
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
self.assertEqual(json_response["hitParsingResult"][0]["parserMessage"][1]["description"], "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.")
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
def test_send_ga_tracking_celery_event_sent(self):
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
def test_send_ga_tracking_celery_event_sent(self, requests_mocker):
# given
requests_mocker.register_uri('POST', GOOGLE_ANALYTICS_DEBUG_URL)
token = 'G-6LYSMYK8DE'
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
category = 'test'
action = 'test'
label = 'test'
value = '1'
response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value)
self.assertEqual(response.status_code, 200)
# when
task = send_ga_tracking_celery_event(
token,
secret,
category,
action,
label,
value)
# then
self.assertEqual(task, 200)
def test_send_ga_tracking_celery_event_success(self):
tracking_id = 'UA-186249766-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
def test_send_ga_tracking_celery_event_success(self, requests_mocker):
# given
requests_mocker.register_uri(
'POST',
GOOGLE_ANALYTICS_DEBUG_URL,
json={"validationMessages": []}
)
token = 'G-6LYSMYK8DE'
secret = 'KLlpjLZ-SRGozS5f5wb_kw',
category = 'test'
action = 'test'
label = 'test'
value = '1'
json_response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value).json()
self.assertTrue(json_response["hitParsingResult"][0]["valid"])
def test_send_ga_tracking_celery_event_invalid_token(self):
tracking_id = 'UA-IntentionallyBadTrackingID-2'
client_id = 'ab33e241fbf042b6aa77c7655a768af7'
category = 'test'
action = 'test'
label = 'test'
value = '1'
json_response = send_ga_tracking_celery_event(
tracking_id,
client_id,
category,
action,
label,
value).json()
self.assertFalse(json_response["hitParsingResult"][0]["valid"])
self.assertEqual(json_response["hitParsingResult"][0]["parserMessage"][1]["description"], "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.")
# [{'valid': False, 'parserMessage': [{'messageType': 'INFO', 'description': 'IP Address from this hit was anonymized to 1.132.110.0.', 'messageCode': 'VALUE_MODIFIED'}, {'messageType': 'ERROR', 'description': "The value provided for parameter 'tid' is invalid. Please see http://goo.gl/a8d4RP#tid for details.", 'messageCode': 'VALUE_INVALID', 'parameter': 'tid'}], 'hit': '/debug/collect?v=1&tid=UA-IntentionallyBadTrackingID-2&cid=ab33e241fbf042b6aa77c7655a768af7&t=pageview&dp=/index/&dt=Hello World&ul=en&ua=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36&aip=1&an=allianceauth&av=2.9.0a2'}]
# when
task = send_ga_tracking_celery_event(
token,
secret,
category,
action,
label,
value)
# then
self.assertTrue(task, 200)

View File

@@ -18,17 +18,17 @@ def create_testdata():
'abc@example.com',
'password'
)
#Token.objects.all().delete()
#Token.objects.create(
# Token.objects.all().delete()
# Token.objects.create(
# character_id=101,
# character_name='character1',
# access_token='my_access_token'
#)
#Token.objects.create(
# )
# Token.objects.create(
# character_id=102,
# character_name='character2',
# access_token='my_access_token'
#)
# )
class TestAnalyticsUtils(TestCase):
@@ -40,7 +40,7 @@ class TestAnalyticsUtils(TestCase):
users = install_stat_users()
self.assertEqual(users, expected)
#def test_install_stat_tokens(self):
# def test_install_stat_tokens(self):
# create_testdata()
# expected = 2
#

View File

@@ -1,5 +1,30 @@
from django.apps import AppConfig
from django.core.checks import Warning, Error, register
class AllianceAuthConfig(AppConfig):
name = 'allianceauth'
@register()
def check_settings(app_configs, **kwargs):
from django.conf import settings
errors = []
if hasattr(settings, "SITE_URL"):
if settings.SITE_URL[-1] == "/":
errors.append(Warning(
"'SITE_URL' Has a trailing slash. This may lead to incorrect links being generated by Auth."))
else:
errors.append(Error(
"No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules."))
if hasattr(settings, "CSRF_TRUSTED_ORIGINS"):
if hasattr(settings, "SITE_URL"):
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
errors.append(Warning(
"'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified."))
else:
errors.append(Error(
"No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified"))
return errors

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.authentication.apps.AuthenticationConfig'

View File

@@ -1,26 +1,44 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User as BaseUser, \
Permission as BasePermission, Group
from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission as BasePermission
from django.contrib.auth.models import User as BaseUser
from django.db.models import Count, Q
from allianceauth.services.hooks import ServicesHook
from django.db.models.signals import pre_save, post_save, pre_delete, \
post_delete, m2m_changed
from django.db.models.functions import Lower
from django.db.models.signals import (
m2m_changed,
post_delete,
post_save,
pre_delete,
pre_save
)
from django.dispatch import receiver
from django.forms import ModelForm
from django.utils.html import format_html
from django.urls import reverse
from django.utils.html import format_html
from django.utils.text import slugify
from allianceauth.authentication.models import State, get_guest_state,\
CharacterOwnership, UserProfile, OwnershipRecord
from allianceauth.hooks import get_hooks
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo, EveFactionInfo
from allianceauth.authentication.models import (
CharacterOwnership,
OwnershipRecord,
State,
UserProfile,
get_guest_state
)
from allianceauth.eveonline.models import (
EveAllianceInfo,
EveCharacter,
EveCorporationInfo,
EveFactionInfo
)
from allianceauth.eveonline.tasks import update_character
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
from allianceauth.hooks import get_hooks
from allianceauth.services.hooks import ServicesHook
from .app_settings import (
AUTHENTICATION_ADMIN_USERS_MAX_CHARS,
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
)
from .forms import UserChangeForm, UserProfileForm
def make_service_hooks_update_groups_action(service):
@@ -59,19 +77,10 @@ def make_service_hooks_sync_nickname_action(service):
return sync_nickname
class QuerysetModelForm(ModelForm):
# allows specifying FK querysets through kwarg
def __init__(self, querysets=None, *args, **kwargs):
querysets = querysets or {}
super().__init__(*args, **kwargs)
for field, qs in querysets.items():
self.fields[field].queryset = qs
class UserProfileInline(admin.StackedInline):
model = UserProfile
readonly_fields = ('state',)
form = QuerysetModelForm
form = UserProfileForm
verbose_name = ''
verbose_name_plural = 'Profile'
@@ -99,6 +108,7 @@ class UserProfileInline(admin.StackedInline):
return False
@admin.display(description="")
def user_profile_pic(obj):
"""profile pic column data for user objects
@@ -111,13 +121,10 @@ def user_profile_pic(obj):
'<img src="{}" class="img-circle">',
user_obj.profile.main_character.portrait_url(size=32)
)
else:
return None
user_profile_pic.short_description = ''
return None
@admin.display(description="user / main", ordering="username")
def user_username(obj):
"""user column data for user objects
@@ -139,18 +146,17 @@ def user_username(obj):
user_obj.username,
user_obj.profile.main_character.character_name
)
else:
return format_html(
'<strong><a href="{}">{}</a></strong>',
link,
user_obj.username,
)
user_username.short_description = 'user / main'
user_username.admin_order_field = 'username'
return format_html(
'<strong><a href="{}">{}</a></strong>',
link,
user_obj.username,
)
@admin.display(
description="Corporation / Alliance (Main)",
ordering="profile__main_character__corporation_name"
)
def user_main_organization(obj):
"""main organization column data for user objects
@@ -159,21 +165,15 @@ def user_main_organization(obj):
"""
user_obj = obj.user if hasattr(obj, 'user') else obj
if not user_obj.profile.main_character:
result = ''
else:
result = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id:
result += f'<br>{user_obj.profile.main_character.alliance_name}'
elif user_obj.profile.main_character.faction_name:
result += f'<br>{user_obj.profile.main_character.faction_name}'
return ''
result = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id:
result += f'<br>{user_obj.profile.main_character.alliance_name}'
elif user_obj.profile.main_character.faction_name:
result += f'<br>{user_obj.profile.main_character.faction_name}'
return format_html(result)
user_main_organization.short_description = 'Corporation / Alliance (Main)'
user_main_organization.admin_order_field = \
'profile__main_character__corporation_name'
class MainCorporationsFilter(admin.SimpleListFilter):
"""Custom filter to filter on corporations from mains only
@@ -196,15 +196,13 @@ class MainCorporationsFilter(admin.SimpleListFilter):
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs.filter(
profile__main_character__corporation_id=self.value()
)
else:
return qs.filter(
user__profile__main_character__corporation_id=self.value()
)
if qs.model == User:
return qs.filter(
profile__main_character__corporation_id=self.value()
)
return qs.filter(
user__profile__main_character__corporation_id=self.value()
)
class MainAllianceFilter(admin.SimpleListFilter):
@@ -217,12 +215,14 @@ class MainAllianceFilter(admin.SimpleListFilter):
parameter_name = 'main_alliance_id__exact'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(alliance_id=None)\
.exclude(userprofile=None)\
.values('alliance_id', 'alliance_name')\
.distinct()\
qs = (
EveCharacter.objects
.exclude(alliance_id=None)
.exclude(userprofile=None)
.values('alliance_id', 'alliance_name')
.distinct()
.order_by(Lower('alliance_name'))
)
return tuple(
(x['alliance_id'], x['alliance_name']) for x in qs
)
@@ -230,13 +230,11 @@ class MainAllianceFilter(admin.SimpleListFilter):
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs.filter(profile__main_character__alliance_id=self.value())
else:
return qs.filter(
user__profile__main_character__alliance_id=self.value()
)
if qs.model == User:
return qs.filter(profile__main_character__alliance_id=self.value())
return qs.filter(
user__profile__main_character__alliance_id=self.value()
)
class MainFactionFilter(admin.SimpleListFilter):
@@ -249,12 +247,14 @@ class MainFactionFilter(admin.SimpleListFilter):
parameter_name = 'main_faction_id__exact'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(faction_id=None)\
.exclude(userprofile=None)\
.values('faction_id', 'faction_name')\
.distinct()\
qs = (
EveCharacter.objects
.exclude(faction_id=None)
.exclude(userprofile=None)
.values('faction_id', 'faction_name')
.distinct()
.order_by(Lower('faction_name'))
)
return tuple(
(x['faction_id'], x['faction_name']) for x in qs
)
@@ -262,15 +262,14 @@ class MainFactionFilter(admin.SimpleListFilter):
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs.filter(profile__main_character__faction_id=self.value())
else:
return qs.filter(
user__profile__main_character__faction_id=self.value()
)
if qs.model == User:
return qs.filter(profile__main_character__faction_id=self.value())
return qs.filter(
user__profile__main_character__faction_id=self.value()
)
@admin.display(description="Update main character model from ESI")
def update_main_character_model(modeladmin, request, queryset):
tasks_count = 0
for obj in queryset:
@@ -279,33 +278,72 @@ def update_main_character_model(modeladmin, request, queryset):
tasks_count += 1
modeladmin.message_user(
request,
f'Update from ESI started for {tasks_count} characters'
request, f'Update from ESI started for {tasks_count} characters'
)
update_main_character_model.short_description = \
'Update main character model from ESI'
class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model
Behavior of groups and characters columns can be configured via settings
"""
inlines = [UserProfileInline]
ordering = ('username', )
list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True
list_display = (
user_profile_pic,
user_username,
'_state',
'_groups',
user_main_organization,
'_characters',
'is_active',
'date_joined',
'_role'
)
list_display_links = None
list_filter = (
'profile__state',
'groups',
MainCorporationsFilter,
MainAllianceFilter,
MainFactionFilter,
'is_active',
'date_joined',
'is_staff',
'is_superuser'
)
search_fields = ('username', 'character_ownerships__character__character_name')
readonly_fields = ('date_joined', 'last_login')
filter_horizontal = ('groups', 'user_permissions',)
form = UserChangeForm
class Media:
css = {
"all": ("authentication/css/admin.css",)
"all": ("allianceauth/authentication/css/admin.css",)
}
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.prefetch_related("character_ownerships__character", "groups")
def get_actions(self, request):
actions = super(BaseUserAdmin, self).get_actions(request)
def get_form(self, request, obj=None, **kwargs):
"""Inject current request into change form object."""
MyForm = super().get_form(request, obj, **kwargs)
if obj:
class MyFormInjected(MyForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return MyForm(*args, **kwargs)
return MyFormInjected
return MyForm
def get_actions(self, request):
actions = super().get_actions(request)
actions[update_main_character_model.__name__] = (
update_main_character_model,
update_main_character_model.__name__,
@@ -349,38 +387,6 @@ class UserAdmin(BaseUserAdmin):
)
return result
inlines = BaseUserAdmin.inlines + [UserProfileInline]
ordering = ('username', )
list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True
list_display = (
user_profile_pic,
user_username,
'_state',
'_groups',
user_main_organization,
'_characters',
'is_active',
'date_joined',
'_role'
)
list_display_links = None
list_filter = (
'profile__state',
'groups',
MainCorporationsFilter,
MainAllianceFilter,
MainFactionFilter,
'is_active',
'date_joined',
'is_staff',
'is_superuser'
)
search_fields = (
'username',
'character_ownerships__character__character_name'
)
def _characters(self, obj):
character_ownerships = list(obj.character_ownerships.all())
characters = [obj.character.character_name for obj in character_ownerships]
@@ -389,22 +395,16 @@ class UserAdmin(BaseUserAdmin):
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
)
_characters.short_description = 'characters'
@admin.display(ordering="profile__state")
def _state(self, obj):
return obj.profile.state.name
_state.short_description = 'state'
_state.admin_order_field = 'profile__state'
def _groups(self, obj):
my_groups = sorted(group.name for group in list(obj.groups.all()))
return self._list_2_html_w_tooltips(
my_groups, AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
)
_groups.short_description = 'groups'
def _role(self, obj):
if obj.is_superuser:
role = 'Superuser'
@@ -414,8 +414,6 @@ class UserAdmin(BaseUserAdmin):
role = 'User'
return role
_role.short_description = 'role'
def has_change_permission(self, request, obj=None):
return request.user.has_perm('auth.change_user')
@@ -425,12 +423,28 @@ class UserAdmin(BaseUserAdmin):
def has_delete_permission(self, request, obj=None):
return request.user.has_perm('auth.delete_user')
def get_object(self, *args , **kwargs):
obj = super().get_object(*args , **kwargs)
self.obj = obj # storing current object for use in formfield_for_manytomany
return obj
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""overriding this formfield to have sorted lists in the form"""
if db_field.name == "groups":
kwargs["queryset"] = Group.objects.all().order_by(Lower('name'))
groups_qs = Group.objects.filter(authgroup__states__isnull=True)
obj_state = self.obj.profile.state
if obj_state:
matching_groups_qs = Group.objects.filter(authgroup__states=obj_state)
groups_qs = groups_qs | matching_groups_qs
kwargs["queryset"] = groups_qs.order_by(Lower("name"))
return super().formfield_for_manytomany(db_field, request, **kwargs)
def get_readonly_fields(self, request, obj=None):
if obj and not request.user.is_superuser:
return self.readonly_fields + (
"is_staff", "is_superuser", "user_permissions"
)
return self.readonly_fields
@admin.register(State)
class StateAdmin(admin.ModelAdmin):
@@ -441,10 +455,9 @@ class StateAdmin(admin.ModelAdmin):
qs = super().get_queryset(request)
return qs.annotate(user_count=Count("userprofile__id"))
@admin.display(description="Users", ordering="user_count")
def _user_count(self, obj):
return obj.user_count
_user_count.short_description = 'Users'
_user_count.admin_order_field = 'user_count'
fieldsets = (
(None, {
@@ -500,13 +513,13 @@ class StateAdmin(admin.ModelAdmin):
)
return super().get_fieldsets(request, obj=obj)
def get_readonly_fields(self, request, obj=None):
if not request.user.is_superuser:
return self.readonly_fields + ("permissions",)
return self.readonly_fields
class BaseOwnershipAdmin(admin.ModelAdmin):
class Media:
css = {
"all": ("authentication/css/admin.css",)
}
list_select_related = (
'user__profile__state', 'user__profile__main_character', 'character')
list_display = (
@@ -527,6 +540,11 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
MainAllianceFilter,
)
class Media:
css = {
"all": ("allianceauth/authentication/css/admin.css",)
}
def get_readonly_fields(self, request, obj=None):
if obj and obj.pk:
return 'owner_hash', 'character'

View File

@@ -3,10 +3,14 @@ from django.core.checks import register, Tags
class AuthenticationConfig(AppConfig):
name = 'allianceauth.authentication'
label = 'authentication'
name = "allianceauth.authentication"
label = "authentication"
def ready(self):
super().ready()
from allianceauth.authentication import checks, signals
from allianceauth.authentication import checks, signals # noqa: F401
from allianceauth.authentication.task_statistics import (
signals as celery_signals,
)
register(Tags.security)(checks.check_login_scopes_setting)
celery_signals.reset_counters()

View File

@@ -0,0 +1,45 @@
from allianceauth.hooks import DashboardItemHook
from allianceauth import hooks
from .views import dashboard_characters, dashboard_groups, dashboard_admin
class UserCharactersHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_characters,
5
)
class UserGroupsHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_groups,
5
)
class AdminHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_admin,
0
)
@hooks.register('dashboard_hook')
def register_character_hook():
return UserCharactersHook()
@hooks.register('dashboard_hook')
def register_groups_hook():
return UserGroupsHook()
@hooks.register('dashboard_hook')
def register_admin_hook():
return AdminHook()

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 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

@@ -0,0 +1,48 @@
"""API for interacting with celery workers."""
import itertools
import logging
from typing import Optional
from amqp.exceptions import ChannelError
from celery import current_app
from django.conf import settings
logger = logging.getLogger(__name__)
def active_tasks_count() -> Optional[int]:
"""Return count of currently active tasks
or None if celery workers are not online.
"""
inspect = current_app.control.inspect()
return _tasks_count(inspect.active())
def _tasks_count(data: dict) -> Optional[int]:
"""Return count of tasks in data from celery inspect API."""
try:
tasks = itertools.chain(*data.values())
except AttributeError:
return None
return len(list(tasks))
def queued_tasks_count() -> Optional[int]:
"""Return count of queued tasks. Return None if there was an error."""
try:
with current_app.connection_or_acquire() as conn:
result = conn.default_channel.queue_declare(
queue=getattr(settings, "CELERY_DEFAULT_QUEUE", "celery"), passive=True
)
return result.message_count
except ChannelError:
# Queue doesn't exist, probably empty
return 0
except Exception:
logger.exception("Failed to get celery queue length")
return None

View File

@@ -1,18 +1,31 @@
from django.conf.urls import include
from django.urls import include
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
from functools import wraps
from 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 +35,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,66 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.forms import UserChangeForm as BaseUserChangeForm
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _
from allianceauth.authentication.models import User
class RegistrationForm(forms.Form):
email = forms.EmailField(label=_('Email'), max_length=254, required=True)
class _meta:
model = User
class UserProfileForm(ModelForm):
"""Allows specifying FK querysets through kwarg"""
def __init__(self, querysets=None, *args, **kwargs):
querysets = querysets or {}
super().__init__(*args, **kwargs)
for field, qs in querysets.items():
self.fields[field].queryset = qs
class UserChangeForm(BaseUserChangeForm):
"""Add custom cleaning to UserChangeForm"""
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request") # Inject current request into form object
super().__init__(*args, **kwargs)
def clean(self):
cleaned_data = super().clean()
if not self.request.user.is_superuser:
if self.instance:
current_restricted = set(
self.instance.groups.filter(
authgroup__restricted=True
).values_list("pk", flat=True)
)
else:
current_restricted = set()
new_restricted = set(
cleaned_data["groups"].filter(
authgroup__restricted=True
).values_list("pk", flat=True)
)
if current_restricted != new_restricted:
restricted_removed = current_restricted - new_restricted
restricted_added = new_restricted - current_restricted
restricted_changed = restricted_removed | restricted_added
restricted_names_qs = Group.objects.filter(
pk__in=restricted_changed
).values_list("name", flat=True)
restricted_names = ",".join(list(restricted_names_qs))
raise ValidationError(
{
"groups": _(
"You are not allowed to add or remove these "
"restricted groups: %s" % restricted_names
)
}
)

View File

@@ -1,14 +1,13 @@
from django.conf.urls import url, include
from allianceauth.authentication import views
from django.urls import include, re_path, path
urlpatterns = [
url(r'^activate/complete/$', views.activation_complete, name='registration_activation_complete'),
path('activate/complete/', views.activation_complete, name='registration_activation_complete'),
# The activation key can make use of any character from the
# URL-safe base64 alphabet, plus the colon as a separator.
url(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'),
url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
url(r'', include('django.contrib.auth.urls')),
re_path(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
path('register/', views.RegistrationView.as_view(), name='registration_register'),
path('register/complete/', views.registration_complete, name='registration_complete'),
path('register/closed/', views.registration_closed, name='registration_disallowed'),
path('', include('django.contrib.auth.urls')),
]

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

View File

@@ -0,0 +1,55 @@
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
import logging
logger = logging.getLogger(__name__)
class UserSettingsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
"""Django Middleware: User Settings."""
# Intercept the built in django /setlang/ view and also save it to Database.
# Note the annoymous user check, only logged in users will ever hit the DB here
if request.path == '/i18n/setlang/' and not request.user.is_anonymous:
try:
request.user.profile.language = request.POST['language']
request.user.profile.save()
except Exception as e:
logger.exception(e)
# Only act during the login flow, _after_ user is activated (step 2: post-sso)
elif request.path == '/sso/login' and not request.user.is_anonymous:
# Set the Language Cookie, if it doesnt match the DB
# Null = hasnt been set by the user ever, dont act.
try:
if request.user.profile.language != request.LANGUAGE_CODE and request.user.profile.language is not None:
response.set_cookie(key=settings.LANGUAGE_COOKIE_NAME,
value=request.user.profile.language,
max_age=settings.LANGUAGE_COOKIE_AGE)
except Exception as e:
logger.exception(e)
# AA v3 NIGHT_MODE
# Set our Night mode flag from the DB
# Null = hasnt been set by the user ever, dont act.
#
# Night mode intercept is not needed in this middleware.
# is saved direct to DB in NightModeRedirectView
try:
if request.user.profile.night_mode is not None:
request.session["NIGHT_MODE"] = request.user.profile.night_mode
except Exception as e:
logger.exception(e)
# AA v4 Themes
# Null = has not been set by the user ever, dont act
# DEFAULT_THEME or DEFAULT_THEME_DARK will be used in get_theme()
try:
if request.user.profile.theme is not None:
request.session["THEME"] = request.user.profile.theme
except Exception as e:
logger.exception(e)
return response

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.2 on 2022-02-26 03:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0019_merge_20211026_0919'),
]
operations = [
migrations.AddField(
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')], default='', max_length=10, verbose_name='Language'),
),
migrations.AddField(
model_name='userprofile',
name='night_mode',
field=models.BooleanField(blank=True, null=True, verbose_name='Night Mode'),
),
]

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",
),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.10 on 2023-10-07 07:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0021_alter_userprofile_language'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='theme',
field=models.CharField(blank=True, help_text='Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps', max_length=200, null=True, verbose_name='Theme'),
),
]

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

@@ -2,9 +2,10 @@ import logging
from django.contrib.auth.models import User, Permission
from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
from allianceauth.notifications import notify
from django.conf import settings
from .managers import CharacterOwnershipManager, StateManager
@@ -17,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()
@@ -62,9 +63,51 @@ class UserProfile(models.Model):
class Meta:
default_permissions = ('change',)
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
main_character = models.OneToOneField(EveCharacter, blank=True, null=True, on_delete=models.SET_NULL)
state = models.ForeignKey(State, on_delete=models.SET_DEFAULT, default=get_guest_state_pk)
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',
on_delete=models.CASCADE)
main_character = models.OneToOneField(
EveCharacter,
blank=True,
null=True,
on_delete=models.SET_NULL)
state = models.ForeignKey(
State,
on_delete=models.SET_DEFAULT,
default=get_guest_state_pk)
language = models.CharField(
_("Language"), max_length=10,
choices=Language.choices,
blank=True,
default='')
night_mode = models.BooleanField(
_("Night Mode"),
blank=True,
null=True)
theme = models.CharField(
_("Theme"),
max_length=200,
blank=True,
null=True,
help_text="Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps"
)
def assign_state(self, state=None, commit=True):
if not state:
@@ -93,8 +136,6 @@ class UserProfile(models.Model):
def __str__(self):
return str(self.user)
class CharacterOwnership(models.Model):
class Meta:
default_permissions = ('change', 'delete')

View File

@@ -1,6 +1,11 @@
import logging
from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord
from .models import (
CharacterOwnership,
UserProfile,
get_guest_state,
State,
OwnershipRecord)
from django.contrib.auth.models import User
from django.db.models import Q
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
@@ -11,7 +16,7 @@ from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__)
state_changed = Signal(providing_args=['user', 'state'])
state_changed = Signal()
def trigger_state_check(state):
@@ -71,7 +76,7 @@ def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
@receiver(post_save, sender=User)
def create_required_models(sender, instance, created, *args, **kwargs):
# ensure all users have a model
# ensure all users have our Sub-Models
if created:
logger.debug(f'User {instance} created. Creating default UserProfile.')
UserProfile.objects.get_or_create(user=instance)

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
"""Counters for Task Statistics."""
import datetime as dt
from typing import NamedTuple, Optional
from .event_series import EventSeries
# Global series for counting task events.
succeeded_tasks = EventSeries("SUCCEEDED_TASKS")
retried_tasks = EventSeries("RETRIED_TASKS")
failed_tasks = EventSeries("FAILED_TASKS")
class _TaskCounts(NamedTuple):
succeeded: int
retried: int
failed: int
total: int
earliest_task: Optional[dt.datetime]
hours: int
def dashboard_results(hours: int) -> _TaskCounts:
"""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 = []
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)
return _TaskCounts(
succeeded=succeeded_count,
retried=retried_count,
failed=failed_count,
total=succeeded_count + retried_count + failed_count,
earliest_task=min(earliest_events) if earliest_events else None,
hours=hours,
)

View File

@@ -0,0 +1,100 @@
"""Event series for Task Statistics."""
import datetime as dt
import logging
from typing import List, Optional
from pytz import utc
from redis import Redis
from .helpers import get_redis_client_or_stub
logger = logging.getLogger(__name__)
class EventSeries:
"""API for recording and analyzing a series of events."""
_ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES"
def __init__(self, key_id: str, redis: Optional[Redis] = None) -> None:
self._redis = get_redis_client_or_stub() if not redis else redis
self._key_id = str(key_id)
self.clear()
@property
def is_disabled(self):
"""True when this object is disabled, e.g. Redis was not available at startup."""
return hasattr(self._redis, "IS_STUB")
@property
def _key_counter(self):
return f"{self._ROOT_KEY}_{self._key_id}_COUNTER"
@property
def _key_sorted_set(self):
return f"{self._ROOT_KEY}_{self._key_id}_SORTED_SET"
def add(self, event_time: dt.datetime = None) -> None:
"""Add event.
Args:
- event_time: timestamp of event. Will use current time if not specified.
"""
if not event_time:
event_time = dt.datetime.utcnow()
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."""
return [
event[1]
for event in self._redis.zrangebyscore(
self._key_sorted_set,
"-inf",
"+inf",
withscores=True,
score_cast_func=self._cast_scores_to_dt,
)
]
def clear(self) -> None:
"""Clear all events."""
self._redis.delete(self._key_sorted_set)
self._redis.delete(self._key_counter)
def count(self, earliest: dt.datetime = None, latest: dt.datetime = None) -> int:
"""Count of events, can be restricted to given time frame.
Args:
- 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
"""
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.
Args:
- earliest: Date of first events to count(inclusive), or any if not specified
"""
minimum = "-inf" if not earliest else earliest.timestamp()
event = self._redis.zrangebyscore(
self._key_sorted_set,
minimum,
"+inf",
withscores=True,
start=0,
num=1,
score_cast_func=self._cast_scores_to_dt,
)
if not event:
return None
return event[0][1]
@staticmethod
def _cast_scores_to_dt(score) -> dt.datetime:
return dt.datetime.fromtimestamp(float(score), tz=utc)

View File

@@ -0,0 +1,49 @@
"""Helpers for Task Statistics."""
import logging
from redis import Redis, RedisError
from allianceauth.utils.cache import get_redis_client
logger = logging.getLogger(__name__)
class _RedisStub:
"""Stub of a Redis client.
It's purpose is to prevent EventSeries objects from trying to access Redis
when it is not available. e.g. when the Sphinx docs are rendered by readthedocs.org.
"""
IS_STUB = True
def delete(self, *args, **kwargs):
pass
def incr(self, *args, **kwargs):
return 0
def zadd(self, *args, **kwargs):
pass
def zcount(self, *args, **kwargs):
pass
def zrangebyscore(self, *args, **kwargs):
pass
def get_redis_client_or_stub() -> Redis:
"""Return AA's default cache client or a stub if Redis is not available."""
redis = get_redis_client()
try:
if not redis.ping():
raise RuntimeError()
except (AttributeError, RedisError, RuntimeError):
logger.exception(
"Failed to establish a connection with Redis. "
"This EventSeries object is disabled.",
)
return _RedisStub()
return redis

View File

@@ -0,0 +1,53 @@
"""Signals for Task Statistics."""
from celery.signals import (
task_failure, task_internal_error, task_retry, task_success, worker_ready,
)
from django.conf import settings
from .counters import failed_tasks, retried_tasks, succeeded_tasks
def reset_counters():
"""Reset all counters for the celery status."""
succeeded_tasks.clear()
failed_tasks.clear()
retried_tasks.clear()
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)
)
@worker_ready.connect
def reset_counters_when_celery_restarted(*args, **kwargs):
if is_enabled():
reset_counters()
@task_success.connect
def record_task_succeeded(*args, **kwargs):
if is_enabled():
succeeded_tasks.add()
@task_retry.connect
def record_task_retried(*args, **kwargs):
if is_enabled():
retried_tasks.add()
@task_failure.connect
def record_task_failed(*args, **kwargs):
if is_enabled():
failed_tasks.add()
@task_internal_error.connect
def record_task_internal_error(*args, **kwargs):
if is_enabled():
failed_tasks.add()

View File

@@ -0,0 +1,52 @@
import datetime as dt
from django.test import TestCase
from django.utils.timezone import now
from allianceauth.authentication.task_statistics.counters import (
dashboard_results, failed_tasks, retried_tasks, succeeded_tasks,
)
class TestDashboardResults(TestCase):
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()
# when
results = dashboard_results(hours=1)
# then
self.assertEqual(results.succeeded, 3)
self.assertEqual(results.retried, 2)
self.assertEqual(results.failed, 1)
self.assertEqual(results.total, 6)
self.assertEqual(results.earliest_task, earliest_task)
def test_should_work_with_no_data(self):
# given
succeeded_tasks.clear()
retried_tasks.clear()
failed_tasks.clear()
# when
results = dashboard_results(hours=1)
# then
self.assertEqual(results.succeeded, 0)
self.assertEqual(results.retried, 0)
self.assertEqual(results.failed, 0)
self.assertEqual(results.total, 0)
self.assertIsNone(results.earliest_task)

View File

@@ -0,0 +1,151 @@
import datetime as dt
from pytz import utc
from django.test import TestCase
from django.utils.timezone import now
from allianceauth.authentication.task_statistics.event_series import (
EventSeries,
)
from allianceauth.authentication.task_statistics.helpers import _RedisStub
MODULE_PATH = "allianceauth.authentication.task_statistics.event_series"
class TestEventSeries(TestCase):
def test_should_add_event(self):
# given
events = EventSeries("dummy")
# when
events.add()
# then
result = events.all()
self.assertEqual(len(result), 1)
self.assertAlmostEqual(result[0], now(), delta=dt.timedelta(seconds=30))
def test_should_add_event_with_specified_time(self):
# given
events = EventSeries("dummy")
my_time = dt.datetime(2021, 11, 1, 12, 15, tzinfo=utc)
# when
events.add(my_time)
# then
result = events.all()
self.assertEqual(len(result), 1)
self.assertAlmostEqual(result[0], my_time, delta=dt.timedelta(seconds=30))
def test_should_count_events(self):
# given
events = EventSeries("dummy")
events.add()
events.add()
# when
result = events.count()
# then
self.assertEqual(result, 2)
def test_should_count_zero(self):
# given
events = EventSeries("dummy")
# when
result = events.count()
# then
self.assertEqual(result, 0)
def test_should_count_events_within_timeframe_1(self):
# given
events = EventSeries("dummy")
events.add(dt.datetime(2021, 12, 1, 12, 0, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 10, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 15, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 30, tzinfo=utc))
# when
result = events.count(
earliest=dt.datetime(2021, 12, 1, 12, 8, tzinfo=utc),
latest=dt.datetime(2021, 12, 1, 12, 17, tzinfo=utc),
)
# then
self.assertEqual(result, 2)
def test_should_count_events_within_timeframe_2(self):
# given
events = EventSeries("dummy")
events.add(dt.datetime(2021, 12, 1, 12, 0, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 10, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 15, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 30, tzinfo=utc))
# when
result = events.count(earliest=dt.datetime(2021, 12, 1, 12, 8))
# then
self.assertEqual(result, 3)
def test_should_count_events_within_timeframe_3(self):
# given
events = EventSeries("dummy")
events.add(dt.datetime(2021, 12, 1, 12, 0, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 10, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 15, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 30, tzinfo=utc))
# when
result = events.count(latest=dt.datetime(2021, 12, 1, 12, 12))
# then
self.assertEqual(result, 2)
def test_should_clear_events(self):
# given
events = EventSeries("dummy")
events.add()
events.add()
# when
events.clear()
# then
self.assertEqual(events.count(), 0)
def test_should_return_date_of_first_event(self):
# given
events = EventSeries("dummy")
events.add(dt.datetime(2021, 12, 1, 12, 0, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 10, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 15, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 30, tzinfo=utc))
# when
result = events.first_event()
# then
self.assertEqual(result, dt.datetime(2021, 12, 1, 12, 0, tzinfo=utc))
def test_should_return_date_of_first_event_with_range(self):
# given
events = EventSeries("dummy")
events.add(dt.datetime(2021, 12, 1, 12, 0, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 10, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 15, tzinfo=utc))
events.add(dt.datetime(2021, 12, 1, 12, 30, tzinfo=utc))
# when
result = events.first_event(
earliest=dt.datetime(2021, 12, 1, 12, 8, tzinfo=utc)
)
# then
self.assertEqual(result, dt.datetime(2021, 12, 1, 12, 10, tzinfo=utc))
def test_should_return_all_events(self):
# given
events = EventSeries("dummy")
events.add()
events.add()
# when
results = events.all()
# then
self.assertEqual(len(results), 2)
def test_should_not_report_as_disabled_when_initialized_normally(self):
# given
events = EventSeries("dummy")
# when/then
self.assertFalse(events.is_disabled)
def test_should_report_as_disabled_when_initialized_with_redis_stub(self):
# given
events = EventSeries("dummy", redis=_RedisStub())
# when/then
self.assertTrue(events.is_disabled)

View File

@@ -0,0 +1,28 @@
from unittest import TestCase
from unittest.mock import patch
from redis import RedisError
from allianceauth.authentication.task_statistics.helpers import (
_RedisStub, get_redis_client_or_stub,
)
MODULE_PATH = "allianceauth.authentication.task_statistics.helpers"
class TestGetRedisClient(TestCase):
def test_should_return_mock_if_redis_not_available_1(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
mock_get_master_client.return_value.ping.side_effect = RedisError
result = get_redis_client_or_stub()
# then
self.assertIsInstance(result, _RedisStub)
def test_should_return_mock_if_redis_not_available_2(self):
# when
with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client:
mock_get_master_client.return_value.ping.return_value = False
result = get_redis_client_or_stub()
# then
self.assertIsInstance(result, _RedisStub)

View File

@@ -0,0 +1,86 @@
from unittest.mock import patch
from celery.exceptions import Retry
from django.test import TestCase, override_settings
from allianceauth.authentication.task_statistics.counters import (
failed_tasks,
retried_tasks,
succeeded_tasks,
)
from allianceauth.authentication.task_statistics.signals import (
reset_counters,
is_enabled,
)
from allianceauth.eveonline.tasks import update_character
@override_settings(
CELERY_ALWAYS_EAGER=True, ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False
)
class TestTaskSignals(TestCase):
fixtures = ["disable_analytics"]
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"
) as mock_update:
mock_update.return_value = None
update_character.delay(1)
# then
self.assertEqual(succeeded_tasks.count(), 1)
self.assertEqual(retried_tasks.count(), 0)
self.assertEqual(failed_tasks.count(), 0)
def test_should_record_retried_task(self):
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
) as mock_update:
mock_update.side_effect = Retry
update_character.delay(1)
# then
self.assertEqual(succeeded_tasks.count(), 0)
self.assertEqual(failed_tasks.count(), 0)
self.assertEqual(retried_tasks.count(), 1)
def test_should_record_failed_task(self):
# when
with patch(
"allianceauth.eveonline.tasks.EveCharacter.objects.update_character"
) as mock_update:
mock_update.side_effect = RuntimeError
update_character.delay(1)
# then
self.assertEqual(succeeded_tasks.count(), 0)
self.assertEqual(retried_tasks.count(), 0)
self.assertEqual(failed_tasks.count(), 1)
def test_should_reset_counters(self):
# given
succeeded_tasks.add()
retried_tasks.add()
failed_tasks.add()
# when
reset_counters()
# then
self.assertEqual(succeeded_tasks.count(), 0)
self.assertEqual(retried_tasks.count(), 0)
self.assertEqual(failed_tasks.count(), 0)
class TestIsEnabled(TestCase):
@override_settings(ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=False)
def test_enabled(self):
self.assertTrue(is_enabled())
@override_settings(ALLIANCEAUTH_DASHBOARD_TASK_STATISTICS_DISABLED=True)
def test_disabled(self):
self.assertFalse(is_enabled())

View File

@@ -0,0 +1,46 @@
{% load i18n %}
<div class="col-12 col-xl-8 align-self-stretch p-2 ps-0">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<h4 class="ms-auto me-auto">
{% translate "Characters" %}
</h4>
</div>
<div class="card-body">
<div style="height: 300px; overflow-y:auto;">
<div class="d-flex">
<a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}">
<span class="d-md-inline m-2">{% translate 'Add Character' %}</span>
</a>
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Change Main' %}">
<span class="d-md-inline m-2">{% translate 'Change Main' %}</span>
</a>
</div>
<table class="table">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% translate "Name" %}</th>
<th class="text-center">{% translate "Corp" %}</th>
<th class="text-center">{% translate "Alliance" %}</th>
</tr>
</thead>
<tbody>
{% for char in characters %}
<tr>
<td class="text-center">
<img class="ra-avatar rounded-circle" src="{{ char.portrait_url_32 }}" alt="{{ char.character_name }}">
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name|default_if_none:"" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
{% load i18n %}
<div class="col-12 col-xl-4 align-self-stretch py-2 ps-2">
<div class="card h-100">
<div class="card-body">
<h4 class="card-title text-center">{% translate "Membership" %}</h4>
<div class="card-body">
<div style="height: 300px; overflow-y:auto;">
<h6 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h6>
<table class="table">
{% for group in groups %}
<tr>
<td class="text-center">{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,186 +1,15 @@
{% extends "allianceauth/base.html" %}
{% extends "allianceauth/base-bs5.html" %}
{% load static %}
{% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
{% block header_nav_brand %}
{% translate "Dashboard" %}
{% endblock %}
{% block content %}
<h1 class="page-header text-center">{% translate "Dashboard" %}</h1>
{% if user.is_staff %}
{% include 'allianceauth/admin-status/include.html' %}
{% endif %}
<div class="col-sm-12">
<div class="row vertical-flexbox-row2">
<div class="col-sm-6 text-center">
<div class="panel panel-primary" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">
{% blocktrans with state=request.user.profile.state %}
Main Character (State: {{ state }})
{% endblocktrans %}
</h3>
</div>
<div class="panel-body">
{% if request.user.profile.main_character %}
{% with request.user.profile.main_character as main %}
<div class="hidden-xs">
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.portrait_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.character_name }}</td>
</tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.corporation_logo_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.corporation_name }}</td>
</tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
{% if main.alliance_id %}
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.alliance_logo_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.alliance_name }}</td>
<tr>
</table>
{% elif main.faction_id %}
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.faction_logo_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.faction_name }}</td>
<tr>
</table>
{% endif %}
</div>
</div>
<div class="table visible-xs-block">
<p>
<img class="ra-avatar" src="{{ main.portrait_url_64 }}">
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
{% if main.alliance_id %}
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}">
{% endif %}
{% if main.faction_id %}
<img class="ra-avatar" src="{{ main.faction_logo_url_64 }}">
{% endif %}
</p>
<p>
<strong>{{ main.character_name }}</strong><br>
{{ main.corporation_name }}<br>
{% if main.alliance_id %}
{{ main.alliance_name }}<br>
{% endif %}
{% if main.faction_id %}
{{ main.faction_name }}
{% endif %}
</p>
</div>
{% endwith %}
{% else %}
<div class="alert alert-danger" role="alert">
{% translate "No main character set." %}
</div>
{% endif %}
<div class="clearfix"></div>
<div class="row">
<div class="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>
<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>
</div>
</div>
</div>
</div>
<div class="col-sm-6 text-center">
<div class="panel panel-success" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">{% translate "Group Memberships" %}</h3>
</div>
<div class="panel-body">
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<table class="table table-aa">
{% for group in groups %}
<tr>
<td>{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title text-center" style="text-align: center">
{% translate 'Characters' %}
</h3>
</div>
<div class="panel-body">
<table class="table table-aa hidden-xs">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% translate 'Name' %}</th>
<th class="text-center">{% translate 'Corp' %}</th>
<th class="text-center">{% translate 'Alliance' %}</th>
</tr>
</thead>
<tbody>
{% for char in characters %}
<tr>
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</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>
</tr>
{% endfor %}
</tbody>
</table>
<table class="table table-aa visible-xs-block" style="width: 100%">
<tbody>
{% for char in characters %}
<tr>
<td class="text-center" style="vertical-align: middle">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</td>
<td class="text-center" style="vertical-align: middle; width: 100%">
<strong>{{ char.character_name }}</strong><br>
{{ char.corporation_name }}<br>
{{ char.alliance_name|default:"" }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-around align-self-center flex-wrap">
{% for dash in views %}
{{ dash | safe }}
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,65 @@
{% extends "allianceauth/base-bs5.html" %}
{% load i18n %}
{% block page_title %}{% translate "Dashboard" %}{% endblock page_title %}
{% block content %}
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
<div>
<table class="table table-aa" id="table_tokens" style="width: 100%;">
<thead>
<tr>
<th>{% translate "Scopes" %}</th>
<th class="text-end">{% translate "Actions" %}</th>
<th>{% translate "Character" %}</th>
</tr>
</thead>
<tbody>
{% for t in tokens %}
<tr>
<td style="white-space:initial;">{% for s in t.scopes.all %}<span class="badge bg-secondary">{{ s.name }}</span>{% endfor %}</td>
<td nowrap class="text-end">
<a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="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 content %}
{% block extra_javascript %}
{% include "bundles/datatables-js-bs5.html" %}
{% endblock extra_javascript %}
{% block extra_css %}
{% include "bundles/datatables-css-bs5.html" %}
{% endblock extra_css %}
{% 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 extra_script %}

View File

@@ -1,4 +1,5 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
@@ -6,8 +7,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<!-- TODO Bundle all the site specific stuff up into its own template for easy overide -->
<meta property="og:title" content="{{ SITE_NAME }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
{% include 'allianceauth/icons.html' %}
@@ -21,7 +23,7 @@
<style>
body {
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat center center fixed;
background: url('{% static 'allianceauth/authentication/img/background.jpg' %}') no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
@@ -29,8 +31,9 @@
}
.panel-transparent {
background: rgba(48, 48, 48, 0.7);
background: rgba(48 48 48 / 0.7);
color: #ffffff;
padding-bottom: 21px;
}
.panel-body {
@@ -47,7 +50,7 @@
</style>
</head>
<body>
<div class="container" style="margin-top:150px">
<div class="container" style="margin-top:150px;">
{% block content %}
{% endblock %}
</div>

View File

@@ -5,8 +5,8 @@
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
{% 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 }})
<option lang="{{ language.code }}" value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ 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%}">
<img class="img-responsive center-block" src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
<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,5 +1,7 @@
{% extends 'public/base.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="col-md-4 col-md-offset-4">
{% if messages %}
@@ -7,6 +9,7 @@
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
{% endfor %}
{% endif %}
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<div class="col-md-12">
@@ -14,10 +17,25 @@
{% endblock %}
</div>
</div>
{% include 'public/lang_select.html' %}
<p class="text-center" style="margin-top: 2rem;">
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
<a href="https://www.eveonline.com/article/introducing-esi" target="_blank" rel="noopener noreferrer">
{% translate "Introducing ESI - A New API For Eve Online" %}
</a>
</p>
<p class="text-center">
<a href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
{% translate "Manage ESI Applications" %}
</a>
</p>
</div>
</div>
{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}

View File

@@ -1,6 +1,5 @@
{% extends 'public/base.html' %}
{% load static %}
{% load bootstrap %}
{% load i18n %}

View File

@@ -1,15 +0,0 @@
{% load i18n %}{% autoescape off %}
{% blocktrans trimmed %}You're receiving this email because you requested a password reset for your
user account.{% endblocktrans %}
{% translate "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{domain}}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% translate "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% translate "Thanks for using our site!" %}
{% blocktrans %}Your IT Team{% endblocktrans %}
{% endautoescape %}

View File

@@ -1,14 +0,0 @@
{% extends 'public/middle_box.html' %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block page_title %}{% translate "Register" %}{% endblock %}
{% block middle_box_content %}
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Submit" %}</button>
<br/>
</form>
{% endblock %}

View File

@@ -1,4 +1,17 @@
from django.db.models.signals import (
m2m_changed,
post_save,
pre_delete,
pre_save
)
from django.urls import reverse
from unittest import mock
MODULE_PATH = 'allianceauth.authentication'
def patch(target, *args, **kwargs):
return mock.patch(f'{MODULE_PATH}{target}', *args, **kwargs)
def get_admin_change_view_url(obj: object) -> str:

View File

@@ -0,0 +1,85 @@
from unittest.mock import patch
from amqp.exceptions import ChannelError
from django.test import TestCase
from allianceauth.authentication.core.celery_workers import (
active_tasks_count, queued_tasks_count,
)
MODULE_PATH = "allianceauth.authentication.core.celery_workers"
@patch(MODULE_PATH + ".current_app")
class TestActiveTasksCount(TestCase):
def test_should_return_correct_count_when_no_active_tasks(self, mock_current_app):
# given
mock_current_app.control.inspect.return_value.active.return_value = {
"queue": []
}
# when
result = active_tasks_count()
# then
self.assertEqual(result, 0)
def test_should_return_correct_task_count_for_active_tasks(self, mock_current_app):
# given
mock_current_app.control.inspect.return_value.active.return_value = {
"queue": [1, 2, 3]
}
# when
result = active_tasks_count()
# then
self.assertEqual(result, 3)
def test_should_return_correct_task_count_for_multiple_queues(
self, mock_current_app
):
# given
mock_current_app.control.inspect.return_value.active.return_value = {
"queue_1": [1, 2],
"queue_2": [3, 4],
}
# when
result = active_tasks_count()
# then
self.assertEqual(result, 4)
def test_should_return_none_when_celery_not_available(self, mock_current_app):
# given
mock_current_app.control.inspect.return_value.active.return_value = None
# when
result = active_tasks_count()
# then
self.assertIsNone(result)
@patch(MODULE_PATH + ".current_app")
class TestQueuedTasksCount(TestCase):
def test_should_return_queue_length_when_queue_exists(self, mock_current_app):
# given
mock_conn = (
mock_current_app.connection_or_acquire.return_value.__enter__.return_value
)
mock_conn.default_channel.queue_declare.return_value.message_count = 7
# when
result = queued_tasks_count()
# then
self.assertEqual(result, 7)
def test_should_return_0_when_queue_does_not_exists(self, mock_current_app):
# given
mock_current_app.connection_or_acquire.side_effect = ChannelError
# when
result = queued_tasks_count()
# then
self.assertEqual(result, 0)
def test_should_return_None_on_other_errors(self, mock_current_app):
# given
mock_current_app.connection_or_acquire.side_effect = RuntimeError
# when
result = queued_tasks_count()
# then
self.assertIsNone(result)

View File

@@ -1,6 +1,9 @@
from bs4 import BeautifulSoup
from urllib.parse import quote
from unittest.mock import patch, MagicMock
from django_webtest import WebTest
from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import Group
from django.test import TestCase, RequestFactory, Client
@@ -188,7 +191,7 @@ class TestCaseWithTestData(TestCase):
corporation_id=5432,
corporation_name="Xavier's School for Gifted Youngsters",
corporation_ticker='MUTNT',
alliance_id = None,
alliance_id=None,
faction_id=999,
faction_name='The X-Men',
)
@@ -206,6 +209,7 @@ class TestCaseWithTestData(TestCase):
cls.user_4.profile.save()
EveFactionInfo.objects.create(faction_id=999, faction_name='The X-Men')
def make_generic_search_request(ModelClass: type, search_term: str):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
@@ -218,6 +222,7 @@ def make_generic_search_request(ModelClass: type, search_term: str):
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
fixtures = ["disable_analytics"]
def setUp(self):
self.modeladmin = CharacterOwnershipAdmin(
@@ -244,6 +249,7 @@ class TestCharacterOwnershipAdmin(TestCaseWithTestData):
class TestOwnershipRecordAdmin(TestCaseWithTestData):
fixtures = ["disable_analytics"]
def setUp(self):
self.modeladmin = OwnershipRecordAdmin(
@@ -270,11 +276,12 @@ class TestOwnershipRecordAdmin(TestCaseWithTestData):
class TestStateAdmin(TestCaseWithTestData):
fixtures = ["disable_analytics"]
def setUp(self):
self.modeladmin = StateAdmin(
model=User, admin_site=AdminSite()
)
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.modeladmin = StateAdmin(model=User, admin_site=AdminSite())
def test_change_view_loads_normally(self):
User.objects.create_superuser(
@@ -299,6 +306,7 @@ class TestStateAdmin(TestCaseWithTestData):
class TestUserAdmin(TestCaseWithTestData):
fixtures = ["disable_analytics"]
def setUp(self):
self.factory = RequestFactory()
@@ -344,7 +352,7 @@ class TestUserAdmin(TestCaseWithTestData):
self.assertEqual(user_main_organization(self.user_3), expected)
def test_user_main_organization_u4(self):
expected="Xavier's School for Gifted Youngsters<br>The X-Men"
expected = "Xavier's School for Gifted Youngsters<br>The X-Men"
self.assertEqual(user_main_organization(self.user_4), expected)
def test_characters_u1(self):
@@ -419,7 +427,7 @@ class TestUserAdmin(TestCaseWithTestData):
# actions
@patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True)
@patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True, unsafe=True)
@patch(MODULE_PATH + '.update_character')
def test_action_update_main_character_model(
self, mock_task, mock_message_user
@@ -537,6 +545,229 @@ class TestUserAdmin(TestCaseWithTestData):
self.assertEqual(response.status_code, expected)
class TestStateAdminChangeFormSuperuserExclusiveEdits(WebTest):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.super_admin = User.objects.create_superuser("super_admin")
cls.staff_admin = User.objects.create_user("staff_admin")
cls.staff_admin.is_staff = True
cls.staff_admin.save()
cls.staff_admin = AuthUtils.add_permissions_to_user_by_name(
[
"authentication.add_state",
"authentication.change_state",
"authentication.view_state",
],
cls.staff_admin
)
cls.superuser_exclusive_fields = ["permissions",]
def test_should_show_all_fields_to_superuser_for_add(self):
# given
self.app.set_user(self.super_admin)
page = self.app.get("/admin/authentication/state/add/")
# when
form = page.forms["state_form"]
# then
for field in self.superuser_exclusive_fields:
with self.subTest(field=field):
self.assertIn(field, form.fields)
def test_should_not_show_all_fields_to_staff_admins_for_add(self):
# given
self.app.set_user(self.staff_admin)
page = self.app.get("/admin/authentication/state/add/")
# when
form = page.forms["state_form"]
# then
for field in self.superuser_exclusive_fields:
with self.subTest(field=field):
self.assertNotIn(field, form.fields)
def test_should_show_all_fields_to_superuser_for_change(self):
# given
self.app.set_user(self.super_admin)
state = AuthUtils.get_member_state()
page = self.app.get(f"/admin/authentication/state/{state.pk}/change/")
# when
form = page.forms["state_form"]
# then
for field in self.superuser_exclusive_fields:
with self.subTest(field=field):
self.assertIn(field, form.fields)
def test_should_not_show_all_fields_to_staff_admin_for_change(self):
# given
self.app.set_user(self.staff_admin)
state = AuthUtils.get_member_state()
page = self.app.get(f"/admin/authentication/state/{state.pk}/change/")
# when
form = page.forms["state_form"]
# then
for field in self.superuser_exclusive_fields:
with self.subTest(field=field):
self.assertNotIn(field, form.fields)
class TestUserAdminChangeForm(TestCase):
fixtures = ["disable_analytics"]
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.modeladmin = UserAdmin(model=User, admin_site=AdminSite())
def test_should_show_groups_available_to_user_with_blue_state_only(self):
# given
superuser = User.objects.create_superuser("Super")
user = AuthUtils.create_user("bruce_wayne")
character = AuthUtils.add_main_character_2(
user,
name="Bruce Wayne",
character_id=1001,
corp_id=2001,
corp_name="Wayne Technologies"
)
blue_state = State.objects.get(name="Blue")
blue_state.member_characters.add(character)
member_state = AuthUtils.get_member_state()
group_1 = Group.objects.create(name="Group 1")
group_2 = Group.objects.create(name="Group 2")
group_2.authgroup.states.add(blue_state)
group_3 = Group.objects.create(name="Group 3")
group_3.authgroup.states.add(member_state)
self.client.force_login(superuser)
# when
response = self.client.get(f"/admin/authentication/user/{user.pk}/change/")
# then
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.rendered_content, features="html.parser")
groups_select = soup.find("select", {"id": "id_groups"}).find_all('option')
group_ids = {int(option["value"]) for option in groups_select}
self.assertSetEqual(group_ids, {group_1.pk, group_2.pk})
class TestUserAdminChangeFormSuperuserExclusiveEdits(WebTest):
fixtures = ["disable_analytics"]
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.super_admin = User.objects.create_superuser("super_admin")
cls.staff_admin = User.objects.create_user("staff_admin")
cls.staff_admin.is_staff = True
cls.staff_admin.save()
cls.staff_admin = AuthUtils.add_permissions_to_user_by_name(
[
"auth.change_user",
"auth.view_user",
"authentication.change_user",
"authentication.change_userprofile",
"authentication.view_user"
],
cls.staff_admin
)
cls.superuser_exclusive_fields = [
"is_staff", "is_superuser", "user_permissions"
]
def setUp(self) -> None:
self.user = AuthUtils.create_user("bruce_wayne")
def test_should_show_all_fields_to_superuser_for_change(self):
# given
self.app.set_user(self.super_admin)
page = self.app.get(f"/admin/authentication/user/{self.user.pk}/change/")
# when
form = page.forms["user_form"]
# then
for field in self.superuser_exclusive_fields:
with self.subTest(field=field):
self.assertIn(field, form.fields)
def test_should_not_show_all_fields_to_staff_admin_for_change(self):
# given
self.app.set_user(self.staff_admin)
page = self.app.get(f"/admin/authentication/user/{self.user.pk}/change/")
# when
form = page.forms["user_form"]
# then
for field in self.superuser_exclusive_fields:
with self.subTest(field=field):
self.assertNotIn(field, form.fields)
def test_should_allow_super_admin_to_add_restricted_group_to_user(self):
# given
self.app.set_user(self.super_admin)
group_restricted = Group.objects.create(name="restricted group")
group_restricted.authgroup.restricted = True
group_restricted.authgroup.save()
page = self.app.get(f"/admin/authentication/user/{self.user.pk}/change/")
form = page.forms["user_form"]
# when
form["groups"].select_multiple(texts=["restricted group"])
response = form.submit("save")
# then
self.assertEqual(response.status_code, 302)
self.user.refresh_from_db()
self.assertIn(
"restricted group", self.user.groups.values_list("name", flat=True)
)
def test_should_not_allow_staff_admin_to_add_restricted_group_to_user(self):
# given
self.app.set_user(self.staff_admin)
group_restricted = Group.objects.create(name="restricted group")
group_restricted.authgroup.restricted = True
group_restricted.authgroup.save()
page = self.app.get(f"/admin/authentication/user/{self.user.pk}/change/")
form = page.forms["user_form"]
# when
form["groups"].select_multiple(texts=["restricted group"])
response = form.submit("save")
# then
self.assertEqual(response.status_code, 200)
self.assertIn(
"You are not allowed to add or remove these restricted groups",
response.text
)
def test_should_not_allow_staff_admin_to_remove_restricted_group_from_user(self):
# given
self.app.set_user(self.staff_admin)
group_restricted = Group.objects.create(name="restricted group")
group_restricted.authgroup.restricted = True
group_restricted.authgroup.save()
self.user.groups.add(group_restricted)
page = self.app.get(f"/admin/authentication/user/{self.user.pk}/change/")
form = page.forms["user_form"]
# when
form["groups"].select_multiple(texts=[])
response = form.submit("save")
# then
self.assertEqual(response.status_code, 200)
self.assertIn(
"You are not allowed to add or remove these restricted groups",
response.text
)
def test_should_allow_staff_admin_to_add_normal_group_to_user(self):
# given
self.app.set_user(self.super_admin)
Group.objects.create(name="normal group")
page = self.app.get(f"/admin/authentication/user/{self.user.pk}/change/")
form = page.forms["user_form"]
# when
form["groups"].select_multiple(texts=["normal group"])
response = form.submit("save")
# then
self.assertEqual(response.status_code, 302)
self.user.refresh_from_db()
self.assertIn("normal group", self.user.groups.values_list("name", flat=True))
class TestMakeServicesHooksActions(TestCaseWithTestData):
class MyServicesHookTypeA(ServicesHook):

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

@@ -0,0 +1,175 @@
from unittest import mock
from allianceauth.authentication.middleware import UserSettingsMiddleware
from unittest.mock import Mock
from django.http import HttpResponse
from django.test.testcases import TestCase
class TestUserSettingsMiddlewareSaveLang(TestCase):
def setUp(self):
self.middleware = UserSettingsMiddleware(HttpResponse)
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/i18n/setlang/'
self.request.POST = {
'language': 'fr'
}
self.request.user.profile.language = 'de'
self.request.user.is_anonymous = False
self.response = Mock()
self.response.content = 'hello world'
def test_middleware_passthrough(self):
"""
Simply tests the middleware runs cleanly
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.response, response)
def test_middleware_save_language_false_anonymous(self):
"""
Ensures the middleware wont change the usersettings
of a non-existent (anonymous) user
"""
self.request.user.is_anonymous = True
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.user.profile.language, 'de')
self.assertFalse(self.request.user.profile.save.called)
self.assertEqual(self.request.user.profile.save.call_count, 0)
def test_middleware_save_language_new(self):
"""
does the middleware change a language not set in the DB
"""
self.request.user.profile.language = None
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.user.profile.language, 'fr')
self.assertTrue(self.request.user.profile.save.called)
self.assertEqual(self.request.user.profile.save.call_count, 1)
def test_middleware_save_language_changed(self):
"""
Tests the middleware will change a language setting
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.user.profile.language, 'fr')
self.assertTrue(self.request.user.profile.save.called)
self.assertEqual(self.request.user.profile.save.call_count, 1)
class TestUserSettingsMiddlewareLoginFlow(TestCase):
def setUp(self):
self.middleware = UserSettingsMiddleware(HttpResponse)
self.request = Mock()
self.request.headers = {
"User-Agent": "AUTOMATED TEST"
}
self.request.path = '/sso/login'
self.request.session = {
'NIGHT_MODE': False
}
self.request.LANGUAGE_CODE = 'en'
self.request.user.profile.language = 'de'
self.request.user.profile.night_mode = True
self.request.user.is_anonymous = False
self.response = Mock()
self.response.content = 'hello world'
def test_middleware_passthrough(self):
"""
Simply tests the middleware runs cleanly
"""
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.response, middleware_response)
def test_middleware_sets_language_cookie_true_no_cookie(self):
"""
tests the middleware will set a cookie, while none is set
"""
self.request.LANGUAGE_CODE = None
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertTrue(middleware_response.set_cookie.called)
self.assertEqual(middleware_response.set_cookie.call_count, 1)
args, kwargs = middleware_response.set_cookie.call_args
self.assertEqual(kwargs['value'], 'de')
def test_middleware_sets_language_cookie_true_wrong_cookie(self):
"""
tests the middleware will set a cookie, while a different value is set
"""
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertTrue(middleware_response.set_cookie.called)
self.assertEqual(middleware_response.set_cookie.call_count, 1)
args, kwargs = middleware_response.set_cookie.call_args
self.assertEqual(kwargs['value'], 'de')
def test_middleware_sets_language_cookie_false_anonymous(self):
"""
ensures the middleware wont set a value for a non existent user (anonymous)
"""
self.request.user.is_anonymous = True
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertFalse = middleware_response.set_cookie.called
self.assertEqual(middleware_response.set_cookie.call_count, 0)
def test_middleware_sets_language_cookie_false_already_set(self):
"""
tests the middleware skips setting the cookie, if its already set correctly
"""
self.request.user.profile.language = 'en'
middleware_response = self.middleware.process_response(
self.request,
self.response
)
self.assertFalse = middleware_response.set_cookie.called
self.assertEqual(middleware_response.set_cookie.call_count, 0)
def test_middleware_sets_night_mode_not_set(self):
"""
tests the middleware will set night_mode if not set
"""
self.request.session = {}
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.session["NIGHT_MODE"], True)
def test_middleware_sets_night_mode_set(self):
"""
tests the middleware will set night_mode if set.
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.session["NIGHT_MODE"], True)

View File

@@ -0,0 +1,94 @@
from allianceauth.authentication.models import User, UserProfile
from allianceauth.eveonline.models import (
EveCharacter,
EveCorporationInfo,
EveAllianceInfo
)
from django.db.models.signals import (
pre_save,
post_save,
pre_delete,
m2m_changed
)
from allianceauth.tests.auth_utils import AuthUtils
from django.test.testcases import TestCase
from unittest.mock import Mock
from . import patch
class TestUserProfileSignals(TestCase):
def setUp(self):
state = AuthUtils.get_member_state()
self.char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.alliance = EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_name='alliance name',
alliance_ticker='TIKR',
executor_corp_id='2345',
)
self.corp = EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_name='corp name',
corporation_ticker='TIKK',
member_count=10,
alliance=self.alliance,
)
state.member_alliances.add(self.alliance)
state.member_corporations.add(self.corp)
self.member = AuthUtils.create_user('test user')
self.member.profile.main_character = self.char
self.member.profile.save()
@patch('.signals.create_required_models')
def test_create_required_models_triggered_true(
self, create_required_models):
"""
Create a User object here,
to generate UserProfile models
"""
post_save.connect(create_required_models, sender=User)
AuthUtils.create_user('test_create_required_models_triggered')
self.assertTrue = create_required_models.called
self.assertEqual(create_required_models.call_count, 1)
user = User.objects.get(username='test_create_required_models_triggered')
self.assertIsNot(UserProfile.objects.get(user=user), False)
@patch('.signals.create_required_models')
def test_create_required_models_triggered_false(
self, create_required_models):
"""
Only call a User object Update here,
which does not need to generate UserProfile models
"""
post_save.connect(create_required_models, sender=User)
char = EveCharacter.objects.create(
character_id='1266',
character_name='test character2',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
self.assertTrue = create_required_models.called
self.assertEqual(create_required_models.call_count, 0)
self.assertIsNot(UserProfile.objects.get(user=self.member), False)

View File

@@ -1,6 +1,7 @@
from math import ceil
from unittest.mock import patch
import requests
import requests_mock
from packaging.version import Version as Pep440Version
@@ -8,12 +9,8 @@ from django.core.cache import cache
from django.test import TestCase
from allianceauth.templatetags.admin_status import (
status_overview,
_fetch_list_from_gitlab,
_current_notifications,
_current_version_summary,
_fetch_notification_issues_from_gitlab,
_latests_versions
_current_notifications, _current_version_summary, _fetch_list_from_gitlab,
_fetch_notification_issues_from_gitlab, _latests_versions, status_overview,
)
MODULE_PATH = 'allianceauth.templatetags'
@@ -54,17 +51,13 @@ TEST_VERSION = '2.6.5'
class TestStatusOverviewTag(TestCase):
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status._fetch_celery_queue_length')
@patch(MODULE_PATH + '.admin_status._current_version_summary')
@patch(MODULE_PATH + '.admin_status._current_notifications')
def test_status_overview(
self,
mock_current_notifications,
mock_current_version_info,
mock_fetch_celery_queue_length
self, mock_current_notifications, mock_current_version_info
):
# given
notifications = {
'notifications': GITHUB_NOTIFICATION_ISSUES[:5]
}
@@ -81,23 +74,19 @@ class TestStatusOverviewTag(TestCase):
'latest_beta_version': '2.4.4a1',
}
mock_current_version_info.return_value = version_info
mock_fetch_celery_queue_length.return_value = 3
# when
result = status_overview()
expected = {
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
'latest_major': True,
'latest_minor': True,
'latest_patch': True,
'latest_beta': False,
'current_version': TEST_VERSION,
'latest_major_version': '2.4.5',
'latest_minor_version': '2.4.0',
'latest_patch_version': '2.4.5',
'latest_beta_version': '2.4.4a1',
'task_queue_length': 3,
}
self.assertEqual(result, expected)
# then
self.assertEqual(result["notifications"], GITHUB_NOTIFICATION_ISSUES[:5])
self.assertTrue(result["latest_major"])
self.assertTrue(result["latest_minor"])
self.assertTrue(result["latest_patch"])
self.assertFalse(result["latest_beta"])
self.assertEqual(result["current_version"], TEST_VERSION)
self.assertEqual(result["latest_major_version"], '2.4.5')
self.assertEqual(result["latest_minor_version"], '2.4.0')
self.assertEqual(result["latest_patch_version"], '2.4.5')
self.assertEqual(result["latest_beta_version"], '2.4.4a1')
class TestNotifications(TestCase):
@@ -307,3 +296,25 @@ class TestFetchListFromGitlab(TestCase):
result = _fetch_list_from_gitlab(self.url, max_pages=max_pages)
self.assertEqual(result, GITHUB_TAGS[:4])
self.assertEqual(requests_mocker.call_count, max_pages)
@requests_mock.mock()
@patch(MODULE_PATH + '.admin_status.logger')
def test_should_not_raise_any_exception_from_github_request_but_log_as_warning(
self, requests_mocker, mock_logger
):
for my_exception in [
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,
requests.exceptions.URLRequired,
requests.exceptions.TooManyRedirects,
requests.exceptions.ConnectTimeout,
requests.exceptions.Timeout,
]:
requests_mocker.get(self.url, exc=my_exception)
try:
result = _fetch_list_from_gitlab(self.url)
except Exception as ex:
self.fail(f"Unexpected exception raised: {ex}")
self.assertTrue(mock_logger.warning.called)
self.assertListEqual(result, [])

View File

@@ -0,0 +1,39 @@
import json
from unittest.mock import patch
from django.test import RequestFactory, TestCase
from allianceauth.authentication.views import task_counts
from allianceauth.tests.auth_utils import AuthUtils
MODULE_PATH = "allianceauth.authentication.views"
def jsonresponse_to_dict(response) -> dict:
return json.loads(response.content)
@patch(MODULE_PATH + ".queued_tasks_count")
@patch(MODULE_PATH + ".active_tasks_count")
class TestRunningTasksCount(TestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.factory = RequestFactory()
cls.user = AuthUtils.create_user("bruce_wayne")
def test_should_return_data(
self, mock_active_tasks_count, mock_queued_tasks_count
):
# given
mock_active_tasks_count.return_value = 2
mock_queued_tasks_count.return_value = 3
request = self.factory.get("/")
request.user = self.user
# when
response = task_counts(request)
# then
self.assertEqual(response.status_code, 200)
self.assertDictEqual(
jsonresponse_to_dict(response), {"tasks_running": 2, "tasks_queued": 3}
)

View File

@@ -1,5 +1,4 @@
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.views.generic.base import TemplateView
from . import views
@@ -7,21 +6,37 @@ from . import views
app_name = 'authentication'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(
r'^account/login/$',
path('', views.index, name='index'),
path(
'account/login/',
TemplateView.as_view(template_name='public/login.html'),
name='login'
),
url(
r'^account/characters/main/$',
path(
'account/characters/main/',
views.main_character_change,
name='change_main_character'
),
url(
r'^account/characters/add/$',
path(
'account/characters/add/',
views.add_character,
name='add_character'
),
url(r'^dashboard/$', views.dashboard, name='dashboard'),
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'),
path('task-counts/', views.task_counts, name='task_counts'),
]

View File

@@ -1,31 +1,32 @@
import logging
from allianceauth.hooks import get_hooks
from django_registration.backends.activation.views import (
REGISTRATION_SALT, ActivationView as BaseActivationView,
RegistrationView as BaseRegistrationView,
)
from django_registration.signals import user_registered
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login, authenticate
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core import signing
from django.core.mail import EmailMultiAlternatives
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.template.loader import render_to_string
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter
from esi.decorators import token_required
from esi.models import Token
from django_registration.backends.activation.views import (
RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView,
REGISTRATION_SALT
)
from django_registration.signals import user_registered
from allianceauth.eveonline.models import EveCharacter
from .models import CharacterOwnership
from .core.celery_workers import active_tasks_count, queued_tasks_count
from .forms import RegistrationForm
from .models import CharacterOwnership
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
@@ -42,26 +43,95 @@ def index(request):
return redirect('authentication:dashboard')
@login_required
def dashboard(request):
def dashboard_groups(request):
groups = request.user.groups.all()
if _has_auto_groups:
groups = groups\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)
groups = groups.order_by('name')
context = {
'groups': groups,
}
return render_to_string('authentication/dashboard.groups.html', context=context, request=request)
def dashboard_characters(request):
characters = EveCharacter.objects\
.filter(character_ownership__user=request.user)\
.select_related()\
.order_by('character_name')
context = {
'groups': groups,
'characters': characters
}
return render_to_string('authentication/dashboard.characters.html', context=context, request=request)
def dashboard_admin(request):
if request.user.is_superuser:
return render_to_string('allianceauth/admin-status/include.html', request=request)
else:
return ""
@login_required
def dashboard(request):
_dash_items = list()
hooks = get_hooks('dashboard_hook')
items = [fn() for fn in hooks]
items.sort(key=lambda i: i.order)
for item in items:
_dash_items.append(item.render(request))
context = {
'views': _dash_items,
}
return render(request, 'authentication/dashboard.html', context)
@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)
def main_character_change(request, token):
@@ -89,7 +159,7 @@ def main_character_change(request, token):
def add_character(request, token):
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name})))
messages.success(request, _('Added %(name)s to your account.' % ({'name': token.character_name})))
else:
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
return redirect('authentication:dashboard')
@@ -230,8 +300,11 @@ class ActivationView(BaseActivationView):
def validate_key(self, activation_key):
try:
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
dump = signing.loads(
activation_key,
salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400
)
return dump
except signing.BadSignature:
return None
@@ -261,3 +334,12 @@ def activation_complete(request):
def registration_closed(request):
messages.error(request, _('Registration of new accounts is not allowed at this time.'))
return redirect('authentication:login')
def task_counts(request) -> JsonResponse:
"""Return task counts as JSON for an AJAX call."""
data = {
"tasks_running": active_tasks_count(),
"tasks_queued": queued_tasks_count()
}
return JsonResponse(data)

View File

@@ -32,6 +32,7 @@ def create_project(parser, options, args):
'python': shutil.which('python'),
'gunicorn': shutil.which('gunicorn'),
'celery': shutil.which('celery'),
'memmon': shutil.which('memmon'),
'extensions': ['py', 'conf', 'json'],
}

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

@@ -1 +0,0 @@
default_app_config = 'allianceauth.corputils.apps.CorpUtilsConfig'

View File

@@ -1,5 +1,6 @@
from allianceauth.services.hooks import MenuItemHook, UrlHook
from django.utils.translation import ugettext_lazy as _
from allianceauth.menu.hooks import MenuItemHook
from allianceauth.services.hooks import UrlHook
from django.utils.translation import gettext_lazy as _
from allianceauth import hooks
from allianceauth.corputils import urls

View File

@@ -1,37 +1,40 @@
{% extends 'allianceauth/base.html' %}
{% extends "allianceauth/base-bs5.html" %}
{% load i18n %}
{% block page_title %}{% translate "Corporation Member Data" %}{% endblock %}
{% block page_title %}
{% translate "Corporation Member Data" %}
{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<div>
<h1 class="page-header text-center">{% translate "Corporation Member Data" %}</h1>
<div class="col-lg-10 col-lg-offset-1 container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
{% for corpstat in available %}
<li>
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
</li>
{% endfor %}
</ul>
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" id="dLabel" class="dropdown-toggle" role="button" data-toggle="dropdown" aria-haspopup="false" aria-expanded="false">{% translate "Corporations" %}<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
{% for corpstat in available %}
<li>
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
</li>
{% endfor %}
</ul>
</li>
{% if perms.corputils.add_corpstats %}
<li>
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
</li>
{% if perms.corputils.add_corpstats %}
<li>
<a href="{% url 'corputils:add' %}">{% translate "Add" %}</a>
</li>
{% endif %}
</ul>
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
<div class="form-group">
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate "Search all corporations..." %}{% endif %}">
</div>
</form>
</div>
</nav>
{% block member_data %}{% endblock %}
</div>
{% endif %}
</ul>
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
<div class="form-group">
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% translate 'Search all corporations...' %}{% endif %}">
</div>
</form>
</div>
</nav>
{% block member_data %}
{% endblock member_data %}
</div>
{% endblock %}
{% endblock content %}

View File

@@ -3,177 +3,175 @@
{% load humanize %}
{% block member_data %}
{% if corpstats %}
<div class="row">
<div class="col-lg-12 text-center">
<table class="table">
<tr>
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}">
</td>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6">
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}">
</td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
<div>
<table class="table">
<tr>
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
</td>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6">
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
</td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<ul class="nav nav-pills pull-left">
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li>
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li>
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
</ul>
<div class="pull-right hidden-xs">
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}&nbsp;
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
<span class="glyphicon glyphicon-refresh"></span>
</a>
</div>
<div class="clearfix"></div>
<div>
<div class="panel panel-default">
<div class="panel-heading">
<ul class="nav nav-pills pull-left">
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li>
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li>
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li>
</ul>
<div class="pull-right hidden-xs">
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}&nbsp;
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
<span class="glyphicon glyphicon-refresh"></span>
</a>
</div>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade in active" id="mains">
{% if mains %}
<div class="table-responsive">
<table class="table table-hover" id="table-mains">
<thead>
<div class="clearfix"></div>
</div>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade in active" id="mains">
{% if mains %}
<div class="table-responsive">
<table class="table table-hover" id="table-mains">
<thead>
<tr>
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
<th></th>
</tr>
</thead>
<tbody>
{% for id, main in mains.items %}
<tr>
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
<th></th>
</tr>
</thead>
<tbody>
{% for id, main in mains.items %}
<tr>
<td class="text-center" style="vertical-align:middle">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ main.main.portrait_url_64 }}" class="img-circle">
<div class="caption text-center">
{{ main.main }}
</div>
<td class="text-center" style="vertical-align:middle">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
<div class="caption text-center">
{{ main.main }}
</div>
</td>
<td>
<table class="table table-hover">
{% for alt in main.alts %}
{% if forloop.first %}
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "Corporation" %}</th>
<th class="text-center">{% translate "Alliance" %}</th>
<th class="text-center"></th>
</tr>
{% endif %}
</div>
</td>
<td>
<table class="table table-hover">
{% for alt in main.alts %}
{% if forloop.first %}
<tr>
<td class="text-center" style="width:5%">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ alt.portrait_url_32 }}" class="img-circle">
</div>
</td>
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "Corporation" %}</th>
<th class="text-center">{% translate "Alliance" %}</th>
<th class="text-center"></th>
</tr>
{% endfor %}
</table>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="members">
{% if members %}
<div class="table-responsive">
<table class="table table-hover" id="table-members">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
<th class="text-center">{% translate "Main Character" %}</th>
<th class="text-center">{% translate "Main Corporation" %}</th>
<th class="text-center">{% translate "Main Alliance" %}</th>
{% endif %}
<tr>
<td class="text-center" style="width:5%">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
</div>
</td>
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
</tr>
{% endfor %}
{% for member in unregistered %}
<tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td class="text-center"></td>
<td class="text-center"></td>
<td class="text-center"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="members">
{% if members %}
<div class="table-responsive">
<table class="table table-hover" id="table-members">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
<th class="text-center">{% translate "Main Character" %}</th>
<th class="text-center">{% translate "Main Corporation" %}</th>
<th class="text-center">{% translate "Main Alliance" %}</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
<td class="text-center">{{ member }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
</tr>
</thead>
<tbody>
{% for member in unregistered %}
<tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endfor %}
{% for member in unregistered %}
<tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td class="text-center"></td>
<td class="text-center"></td>
<td class="text-center"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
<tr>
<th></th>
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% for member in unregistered %}
<tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td class="text-center">{{ member.character_name }}</td>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
@@ -181,18 +179,23 @@
</div>
{% endif %}
{% 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(){
$('#table-mains').DataTable({
"columnDefs": [
{ "sortable": false, "targets": [1] },
],
"stateSave": true,
"stateDuration": 0
});
$('#table-members').DataTable({
"columnDefs": [
@@ -200,6 +203,8 @@
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
"stateSave": true,
"stateDuration": 0
});
$('#table-unregistered').DataTable({
"columnDefs": [
@@ -207,6 +212,8 @@
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
"stateSave": true,
"stateDuration": 0
});
});

View File

@@ -21,10 +21,10 @@
<tbody>
{% for result in results %}
<tr {% if not result.1.registered %}class="danger"{% endif %}>
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle"></td>
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
<td class="text-center">{{ result.1.character_name }}</td>
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% translate "Killboard" %}</a></td>
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
<td class="text-center">{{ result.1.main_character.character_name }}</td>
<td class="text-center">{{ result.1.main_character.corporation_name }}</td>
<td class="text-center">{{ result.1.main_character.alliance_name }}</td>
@@ -43,6 +43,9 @@
{% endblock %}
{% block extra_script %}
$(document).ready(function(){
$('#table-search').DataTable();
$('#table-search').DataTable({
"stateSave": true,
"stateDuration": 0
});
});
{% endblock %}

View File

@@ -1,12 +1,11 @@
from django.conf.urls import url
from django.urls import path
from . import views
app_name = 'corputils'
urlpatterns = [
url(r'^$', views.corpstats_view, name='view'),
url(r'^add/$', views.corpstats_add, name='add'),
url(r'^(?P<corp_id>(\d)*)/$', views.corpstats_view, name='view_corp'),
url(r'^(?P<corp_id>(\d)+)/update/$', views.corpstats_update, name='update'),
url(r'^search/$', views.corpstats_search, name='search'),
]
path('', views.corpstats_view, name='view'),
path('add/', views.corpstats_add, name='add'),
path('<int:corp_id>/', views.corpstats_view, name='view_corp'),
path('<int:corp_id>/update/', views.corpstats_update, name='update'),
path('search/', views.corpstats_search, name='search'),
]

View File

@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required, permission_required,
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.db import IntegrityError
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from esi.decorators import token_required
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.eveonline.apps.EveonlineConfig'

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.eveonline.autogroups.apps.EveAutogroupsConfig'

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

@@ -93,7 +93,7 @@ class AutogroupsConfigTestCase(TestCase):
group_qs = Group.objects.filter(pk=group.pk)
self.assertIn(group, self.member.groups.all())
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
def test_update_alliance_group_membership_no_main_character(self):
obj = AutogroupsConfig.objects.create()
@@ -172,7 +172,7 @@ class AutogroupsConfigTestCase(TestCase):
group_qs = Group.objects.filter(pk=group.pk)
self.assertIn(group, self.member.groups.all())
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
self.assertQuerySetEqual(self.member.groups.all(), pre_groups | group_qs, ordered=False)
def test_update_corp_group_membership_no_state(self):
obj = AutogroupsConfig.objects.create(corp_groups=True)

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.10 on 2022-01-05 18:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0015_factions'),
]
operations = [
migrations.AlterField(
model_name='evecharacter',
name='character_name',
field=models.CharField(db_index=True, max_length=254),
),
]

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

@@ -1,16 +1,32 @@
from django.db import models
import logging
from typing import Union
from .managers import EveCharacterManager, EveCharacterProviderManager
from .managers import EveCorporationManager, EveCorporationProviderManager
from .managers import EveAllianceManager, EveAllianceProviderManager
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from esi.models import Token
from allianceauth.notifications import notify
from . import providers
from .evelinks import eveimageserver
from .managers import (
EveAllianceManager,
EveAllianceProviderManager,
EveCharacterManager,
EveCharacterProviderManager,
EveCorporationManager,
EveCorporationProviderManager,
)
logger = logging.getLogger(__name__)
_DEFAULT_IMAGE_SIZE = 32
DOOMHEIM_CORPORATION_ID = 1000001
class EveFactionInfo(models.Model):
"""A faction in Eve Online."""
faction_id = models.PositiveIntegerField(unique=True, db_index=True)
faction_name = models.CharField(max_length=254, unique=True)
@@ -52,8 +68,10 @@ class EveFactionInfo(models.Model):
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()
@@ -68,13 +86,12 @@ class EveAllianceInfo(models.Model):
for corp_id in alliance.corp_ids:
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
EveCorporationInfo.objects.create_corporation(corp_id)
EveCorporationInfo.objects.filter(
corporation_id__in=alliance.corp_ids).update(alliance=self
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(
alliance=self
)
EveCorporationInfo.objects\
.filter(alliance=self)\
.exclude(corporation_id__in=alliance.corp_ids)\
.update(alliance=None)
EveCorporationInfo.objects.filter(alliance=self).exclude(
corporation_id__in=alliance.corp_ids
).update(alliance=None)
def update_alliance(self, alliance: providers.Alliance = None):
if alliance is None:
@@ -119,8 +136,10 @@ class EveAllianceInfo(models.Model):
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)
@@ -182,8 +201,10 @@ class EveCorporationInfo(models.Model):
class EveCharacter(models.Model):
"""A character in Eve Online."""
character_id = models.PositiveIntegerField(unique=True)
character_name = models.CharField(max_length=254, unique=True)
character_name = models.CharField(max_length=254, db_index=True)
corporation_id = models.PositiveIntegerField()
corporation_name = models.CharField(max_length=254)
corporation_ticker = models.CharField(max_length=5)
@@ -198,12 +219,20 @@ class EveCharacter(models.Model):
class Meta:
indexes = [
models.Index(fields=['corporation_id',]),
models.Index(fields=['alliance_id',]),
models.Index(fields=['corporation_name',]),
models.Index(fields=['alliance_name',]),
models.Index(fields=['faction_id',]),
]
models.Index(fields=['corporation_id',]),
models.Index(fields=['alliance_id',]),
models.Index(fields=['corporation_name',]),
models.Index(fields=['alliance_name',]),
models.Index(fields=['faction_id',]),
]
def __str__(self):
return self.character_name
@property
def is_biomassed(self) -> bool:
"""Whether this character is dead or not."""
return self.corporation_id == DOOMHEIM_CORPORATION_ID
@property
def alliance(self) -> Union[EveAllianceInfo, None]:
@@ -249,10 +278,36 @@ class EveCharacter(models.Model):
self.faction_id = character.faction.id
self.faction_name = character.faction.name
self.save()
if self.is_biomassed:
self._remove_tokens_of_biomassed_character()
return self
def __str__(self):
return self.character_name
def _remove_tokens_of_biomassed_character(self) -> None:
"""Remove tokens of this biomassed character."""
try:
user = self.character_ownership.user
except ObjectDoesNotExist:
return
tokens_to_delete = Token.objects.filter(character_id=self.character_id)
tokens_count = tokens_to_delete.count()
if not tokens_count:
return
tokens_to_delete.delete()
logger.info(
"%d tokens from user %s for biomassed character %s [id:%s] deleted.",
tokens_count,
user,
self,
self.character_id,
)
notify(
user=user,
title=f"Character {self} biomassed",
message=(
f"Your former character {self} has been biomassed "
"and has been removed from the list of your alts."
)
)
@staticmethod
def generic_portrait_url(
@@ -336,7 +391,6 @@ class EveCharacter(models.Model):
"""image URL for alliance of this character or empty string"""
return self.alliance_logo_url(256)
def faction_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
"""image URL for alliance of this character or empty string"""
if self.faction_id:

View File

@@ -8,22 +8,24 @@ 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(
os.path.abspath(__file__)), 'swagger.json'
)
"""
Swagger spec operations:
get_alliances_alliance_id
get_alliances_alliance_id_corporations
get_corporations_corporation_id
get_characters_character_id
get_universe_types_type_id
post_character_affiliation
get_universe_factions
"""
# for the love of Bob please add operations you use here. I'm tired of breaking undocumented things.
ESI_OPERATIONS=[
'get_alliances_alliance_id',
'get_alliances_alliance_id_corporations',
'get_corporations_corporation_id',
'get_characters_character_id',
'post_characters_affiliation',
'get_universe_types_type_id',
'get_universe_factions',
'post_universe_names',
]
logger = logging.getLogger(__name__)
@@ -169,20 +171,21 @@ class EveProvider:
"""
:return: an ItemType object for the given ID
"""
raise NotImplemented()
raise NotImplementedError()
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(
@@ -206,7 +209,8 @@ class EveSwaggerProvider(EveProvider):
def __str__(self):
return 'esi'
def get_alliance(self, alliance_id):
def get_alliance(self, alliance_id: int) -> Alliance:
"""Fetch alliance from ESI."""
try:
data = self.client.Alliance.get_alliances_alliance_id(alliance_id=alliance_id).result()
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
@@ -222,7 +226,8 @@ class EveSwaggerProvider(EveProvider):
except HTTPNotFound:
raise ObjectNotFound(alliance_id, 'alliance')
def get_corp(self, corp_id):
def get_corp(self, corp_id: int) -> Corporation:
"""Fetch corporation from ESI."""
try:
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
model = Corporation(
@@ -238,29 +243,43 @@ class EveSwaggerProvider(EveProvider):
except HTTPNotFound:
raise ObjectNotFound(corp_id, 'corporation')
def get_character(self, character_id):
def get_character(self, character_id: int) -> Character:
"""Fetch character from ESI."""
try:
data = self.client.Character.get_characters_character_id(character_id=character_id).result()
character_name = self._fetch_character_name(character_id)
affiliation = self.client.Character.post_characters_affiliation(characters=[character_id]).result()[0]
model = Character(
id=character_id,
name=data['name'],
name=character_name,
corp_id=affiliation['corporation_id'],
alliance_id=affiliation['alliance_id'] if 'alliance_id' in affiliation else None,
faction_id=affiliation['faction_id'] if 'faction_id' in affiliation else None,
)
return model
except (HTTPNotFound, HTTPUnprocessableEntity):
except (HTTPNotFound, HTTPUnprocessableEntity, ObjectNotFound):
raise ObjectNotFound(character_id, 'character')
def _fetch_character_name(self, character_id: int) -> str:
"""Fetch character name from ESI."""
data = self.client.Universe.post_universe_names(ids=[character_id]).result()
character = data.pop() if data else None
if (
not character
or character["category"] != "character"
or character["id"] != character_id
):
raise ObjectNotFound(character_id, 'character')
return character["name"]
def get_all_factions(self):
"""Fetch all factions from ESI."""
if not self._faction_list:
self._faction_list = self.client.Universe.get_universe_factions().result()
return self._faction_list
def get_faction(self, faction_id):
faction_id=int(faction_id)
def get_faction(self, faction_id: int):
"""Fetch faction from ESI."""
faction_id = int(faction_id)
try:
if not self._faction_list:
_ = self.get_all_factions()
@@ -272,7 +291,8 @@ class EveSwaggerProvider(EveProvider):
except (HTTPNotFound, HTTPUnprocessableEntity, KeyError):
raise ObjectNotFound(faction_id, 'faction')
def get_itemtype(self, type_id):
def get_itemtype(self, type_id: int) -> ItemType:
"""Fetch inventory item from ESI."""
try:
data = self.client.Universe.get_universe_types_type_id(type_id=type_id).result()
return ItemType(id=type_id, name=data['name'])

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